@@ -5,9 +5,11 @@ import org.jetbrains.kotlinx.dataframe.AnyCol
55import  org.jetbrains.kotlinx.dataframe.AnyFrame 
66import  org.jetbrains.kotlinx.dataframe.AnyRow 
77import  org.jetbrains.kotlinx.dataframe.DataFrame 
8+ import  org.jetbrains.kotlinx.dataframe.api.CellAttributes 
89import  org.jetbrains.kotlinx.dataframe.api.FormattedFrame 
910import  org.jetbrains.kotlinx.dataframe.api.FormattingDsl 
1011import  org.jetbrains.kotlinx.dataframe.api.RowColFormatter 
12+ import  org.jetbrains.kotlinx.dataframe.api.and 
1113import  org.jetbrains.kotlinx.dataframe.api.asColumnGroup 
1214import  org.jetbrains.kotlinx.dataframe.api.asNumbers 
1315import  org.jetbrains.kotlinx.dataframe.api.format 
@@ -24,6 +26,7 @@ import org.jetbrains.kotlinx.dataframe.columns.FrameColumn
2426import  org.jetbrains.kotlinx.dataframe.dataTypes.IFRAME 
2527import  org.jetbrains.kotlinx.dataframe.dataTypes.IMG 
2628import  org.jetbrains.kotlinx.dataframe.impl.DataFrameSize 
29+ import  org.jetbrains.kotlinx.dataframe.impl.columns.addParentPath 
2730import  org.jetbrains.kotlinx.dataframe.impl.columns.addPath 
2831import  org.jetbrains.kotlinx.dataframe.impl.io.resizeKeepingAspectRatio 
2932import  org.jetbrains.kotlinx.dataframe.impl.renderType 
@@ -161,7 +164,11 @@ internal fun AnyFrame.toHtmlData(
161164 val  scripts =  mutableListOf<String >()
162165 val  queue =  LinkedList <RenderingQueueItem >()
163166
164-  fun  AnyFrame.columnToJs (col :  AnyCol , rowsLimit :  Int? , configuration :  DisplayConfiguration ): ColumnDataForJs  {
167+  fun  AnyFrame.columnToJs (
168+  col :  ColumnWithPath <* >,
169+  rowsLimit :  Int? ,
170+  configuration :  DisplayConfiguration ,
171+  ): ColumnDataForJs  {
165172 val  values =  if  (rowsLimit !=  null ) rows().take(rowsLimit) else  rows()
166173 val  scale =  if  (col.isNumber()) col.asNumbers().scale() else  1 
167174 val  format =  if  (scale >  0 ) {
@@ -170,23 +177,40 @@ internal fun AnyFrame.toHtmlData(
170177 RendererDecimalFormat .of(" %e"  )
171178 }
172179 val  renderConfig =  configuration.copy(decimalFormat =  format)
173-  val  contents =  values.map {
174-  val  value =  col[it ]
175-  val  content  =  value.toDataFrameLikeOrNull()
176-  if  (content  !=  null ) {
177-  val  df =  content .df()
180+  val  contents =  values.map { row  -> 
181+  val  value =  col[row ]
182+  val  dfLikeContent  =  value.toDataFrameLikeOrNull()
183+  if  (dfLikeContent  !=  null ) {
184+  val  df =  dfLikeContent .df()
178185 if  (df.isEmpty()) {
179186 HtmlContent (" "  , null )
180187 } else  {
181188 val  id =  nextTableId()
182-  queue + =  RenderingQueueItem (df, id, content .configuration(defaultConfiguration))
189+  queue + =  RenderingQueueItem (df, id, dfLikeContent .configuration(defaultConfiguration))
183190 DataFrameReference (id, df.size)
184191 }
185192 } else  {
186-  val  html = 
187-  formatter.format(downsizeBufferedImageIfNeeded(value, renderConfig), cellRenderer, renderConfig)
188-  val  style =  renderConfig.cellFormatter
189-  ?.invoke(FormattingDsl , it, col)
193+  val  html =  formatter.format(
194+  value =  downsizeBufferedImageIfNeeded(value, renderConfig),
195+  renderer =  cellRenderer,
196+  configuration =  renderConfig,
197+  )
198+ 
199+  val  formatter =  renderConfig.cellFormatter
200+  ? :  return @map HtmlContent (html, null )
201+ 
202+  //  ask formatter for all attributes defined for this cell or any of its parents (outer column groups)
203+  val  parentCols =  col.path.indices
204+  .map { i ->  col.path.take(i +  1 ) }
205+  .dropLast(1 )
206+  .map { ColumnWithPath (this @toHtmlData[it], it) }
207+  val  parentAttributes =  parentCols
208+  .map { formatter(FormattingDsl , row, it) }
209+  .reduceOrNull(CellAttributes ? ::and )
210+ 
211+  val  cellAttributes =  formatter(FormattingDsl , row, col)
212+ 
213+  val  style =  (parentAttributes and  cellAttributes)
190214 ?.attributes()
191215 ?.ifEmpty { null  }
192216 ?.flatMap {
@@ -204,12 +228,16 @@ internal fun AnyFrame.toHtmlData(
204228 listOf (it)
205229 }
206230 }
207-  ?.joinToString(" ;"  ) { " ${it.first} :${it.second} "   }
231+  ?.toMap() //  removing duplicate keys, allowing only the final one to be applied
232+  ?.entries
233+  ?.joinToString(" ;"  ) { " ${it.key} :${it.value} "   }
208234 HtmlContent (html, style)
209235 }
210236 }
211237 val  nested =  if  (col is  ColumnGroup <* >) {
212-  col.columns().map { col.columnToJs(it, rowsLimit, configuration) }
238+  col.columns().map {
239+  col.columnToJs(it.addParentPath(col.path), rowsLimit, configuration)
240+  }
213241 } else  {
214242 emptyList()
215243 }
@@ -226,7 +254,9 @@ internal fun AnyFrame.toHtmlData(
226254 while  (! queue.isEmpty()) {
227255 val  (nextDf, nextId, configuration) =  queue.pop()
228256 val  rowsLimit =  if  (nextId ==  rootId) configuration.rowsLimit else  configuration.nestedRowsLimit
229-  val  preparedColumns =  nextDf.columns().map { nextDf.columnToJs(it, rowsLimit, configuration) }
257+  val  preparedColumns =  nextDf.columns().map {
258+  nextDf.columnToJs(it.addPath(), rowsLimit, configuration)
259+  }
230260 val  js =  tableJs(preparedColumns, nextId, rootId, nextDf.nrow)
231261 scripts.add(js)
232262 }
0 commit comments