From cac1b7f08b692174ba99e18c979e5ee79a5e8fa5 Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Fri, 19 Sep 2025 14:15:12 +0200 Subject: [PATCH 1/5] (Junie) add formatHeader --- .../jetbrains/kotlinx/dataframe/api/format.kt | 13 +- .../kotlinx/dataframe/api/formatHeader.kt | 119 ++++++++++++++++++ .../kotlinx/dataframe/impl/api/format.kt | 23 ++-- .../jetbrains/kotlinx/dataframe/io/html.kt | 30 ++++- .../kotlinx/dataframe/api/formatHeader.kt | 78 ++++++++++++ 5 files changed, 248 insertions(+), 15 deletions(-) create mode 100644 core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/formatHeader.kt create mode 100644 core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/formatHeader.kt diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/format.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/format.kt index 2d7e6c6110..d3c084f261 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/format.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/format.kt @@ -325,7 +325,7 @@ public fun DataFrame.format(vararg columns: KProperty): FormatClaus * If unspecified, all columns will be formatted. */ public fun FormattedFrame.format(columns: ColumnsSelector): FormatClause = - FormatClause(df, columns, formatter) + FormatClause(df, columns, formatter, oldHeaderFormatter = headerFormatter) /** * @include [CommonFormatDocs] @@ -390,7 +390,7 @@ public fun FormattedFrame.format(): FormatClause = FormatClause( * Check out the full [Grammar][FormatDocs.Grammar]. */ public fun FormatClause.where(filter: RowValueFilter): FormatClause = - FormatClause(filter = this.filter and filter, df = df, columns = columns, oldFormatter = oldFormatter) + FormatClause(filter = this.filter and filter, df = df, columns = columns, oldFormatter = oldFormatter, oldHeaderFormatter = oldHeaderFormatter) /** * Only format the selected columns at given row indices. @@ -780,7 +780,11 @@ public typealias CellFormatter = FormattingDsl.(cell: C) -> CellAttributes? * * You can apply further formatting to this [FormattedFrame] by calling [format()][FormattedFrame.format] once again. */ -public class FormattedFrame(internal val df: DataFrame, internal val formatter: RowColFormatter? = null) { +public class FormattedFrame( + internal val df: DataFrame, + internal val formatter: RowColFormatter? = null, + internal val headerFormatter: HeaderColFormatter<*>? = null, +) { /** * Returns a [DataFrameHtmlData] without additional definitions. @@ -826,7 +830,7 @@ public class FormattedFrame(internal val df: DataFrame, internal val forma /** Applies this formatter to the given [configuration] and returns a new instance. */ @Suppress("UNCHECKED_CAST") public fun getDisplayConfiguration(configuration: DisplayConfiguration): DisplayConfiguration = - configuration.copy(cellFormatter = formatter as RowColFormatter<*, *>?) + configuration.copy(cellFormatter = formatter as RowColFormatter<*, *>?, headerFormatter = headerFormatter as HeaderColFormatter<*>?) } /** @@ -858,6 +862,7 @@ public class FormatClause( internal val columns: ColumnsSelector = { all().cast() }, internal val oldFormatter: RowColFormatter? = null, internal val filter: RowValueFilter = { true }, + internal val oldHeaderFormatter: HeaderColFormatter<*>? = null, ) { override fun toString(): String = "FormatClause(df=$df, columns=$columns, oldFormatter=$oldFormatter, filter=$filter)" diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/formatHeader.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/formatHeader.kt new file mode 100644 index 0000000000..314f21bbf8 --- /dev/null +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/formatHeader.kt @@ -0,0 +1,119 @@ +package org.jetbrains.kotlinx.dataframe.api + +import org.jetbrains.kotlinx.dataframe.ColumnsSelector +import org.jetbrains.kotlinx.dataframe.DataFrame +import org.jetbrains.kotlinx.dataframe.annotations.AccessApiOverload +import org.jetbrains.kotlinx.dataframe.columns.ColumnReference +import org.jetbrains.kotlinx.dataframe.columns.ColumnWithPath +import org.jetbrains.kotlinx.dataframe.columns.toColumnSet +import org.jetbrains.kotlinx.dataframe.impl.getColumnPaths +import org.jetbrains.kotlinx.dataframe.columns.UnresolvedColumnsPolicy +import kotlin.reflect.KProperty +import org.jetbrains.kotlinx.dataframe.util.DEPRECATED_ACCESS_API + +/** + * Formats the headers (column names) of the selected columns similarly to how [format] formats cell values. + * + * This does not immediately produce a [FormattedFrame]; instead it returns a [HeaderFormatClause] which must be + * finalized using [HeaderFormatClause.with]. + * + * Header formatting is additive and supports nested column groups: styles specified for a parent [ColumnGroup] + * are inherited by its child columns unless overridden for the child. + * + * Examples: + * ```kt + * // center a single column header + * df.formatHeader { age }.with { attr("text-align", "center") } + * + * // style a whole group header and override one child + * df.formatHeader { name }.with { bold } + * .formatHeader { name.firstName }.with { textColor(green) } + * .toStandaloneHtml() + * ``` + */ +public typealias HeaderColFormatter = FormattingDsl.(col: ColumnWithPath) -> CellAttributes? + +/** + * Intermediate clause for header formatting, analogous to [FormatClause] but without rows. + * + * Use [with] to specify how to format the selected column headers, producing a [FormattedFrame]. + */ +public class HeaderFormatClause( + internal val df: DataFrame, + internal val columns: ColumnsSelector = { all().cast() }, + internal val oldHeaderFormatter: HeaderColFormatter? = null, + internal val oldCellFormatter: RowColFormatter? = null, +) { + override fun toString(): String = + "HeaderFormatClause(df=$df, columns=$columns, oldHeaderFormatter=$oldHeaderFormatter, oldCellFormatter=$oldCellFormatter)" +} + +// region DataFrame.formatHeader + +/** + * Selects [columns] whose headers should be formatted; finalize with [HeaderFormatClause.with]. + */ +public fun DataFrame.formatHeader(columns: ColumnsSelector): HeaderFormatClause = + HeaderFormatClause(this, columns) + +/** Selects columns by [columns] names for header formatting. */ +public fun DataFrame.formatHeader(vararg columns: String): HeaderFormatClause = + formatHeader { columns.toColumnSet() } + +/** Selects all columns for header formatting. */ +public fun DataFrame.formatHeader(): HeaderFormatClause = HeaderFormatClause(this) + +@Deprecated(DEPRECATED_ACCESS_API) +@AccessApiOverload +public fun DataFrame.formatHeader(vararg columns: ColumnReference): HeaderFormatClause = + formatHeader { columns.toColumnSet() } + +@Deprecated(DEPRECATED_ACCESS_API) +@AccessApiOverload +public fun DataFrame.formatHeader(vararg columns: KProperty): HeaderFormatClause = + formatHeader { columns.toColumnSet() } + +// endregion + +// region FormattedFrame.formatHeader + +public fun FormattedFrame.formatHeader(columns: ColumnsSelector): HeaderFormatClause = + HeaderFormatClause(df = df, columns = columns, oldHeaderFormatter = headerFormatter as HeaderColFormatter?, oldCellFormatter = formatter) + +public fun FormattedFrame.formatHeader(vararg columns: String): HeaderFormatClause = + formatHeader { columns.toColumnSet() } + +public fun FormattedFrame.formatHeader(): HeaderFormatClause = + HeaderFormatClause(df = df, oldHeaderFormatter = headerFormatter as HeaderColFormatter?, oldCellFormatter = formatter) + +// endregion + +// region terminal operations + +@Suppress("UNCHECKED_CAST") +public fun HeaderFormatClause.with(formatter: HeaderColFormatter): FormattedFrame { + val paths = df.getColumnPaths(UnresolvedColumnsPolicy.Skip, columns).toSet() + val oldHeader = oldHeaderFormatter + val composedHeader: HeaderColFormatter = { col -> + val parentCols = col.path.indices + .map { i -> col.path.take(i + 1) } + .dropLast(0) // include self and parents handled below + // Merge attributes from parents that are selected + val parentAttributes = parentCols + .dropLast(1) + .map { path -> ColumnWithPath(df[path], path) } + .map { parentCol -> if (parentCol.path in paths) (oldHeader?.invoke(FormattingDsl, parentCol as ColumnWithPath)) else null } + .reduceOrNull(CellAttributes?::and) + val selfAttr = if (col.path in paths) { + val oldAttr = oldHeader?.invoke(FormattingDsl, col as ColumnWithPath) + oldAttr and formatter(FormattingDsl, col as ColumnWithPath) + } else { + oldHeader?.invoke(FormattingDsl, col as ColumnWithPath) + } + parentAttributes and selfAttr + } + @Suppress("UNCHECKED_CAST") + return FormattedFrame(df, oldCellFormatter, composedHeader) +} + +// endregion diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/format.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/format.kt index 8b78bdfe5e..79a1d4cb34 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/format.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/format.kt @@ -56,15 +56,18 @@ internal inline fun FormatClause.formatImpl( val clause = this val columns = clause.df.getColumnPaths(UnresolvedColumnsPolicy.Skip, clause.columns).toSet() - return FormattedFrame(clause.df) { row, col -> - val oldAttributes = clause.oldFormatter?.invoke(FormattingDsl, row, col.cast()) - if (col.path in columns) { - val value = col[row] as C - if (clause.filter(row, value)) { - return@FormattedFrame oldAttributes and formatter(FormattingDsl, row.cast(), col.cast()) + return FormattedFrame( + df = clause.df, + formatter = { row, col -> + val oldAttributes = clause.oldFormatter?.invoke(FormattingDsl, row, col.cast()) + if (col.path in columns) { + val value = col[row] as C + if (clause.filter(row, value)) { + return@FormattedFrame oldAttributes and formatter(FormattingDsl, row.cast(), col.cast()) + } } - } - - oldAttributes - } + oldAttributes + }, + headerFormatter = clause.oldHeaderFormatter + ) } diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/html.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/html.kt index dec55ef272..2a4874243c 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/html.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/html.kt @@ -9,6 +9,7 @@ import org.jetbrains.kotlinx.dataframe.api.CellAttributes import org.jetbrains.kotlinx.dataframe.api.FormattedFrame import org.jetbrains.kotlinx.dataframe.api.FormattingDsl import org.jetbrains.kotlinx.dataframe.api.RowColFormatter +import org.jetbrains.kotlinx.dataframe.api.HeaderColFormatter import org.jetbrains.kotlinx.dataframe.api.and import org.jetbrains.kotlinx.dataframe.api.asColumnGroup import org.jetbrains.kotlinx.dataframe.api.asNumbers @@ -68,6 +69,7 @@ internal data class ColumnDataForJs( val nested: List, val rightAlign: Boolean, val values: List, + val headerStyle: String?, ) internal val formatter = DataFrameFormatter( @@ -98,7 +100,8 @@ internal fun getResourceText(resource: String, vararg replacement: Pair${column.name()}" + val styleAttr = if (headerStyle != null) " style=\"$headerStyle\"" else "" + return "${column.name()}" } internal fun tableJs( @@ -234,6 +237,27 @@ internal fun AnyFrame.toHtmlData( HtmlContent(html, style) } } + val headerStyle = run { + val hf = configuration.headerFormatter + if (hf == null) null else { + // collect attributes from parents + val parentCols = col.path.indices + .map { i -> col.path.take(i + 1) } + .dropLast(1) + .map { ColumnWithPath(this@toHtmlData[it], it) } + val parentAttributes = parentCols + .map { hf(FormattingDsl, it) } + .reduceOrNull(CellAttributes?::and) + val selfAttributes = hf(FormattingDsl, col) + val attrs = parentAttributes and selfAttributes + attrs + ?.attributes() + ?.ifEmpty { null } + ?.toMap() + ?.entries + ?.joinToString(";") { "${it.key}:${it.value}" } + } + } val nested = if (col is ColumnGroup<*>) { col.columns().map { col.columnToJs(it.addParentPath(col.path), rowsLimit, configuration) @@ -246,6 +270,7 @@ internal fun AnyFrame.toHtmlData( nested = nested, rightAlign = col.isSubtypeOf(), values = contents, + headerStyle = headerStyle, ) } @@ -826,12 +851,15 @@ public class DataFrameHtmlData( * @param cellContentLimit -1 to disable content trimming * @param enableFallbackStaticTables true to add additional pure HTML table that will be visible only if JS is disabled; * For example hosting *.ipynb files with outputs on GitHub + * @param cellFormatter Optional cell formatter applied to data cells during HTML rendering. + * @param headerFormatter Optional header formatter applied to column headers; supports inheritance for nested column groups. */ public data class DisplayConfiguration( var rowsLimit: Int? = 20, var nestedRowsLimit: Int? = 5, var cellContentLimit: Int = 40, var cellFormatter: RowColFormatter<*, *>? = null, + var headerFormatter: HeaderColFormatter<*>? = null, var decimalFormat: RendererDecimalFormat = RendererDecimalFormat.DEFAULT, var isolatedOutputs: Boolean = flagFromEnv("LETS_PLOT_HTML_ISOLATED_FRAME"), internal val localTesting: Boolean = flagFromEnv("KOTLIN_DATAFRAME_LOCAL_TESTING"), diff --git a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/formatHeader.kt b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/formatHeader.kt new file mode 100644 index 0000000000..76cc80a81a --- /dev/null +++ b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/formatHeader.kt @@ -0,0 +1,78 @@ +package org.jetbrains.kotlinx.dataframe.api + +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import org.jetbrains.kotlinx.dataframe.api.FormattingDsl.blue +import org.jetbrains.kotlinx.dataframe.api.FormattingDsl.green +import org.jetbrains.kotlinx.dataframe.samples.api.TestBase +import org.jetbrains.kotlinx.dataframe.samples.api.age +import org.jetbrains.kotlinx.dataframe.samples.api.name +import org.jetbrains.kotlinx.dataframe.samples.api.firstName +import org.junit.Test + +class FormatHeaderTests : TestBase() { + + @Test + fun `formatHeader on single column adds inline style to header`() { + val formatted = df.formatHeader { age }.with { attr("border", "3px solid green") } + val html = formatted.toHtml().toString() + + // header style is rendered inline inside + // count exact style occurrences to avoid interference with CSS + val occurrences = html.split("border:3px solid green").size - 1 + occurrences shouldBe 1 + + } + + @Test + fun `formatHeader by names overload`() { + val formatted = df.formatHeader("age").with { attr("text-align", "center") } + val html = formatted.toHtml().toString() + val occurrences = html.split("text-align:center").size - 1 + occurrences shouldBe 1 + } + + @Test + fun `header style inherited from group to children`() { + // Apply style to the group header only + val formatted = df.formatHeader { name }.with { attr("border", "1px solid red") } + val html = formatted.toHtml().toString() + + // We expect the style on the group header itself and each direct child header + // In the default TestBase dataset, name group has two children + val occurrences = html.split("border:1px solid red").size - 1 + occurrences shouldBe 3 + } + + @Test + fun `child header overrides parent group header style`() { + val formatted = df + .formatHeader { name }.with { attr("border", "1px solid red") } + .formatHeader { name.firstName }.with { attr("border", "2px dashed green") } + val html = formatted.toHtml().toString() + + // Parent style applies to group and lastName, but firstName gets its own style in addition to or replacing + // We check for both occurrences + val parentOcc = html.split("border:1px solid red").size - 1 + val childOcc = html.split("border:2px dashed green").size - 1 + + parentOcc shouldBe 2 // group + lastName + childOcc shouldBe 1 // firstName only + } + + @Test + fun `format and formatHeader can be chained and both persist`() { + val formatted = df + .format { age }.with { background(blue) } + .formatHeader { age }.with { attr("border", "3px solid green") } + + val html = formatted.toHtml().toString() + + // body cell style + (html.split("background-color:#0000ff").size - 1) shouldBe 7 + // header style + (html.split("border:3px solid green").size - 1) shouldBe 1 + + formatted::class.simpleName shouldNotBe null + } +} From 30059fb616eab45fc7423097143f583589a79dc8 Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Sat, 20 Sep 2025 12:07:12 +0200 Subject: [PATCH 2/5] formatting --- .../jetbrains/kotlinx/dataframe/api/format.kt | 13 +++++++-- .../kotlinx/dataframe/api/formatHeader.kt | 27 +++++++++++++++---- .../kotlinx/dataframe/impl/api/format.kt | 2 +- .../jetbrains/kotlinx/dataframe/io/html.kt | 6 +++-- .../kotlinx/dataframe/api/formatHeader.kt | 4 +-- .../kotlinx/dataframe/samples/io/Parquet.kt | 9 +++---- 6 files changed, 43 insertions(+), 18 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/format.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/format.kt index d3c084f261..80bd659083 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/format.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/format.kt @@ -390,7 +390,13 @@ public fun FormattedFrame.format(): FormatClause = FormatClause( * Check out the full [Grammar][FormatDocs.Grammar]. */ public fun FormatClause.where(filter: RowValueFilter): FormatClause = - FormatClause(filter = this.filter and filter, df = df, columns = columns, oldFormatter = oldFormatter, oldHeaderFormatter = oldHeaderFormatter) + FormatClause( + filter = this.filter and filter, + df = df, + columns = columns, + oldFormatter = oldFormatter, + oldHeaderFormatter = oldHeaderFormatter, + ) /** * Only format the selected columns at given row indices. @@ -830,7 +836,10 @@ public class FormattedFrame( /** Applies this formatter to the given [configuration] and returns a new instance. */ @Suppress("UNCHECKED_CAST") public fun getDisplayConfiguration(configuration: DisplayConfiguration): DisplayConfiguration = - configuration.copy(cellFormatter = formatter as RowColFormatter<*, *>?, headerFormatter = headerFormatter as HeaderColFormatter<*>?) + configuration.copy( + cellFormatter = formatter as RowColFormatter<*, *>?, + headerFormatter = headerFormatter as HeaderColFormatter<*>?, + ) } /** diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/formatHeader.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/formatHeader.kt index 314f21bbf8..5f7a5cf4e1 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/formatHeader.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/formatHeader.kt @@ -5,11 +5,11 @@ import org.jetbrains.kotlinx.dataframe.DataFrame import org.jetbrains.kotlinx.dataframe.annotations.AccessApiOverload import org.jetbrains.kotlinx.dataframe.columns.ColumnReference import org.jetbrains.kotlinx.dataframe.columns.ColumnWithPath +import org.jetbrains.kotlinx.dataframe.columns.UnresolvedColumnsPolicy import org.jetbrains.kotlinx.dataframe.columns.toColumnSet import org.jetbrains.kotlinx.dataframe.impl.getColumnPaths -import org.jetbrains.kotlinx.dataframe.columns.UnresolvedColumnsPolicy -import kotlin.reflect.KProperty import org.jetbrains.kotlinx.dataframe.util.DEPRECATED_ACCESS_API +import kotlin.reflect.KProperty /** * Formats the headers (column names) of the selected columns similarly to how [format] formats cell values. @@ -78,13 +78,22 @@ public fun DataFrame.formatHeader(vararg columns: KProperty): Heade // region FormattedFrame.formatHeader public fun FormattedFrame.formatHeader(columns: ColumnsSelector): HeaderFormatClause = - HeaderFormatClause(df = df, columns = columns, oldHeaderFormatter = headerFormatter as HeaderColFormatter?, oldCellFormatter = formatter) + HeaderFormatClause( + df = df, + columns = columns, + oldHeaderFormatter = headerFormatter as HeaderColFormatter?, + oldCellFormatter = formatter, + ) public fun FormattedFrame.formatHeader(vararg columns: String): HeaderFormatClause = formatHeader { columns.toColumnSet() } public fun FormattedFrame.formatHeader(): HeaderFormatClause = - HeaderFormatClause(df = df, oldHeaderFormatter = headerFormatter as HeaderColFormatter?, oldCellFormatter = formatter) + HeaderFormatClause( + df = df, + oldHeaderFormatter = headerFormatter as HeaderColFormatter?, + oldCellFormatter = formatter, + ) // endregion @@ -102,7 +111,15 @@ public fun HeaderFormatClause.with(formatter: HeaderColFormatter val parentAttributes = parentCols .dropLast(1) .map { path -> ColumnWithPath(df[path], path) } - .map { parentCol -> if (parentCol.path in paths) (oldHeader?.invoke(FormattingDsl, parentCol as ColumnWithPath)) else null } + .map { parentCol -> + if (parentCol.path in + paths + ) { + (oldHeader?.invoke(FormattingDsl, parentCol as ColumnWithPath)) + } else { + null + } + } .reduceOrNull(CellAttributes?::and) val selfAttr = if (col.path in paths) { val oldAttr = oldHeader?.invoke(FormattingDsl, col as ColumnWithPath) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/format.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/format.kt index 79a1d4cb34..3f1aec332a 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/format.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/impl/api/format.kt @@ -68,6 +68,6 @@ internal inline fun FormatClause.formatImpl( } oldAttributes }, - headerFormatter = clause.oldHeaderFormatter + headerFormatter = clause.oldHeaderFormatter, ) } diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/html.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/html.kt index 2a4874243c..ec4d632ce3 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/html.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/html.kt @@ -8,8 +8,8 @@ import org.jetbrains.kotlinx.dataframe.DataFrame import org.jetbrains.kotlinx.dataframe.api.CellAttributes import org.jetbrains.kotlinx.dataframe.api.FormattedFrame import org.jetbrains.kotlinx.dataframe.api.FormattingDsl -import org.jetbrains.kotlinx.dataframe.api.RowColFormatter import org.jetbrains.kotlinx.dataframe.api.HeaderColFormatter +import org.jetbrains.kotlinx.dataframe.api.RowColFormatter import org.jetbrains.kotlinx.dataframe.api.and import org.jetbrains.kotlinx.dataframe.api.asColumnGroup import org.jetbrains.kotlinx.dataframe.api.asNumbers @@ -239,7 +239,9 @@ internal fun AnyFrame.toHtmlData( } val headerStyle = run { val hf = configuration.headerFormatter - if (hf == null) null else { + if (hf == null) { + null + } else { // collect attributes from parents val parentCols = col.path.indices .map { i -> col.path.take(i + 1) } diff --git a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/formatHeader.kt b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/formatHeader.kt index 76cc80a81a..295c5dc1eb 100644 --- a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/formatHeader.kt +++ b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/api/formatHeader.kt @@ -3,11 +3,10 @@ package org.jetbrains.kotlinx.dataframe.api import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import org.jetbrains.kotlinx.dataframe.api.FormattingDsl.blue -import org.jetbrains.kotlinx.dataframe.api.FormattingDsl.green import org.jetbrains.kotlinx.dataframe.samples.api.TestBase import org.jetbrains.kotlinx.dataframe.samples.api.age -import org.jetbrains.kotlinx.dataframe.samples.api.name import org.jetbrains.kotlinx.dataframe.samples.api.firstName +import org.jetbrains.kotlinx.dataframe.samples.api.name import org.junit.Test class FormatHeaderTests : TestBase() { @@ -21,7 +20,6 @@ class FormatHeaderTests : TestBase() { // count exact style occurrences to avoid interference with CSS val occurrences = html.split("border:3px solid green").size - 1 occurrences shouldBe 1 - } @Test diff --git a/samples/src/test/kotlin/org/jetbrains/kotlinx/dataframe/samples/io/Parquet.kt b/samples/src/test/kotlin/org/jetbrains/kotlinx/dataframe/samples/io/Parquet.kt index c423cbdfc0..3d235e296e 100644 --- a/samples/src/test/kotlin/org/jetbrains/kotlinx/dataframe/samples/io/Parquet.kt +++ b/samples/src/test/kotlin/org/jetbrains/kotlinx/dataframe/samples/io/Parquet.kt @@ -1,14 +1,13 @@ package org.jetbrains.kotlinx.dataframe.samples.io import io.kotest.matchers.shouldBe -import java.io.File -import java.nio.file.Path -import java.nio.file.Paths import org.jetbrains.kotlinx.dataframe.DataFrame import org.jetbrains.kotlinx.dataframe.api.NullabilityOptions -import org.junit.Test import org.jetbrains.kotlinx.dataframe.io.readParquet import org.jetbrains.kotlinx.dataframe.testParquet +import org.junit.Test +import java.io.File +import java.nio.file.Paths class Parquet { @Test @@ -56,7 +55,7 @@ class Parquet { val df = DataFrame.readParquet( file, nullability = NullabilityOptions.Infer, - batchSize = 64L * 1024 + batchSize = 64L * 1024, ) // SampleEnd df.rowsCount() shouldBe 300 From 92eae1ddc1d57d0adb127deb918ada37616e6b31 Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Sat, 20 Sep 2025 12:29:38 +0200 Subject: [PATCH 3/5] api dump --- core/api/core.api | 43 ++++++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/core/api/core.api b/core/api/core.api index 283e186f54..4674be4b8e 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -2232,11 +2232,23 @@ public final class org/jetbrains/kotlinx/dataframe/api/ForEachKt { } public final class org/jetbrains/kotlinx/dataframe/api/FormatClause { - public fun (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;)V - public synthetic fun (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)V + public synthetic fun (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun toString ()Ljava/lang/String; } +public final class org/jetbrains/kotlinx/dataframe/api/FormatHeaderKt { + public static final fun formatHeader (Lorg/jetbrains/kotlinx/dataframe/DataFrame;)Lorg/jetbrains/kotlinx/dataframe/api/HeaderFormatClause; + public static final fun formatHeader (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Lkotlin/jvm/functions/Function2;)Lorg/jetbrains/kotlinx/dataframe/api/HeaderFormatClause; + public static final fun formatHeader (Lorg/jetbrains/kotlinx/dataframe/DataFrame;[Ljava/lang/String;)Lorg/jetbrains/kotlinx/dataframe/api/HeaderFormatClause; + public static final fun formatHeader (Lorg/jetbrains/kotlinx/dataframe/DataFrame;[Lkotlin/reflect/KProperty;)Lorg/jetbrains/kotlinx/dataframe/api/HeaderFormatClause; + public static final fun formatHeader (Lorg/jetbrains/kotlinx/dataframe/DataFrame;[Lorg/jetbrains/kotlinx/dataframe/columns/ColumnReference;)Lorg/jetbrains/kotlinx/dataframe/api/HeaderFormatClause; + public static final fun formatHeader (Lorg/jetbrains/kotlinx/dataframe/api/FormattedFrame;)Lorg/jetbrains/kotlinx/dataframe/api/HeaderFormatClause; + public static final fun formatHeader (Lorg/jetbrains/kotlinx/dataframe/api/FormattedFrame;Lkotlin/jvm/functions/Function2;)Lorg/jetbrains/kotlinx/dataframe/api/HeaderFormatClause; + public static final fun formatHeader (Lorg/jetbrains/kotlinx/dataframe/api/FormattedFrame;[Ljava/lang/String;)Lorg/jetbrains/kotlinx/dataframe/api/HeaderFormatClause; + public static final fun with (Lorg/jetbrains/kotlinx/dataframe/api/HeaderFormatClause;Lkotlin/jvm/functions/Function2;)Lorg/jetbrains/kotlinx/dataframe/api/FormattedFrame; +} + public final class org/jetbrains/kotlinx/dataframe/api/FormatKt { public static final fun and (Lorg/jetbrains/kotlinx/dataframe/api/CellAttributes;Lorg/jetbrains/kotlinx/dataframe/api/CellAttributes;)Lorg/jetbrains/kotlinx/dataframe/api/CellAttributes; public static final fun at (Lorg/jetbrains/kotlinx/dataframe/api/FormatClause;Ljava/util/Collection;)Lorg/jetbrains/kotlinx/dataframe/api/FormatClause; @@ -2259,8 +2271,8 @@ public final class org/jetbrains/kotlinx/dataframe/api/FormatKt { } public final class org/jetbrains/kotlinx/dataframe/api/FormattedFrame { - public fun (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Lkotlin/jvm/functions/Function3;)V - public synthetic fun (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;)V + public synthetic fun (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getDisplayConfiguration (Lorg/jetbrains/kotlinx/dataframe/io/DisplayConfiguration;)Lorg/jetbrains/kotlinx/dataframe/io/DisplayConfiguration; public final fun toHtml (Lorg/jetbrains/kotlinx/dataframe/io/DisplayConfiguration;)Lorg/jetbrains/kotlinx/dataframe/io/DataFrameHtmlData; public static synthetic fun toHtml$default (Lorg/jetbrains/kotlinx/dataframe/api/FormattedFrame;Lorg/jetbrains/kotlinx/dataframe/io/DisplayConfiguration;ILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/io/DataFrameHtmlData; @@ -2545,6 +2557,12 @@ public final class org/jetbrains/kotlinx/dataframe/api/HeadKt { public static synthetic fun head$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame;IILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; } +public final class org/jetbrains/kotlinx/dataframe/api/HeaderFormatClause { + public fun (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;)V + public synthetic fun (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun toString ()Ljava/lang/String; +} + public final class org/jetbrains/kotlinx/dataframe/api/ImplodeKt { public static final fun implode (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Z)Lorg/jetbrains/kotlinx/dataframe/DataRow; public static final fun implode (Lorg/jetbrains/kotlinx/dataframe/DataFrame;ZLkotlin/jvm/functions/Function2;)Lorg/jetbrains/kotlinx/dataframe/DataFrame; @@ -6194,25 +6212,27 @@ public final class org/jetbrains/kotlinx/dataframe/io/DataFrameHtmlData$Companio public final class org/jetbrains/kotlinx/dataframe/io/DisplayConfiguration { public static final field Companion Lorg/jetbrains/kotlinx/dataframe/io/DisplayConfiguration$Companion; - public synthetic fun (Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/functions/Function3;Ljava/lang/String;ZZZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V - public synthetic fun (Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/functions/Function3;Ljava/lang/String;ZZZZZLkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Ljava/lang/String;ZZZZZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Ljava/lang/String;ZZZZZLkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/Integer; public final fun component10 ()Z + public final fun component11 ()Z public final fun component2 ()Ljava/lang/Integer; public final fun component3 ()I public final fun component4 ()Lkotlin/jvm/functions/Function3; - public final fun component5-3Sl7FsM ()Ljava/lang/String; - public final fun component6 ()Z - public final fun component8 ()Z + public final fun component5 ()Lkotlin/jvm/functions/Function2; + public final fun component6-3Sl7FsM ()Ljava/lang/String; + public final fun component7 ()Z public final fun component9 ()Z - public final fun copy-rqXL5tM (Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/functions/Function3;Ljava/lang/String;ZZZZZ)Lorg/jetbrains/kotlinx/dataframe/io/DisplayConfiguration; - public static synthetic fun copy-rqXL5tM$default (Lorg/jetbrains/kotlinx/dataframe/io/DisplayConfiguration;Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/functions/Function3;Ljava/lang/String;ZZZZZILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/io/DisplayConfiguration; + public final fun copy-bMNacXk (Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Ljava/lang/String;ZZZZZ)Lorg/jetbrains/kotlinx/dataframe/io/DisplayConfiguration; + public static synthetic fun copy-bMNacXk$default (Lorg/jetbrains/kotlinx/dataframe/io/DisplayConfiguration;Ljava/lang/Integer;Ljava/lang/Integer;ILkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function2;Ljava/lang/String;ZZZZZILjava/lang/Object;)Lorg/jetbrains/kotlinx/dataframe/io/DisplayConfiguration; public fun equals (Ljava/lang/Object;)Z public final fun getCellContentLimit ()I public final fun getCellFormatter ()Lkotlin/jvm/functions/Function3; public final fun getDecimalFormat-3Sl7FsM ()Ljava/lang/String; public final fun getDownsizeBufferedImage ()Z public final fun getEnableFallbackStaticTables ()Z + public final fun getHeaderFormatter ()Lkotlin/jvm/functions/Function2; public final fun getIsolatedOutputs ()Z public final fun getNestedRowsLimit ()Ljava/lang/Integer; public final fun getRowsLimit ()Ljava/lang/Integer; @@ -6224,6 +6244,7 @@ public final class org/jetbrains/kotlinx/dataframe/io/DisplayConfiguration { public final fun setDecimalFormat-h5o3lmc (Ljava/lang/String;)V public final fun setDownsizeBufferedImage (Z)V public final fun setEnableFallbackStaticTables (Z)V + public final fun setHeaderFormatter (Lkotlin/jvm/functions/Function2;)V public final fun setIsolatedOutputs (Z)V public final fun setNestedRowsLimit (Ljava/lang/Integer;)V public final fun setRowsLimit (Ljava/lang/Integer;)V From 5b9e1a912989c08e8f3dbe4c78dfe185187bcd77 Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Sat, 20 Sep 2025 15:30:50 +0200 Subject: [PATCH 4/5] disable :samples module --- build.gradle.kts | 2 +- settings.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2ab279f51e..fb7d1aae31 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -158,7 +158,7 @@ val modulesUsingJava11 = with(projects) { dataframeGeoJupyter, examples.ideaExamples.titanic, examples.ideaExamples.unsupportedDataSources, - samples, + //samples, plugins.dataframeGradlePlugin, ) }.map { it.path } diff --git a/settings.gradle.kts b/settings.gradle.kts index e227071c9f..26e42f79e6 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -12,7 +12,7 @@ include("plugins:symbol-processor") include("plugins:expressions-converter") include("plugins:kotlin-dataframe") include("plugins:public-api-modifier") -include("samples") +//include("samples") include("dataframe-json") include("dataframe-arrow") include("dataframe-openapi") From 1f08fbc148dca6ec61210f74b0a307ee864e8f0f Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Sat, 20 Sep 2025 15:33:40 +0200 Subject: [PATCH 5/5] disable :samples module --- build.gradle.kts | 2 +- settings.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index fb7d1aae31..bd471e8d12 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -158,7 +158,7 @@ val modulesUsingJava11 = with(projects) { dataframeGeoJupyter, examples.ideaExamples.titanic, examples.ideaExamples.unsupportedDataSources, - //samples, + // samples, plugins.dataframeGradlePlugin, ) }.map { it.path } diff --git a/settings.gradle.kts b/settings.gradle.kts index 26e42f79e6..89386beb15 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -12,7 +12,7 @@ include("plugins:symbol-processor") include("plugins:expressions-converter") include("plugins:kotlin-dataframe") include("plugins:public-api-modifier") -//include("samples") +// include("samples") include("dataframe-json") include("dataframe-arrow") include("dataframe-openapi")