Skip to content

Commit 45315d5

Browse files
committed
test fix
1 parent 3d93e5b commit 45315d5

File tree

4 files changed

+86
-93
lines changed
  • core
    • generated-sources/src
      • main/kotlin/org/jetbrains/kotlinx/dataframe/io
      • test/kotlin/org/jetbrains/kotlinx/dataframe/rendering
    • src
      • main/kotlin/org/jetbrains/kotlinx/dataframe/io
      • test/kotlin/org/jetbrains/kotlinx/dataframe/rendering

4 files changed

+86
-93
lines changed

core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/html.kt

Lines changed: 39 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,10 @@ import org.jetbrains.kotlinx.dataframe.api.isEmpty
1515
import org.jetbrains.kotlinx.dataframe.api.isNumber
1616
import org.jetbrains.kotlinx.dataframe.api.isSubtypeOf
1717
import org.jetbrains.kotlinx.dataframe.api.rows
18-
import org.jetbrains.kotlinx.dataframe.api.sumOf
19-
import org.jetbrains.kotlinx.dataframe.api.take
2018
import org.jetbrains.kotlinx.dataframe.columns.BaseColumn
2119
import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup
2220
import org.jetbrains.kotlinx.dataframe.columns.ColumnWithPath
2321
import org.jetbrains.kotlinx.dataframe.columns.FrameColumn
24-
import org.jetbrains.kotlinx.dataframe.columns.depth
2522
import org.jetbrains.kotlinx.dataframe.impl.DataFrameSize
2623
import org.jetbrains.kotlinx.dataframe.impl.columns.addPath
2724
import org.jetbrains.kotlinx.dataframe.impl.renderType
@@ -42,6 +39,7 @@ import java.net.URL
4239
import java.nio.file.Path
4340
import java.util.*
4441
import kotlin.io.path.writeText
42+
import kotlin.math.ceil
4543

4644
internal val tooltipLimit = 1000
4745

@@ -163,10 +161,10 @@ internal fun AnyFrame.toHtmlData(
163161
}
164162
}
165163
return ColumnDataForJs(
166-
col,
167-
if (col is ColumnGroup<*>) col.columns().map { col.columnToJs(it, rowsLimit) } else emptyList(),
168-
col.isSubtypeOf<Number?>(),
169-
contents
164+
column = col,
165+
nested = if (col is ColumnGroup<*>) col.columns().map { col.columnToJs(it, rowsLimit) } else emptyList(),
166+
rightAlign = col.isSubtypeOf<Number?>(),
167+
values = contents,
170168
)
171169
}
172170

@@ -181,12 +179,19 @@ internal fun AnyFrame.toHtmlData(
181179
}
182180
val body = getResourceText("/table.html", "ID" to rootId)
183181
val script = scripts.joinToString("\n") + "\n" + getResourceText("/renderTable.js", "___ID___" to rootId)
184-
return DataFrameHtmlData("", body, script)
182+
return DataFrameHtmlData(style = "", body = body, script = script)
185183
}
186184

187185
/**
188186
* Renders [this] [DataFrame] as static HTML (meaning no JS is used).
189187
* CSS rendering is enabled by default but can be turned off using [includeCss]
188+
*
189+
* @param configuration optional configuration for rendering
190+
* @param cellRenderer optional cell renderer for rendering
191+
* @param includeCss whether to include CSS in the output. This is `true` by default but it can be set
192+
* to `false` to emulate what happens in environments where custom CSS is not allowed.
193+
* @param openNestedDfs whether to open nested dataframes. This is `false` by default byt ut can be set
194+
* to `true` to emulate what happens in environments where `<details>` tags are not supported.
190195
*/
191196
public fun AnyFrame.toStaticHtml(
192197
configuration: DisplayConfiguration = DisplayConfiguration.DEFAULT,
@@ -206,6 +211,7 @@ public fun AnyFrame.toStaticHtml(
206211

207212
// Limit for number of rows in dataframes inside frame columns
208213
val nestedRowsLimit = configuration.nestedRowsLimit
214+
val rowsLimit = configuration.rowsLimit
209215

210216
// Adds the given tag to the html, with the given attributes and contents
211217
fun StringBuilder.emitTag(tag: String, attributes: String = "", tagContents: StringBuilder.() -> Unit) {
@@ -239,34 +245,42 @@ public fun AnyFrame.toStaticHtml(
239245
}
240246

241247
// Adds a single cell to the html. DataRows from column groups already need to be split up into separate cells.
242-
fun StringBuilder.emitCell(cellValue: Any?, borders: Set<Border>): Unit =
248+
fun StringBuilder.emitCell(cellValue: Any?, row: AnyRow, col: ColumnWithPath<*>, borders: Set<Border>): Unit =
243249
emitTag("td", "${borders.toClass()} style=\"vertical-align:top\"") {
244-
when (cellValue) {
250+
when (col) {
245251
// uses the <details> and <summary> to create a collapsible cell for dataframes
246-
is AnyFrame ->
252+
is FrameColumn<*> -> {
253+
cellValue as AnyFrame
247254
emitTag("details", if (openNestedDfs) "open" else "") {
248255
emitTag("summary") {
249256
append("DataFrame [${cellValue.size}]")
250257
}
258+
259+
// nestedRowsLimit becomes the rowsLimit for nested DFs
260+
// while the new nested rows limit is halved, keeping at least 1
261+
val newRowsLimit = nestedRowsLimit
262+
val newNestedRowsLimit = nestedRowsLimit?.let { ceil(it / 2.0).toInt() }
263+
251264
// add the dataframe as a nested table limiting the number of rows if needed
252265
// CSS will not be included here, as it is already included in the main table
253-
append(
254-
cellValue.take(nestedRowsLimit ?: Int.MAX_VALUE)
255-
.toStaticHtml(
256-
configuration,
257-
cellRenderer,
258-
includeCss = false,
259-
openNestedDfs = openNestedDfs
260-
)
261-
.body
262-
)
266+
cellValue.toStaticHtml(
267+
configuration = configuration.copy(
268+
rowsLimit = newRowsLimit,
269+
nestedRowsLimit = newNestedRowsLimit,
270+
),
271+
cellRenderer = cellRenderer,
272+
includeCss = false,
273+
openNestedDfs = openNestedDfs,
274+
).let { append(it.body) }
275+
263276
val size = cellValue.rowsCount()
264-
if (size > (nestedRowsLimit ?: Int.MAX_VALUE)) {
277+
if (size > newRowsLimit ?: Int.MAX_VALUE) {
265278
emitTag("p") {
266-
append("... showing only top $nestedRowsLimit of $size rows")
279+
append("... showing only top $newRowsLimit of $size rows")
267280
}
268281
}
269282
}
283+
}
270284

271285
// Else use the default cell renderer
272286
else ->
@@ -283,13 +297,13 @@ public fun AnyFrame.toStaticHtml(
283297
border += Border.RIGHT
284298
}
285299
val cell = row[col.path()]
286-
emitCell(cell, border)
300+
emitCell(cell, row, col, border)
287301
}
288302
}
289303

290304
// Adds the body of the html. This body contains all the cols and rows of the dataframe.
291305
fun StringBuilder.emitBody() = emitTag("tbody") {
292-
val rowsCountToRender = minOf(rowsCount(), configuration.rowsLimit ?: Int.MAX_VALUE)
306+
val rowsCountToRender = minOf(rowsCount(), rowsLimit ?: Int.MAX_VALUE)
293307
for (rowIndex in 0..<rowsCountToRender) {
294308
emitRow(df[rowIndex])
295309
}
@@ -349,15 +363,6 @@ internal fun BaseColumn<*>.maxWidth(): Int =
349363
if (this is ColumnGroup<*>) columns().sumOf { it.maxWidth() }.coerceAtLeast(1)
350364
else 1
351365

352-
internal fun BaseColumn<*>.maxNumberOfRows(): Int =
353-
if (this is ColumnGroup<*>) {
354-
columns().maxOfOrNull { it.maxNumberOfRows() } ?: 0
355-
} else if (this is FrameColumn<*>) {
356-
values().sumOf { it.asColumnGroup("").maxNumberOfRows() }
357-
} else {
358-
this.size()
359-
}
360-
361366
/**
362367
* Given a [DataFrame], this function returns a depth-first "matrix" containing all columns
363368
* laid out in such a way that they can be used to render the header of a table. The

core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/rendering/RenderingTests.kt

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@ package org.jetbrains.kotlinx.dataframe.rendering
33
import io.kotest.matchers.shouldBe
44
import io.kotest.matchers.string.shouldContain
55
import io.kotest.matchers.string.shouldNotContain
6-
import org.jetbrains.kotlinx.dataframe.DataFrame
76
import org.jetbrains.kotlinx.dataframe.api.add
87
import org.jetbrains.kotlinx.dataframe.api.asColumnGroup
98
import org.jetbrains.kotlinx.dataframe.api.columnOf
109
import org.jetbrains.kotlinx.dataframe.api.dataFrameOf
1110
import org.jetbrains.kotlinx.dataframe.api.emptyDataFrame
12-
import org.jetbrains.kotlinx.dataframe.api.frameColumn
1311
import org.jetbrains.kotlinx.dataframe.api.group
1412
import org.jetbrains.kotlinx.dataframe.api.into
1513
import org.jetbrains.kotlinx.dataframe.api.move
@@ -21,11 +19,9 @@ import org.jetbrains.kotlinx.dataframe.io.formatter
2119
import org.jetbrains.kotlinx.dataframe.io.maxDepth
2220
import org.jetbrains.kotlinx.dataframe.io.maxWidth
2321
import org.jetbrains.kotlinx.dataframe.io.print
24-
import org.jetbrains.kotlinx.dataframe.io.read
2522
import org.jetbrains.kotlinx.dataframe.io.renderToString
2623
import org.jetbrains.kotlinx.dataframe.io.renderToStringTable
2724
import org.jetbrains.kotlinx.dataframe.io.toHTML
28-
import org.jetbrains.kotlinx.dataframe.io.toStaticHtml
2925
import org.jetbrains.kotlinx.dataframe.jupyter.DefaultCellRenderer
3026
import org.jetbrains.kotlinx.dataframe.jupyter.RenderedContent
3127
import org.jetbrains.kotlinx.dataframe.samples.api.TestBase
@@ -149,14 +145,14 @@ class RenderingTests : TestBase() {
149145
body shouldContain """
150146
<thead>
151147
<tr>
152-
<th class="bottomBorder">a</th>
153-
<th class="bottomBorder">b</th>
148+
<th class="bottomBorder" style="text-align:left">a</th>
149+
<th class="bottomBorder" style="text-align:left">b</th>
154150
</tr>
155151
</thead>
156152
<tbody>
157153
<tr>
158-
<td>[1, 1]</td>
159-
<td>[2, 4]</td>
154+
<td style="vertical-align:top">[1, 1]</td>
155+
<td style="vertical-align:top">[2, 4]</td>
160156
</tr>
161157
</tbody>
162158
</table>
@@ -178,13 +174,4 @@ class RenderingTests : TestBase() {
178174
dfGroup.name.lastName.maxWidth() shouldBe 1
179175
dfGroup.name.firstName.secondName.maxWidth() shouldBe 1
180176
}
181-
182-
@Test
183-
fun `test static rendering TODO temp`() {
184-
val df = DataFrame.read("https://raw.githubusercontent.com/Kotlin/dataframe/master/data/jetbrains.json")
185-
186-
// df.toHTML().openInBrowser()
187-
df.toStaticHtml(openNestedDfs = true, includeCss = false).copy(script = "").openInBrowser()
188-
// df.get(frameColumn("repos")).get(0).toStaticHtml(openNestedDfs = false).copy(script = "").openInBrowser()
189-
}
190177
}

core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/io/html.kt

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,10 @@ import org.jetbrains.kotlinx.dataframe.api.isEmpty
1515
import org.jetbrains.kotlinx.dataframe.api.isNumber
1616
import org.jetbrains.kotlinx.dataframe.api.isSubtypeOf
1717
import org.jetbrains.kotlinx.dataframe.api.rows
18-
import org.jetbrains.kotlinx.dataframe.api.sumOf
19-
import org.jetbrains.kotlinx.dataframe.api.take
2018
import org.jetbrains.kotlinx.dataframe.columns.BaseColumn
2119
import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup
2220
import org.jetbrains.kotlinx.dataframe.columns.ColumnWithPath
2321
import org.jetbrains.kotlinx.dataframe.columns.FrameColumn
24-
import org.jetbrains.kotlinx.dataframe.columns.depth
2522
import org.jetbrains.kotlinx.dataframe.impl.DataFrameSize
2623
import org.jetbrains.kotlinx.dataframe.impl.columns.addPath
2724
import org.jetbrains.kotlinx.dataframe.impl.renderType
@@ -42,6 +39,7 @@ import java.net.URL
4239
import java.nio.file.Path
4340
import java.util.*
4441
import kotlin.io.path.writeText
42+
import kotlin.math.ceil
4543

4644
internal val tooltipLimit = 1000
4745

@@ -163,10 +161,10 @@ internal fun AnyFrame.toHtmlData(
163161
}
164162
}
165163
return ColumnDataForJs(
166-
col,
167-
if (col is ColumnGroup<*>) col.columns().map { col.columnToJs(it, rowsLimit) } else emptyList(),
168-
col.isSubtypeOf<Number?>(),
169-
contents
164+
column = col,
165+
nested = if (col is ColumnGroup<*>) col.columns().map { col.columnToJs(it, rowsLimit) } else emptyList(),
166+
rightAlign = col.isSubtypeOf<Number?>(),
167+
values = contents,
170168
)
171169
}
172170

@@ -181,12 +179,19 @@ internal fun AnyFrame.toHtmlData(
181179
}
182180
val body = getResourceText("/table.html", "ID" to rootId)
183181
val script = scripts.joinToString("\n") + "\n" + getResourceText("/renderTable.js", "___ID___" to rootId)
184-
return DataFrameHtmlData("", body, script)
182+
return DataFrameHtmlData(style = "", body = body, script = script)
185183
}
186184

187185
/**
188186
* Renders [this] [DataFrame] as static HTML (meaning no JS is used).
189187
* CSS rendering is enabled by default but can be turned off using [includeCss]
188+
*
189+
* @param configuration optional configuration for rendering
190+
* @param cellRenderer optional cell renderer for rendering
191+
* @param includeCss whether to include CSS in the output. This is `true` by default but it can be set
192+
* to `false` to emulate what happens in environments where custom CSS is not allowed.
193+
* @param openNestedDfs whether to open nested dataframes. This is `false` by default byt ut can be set
194+
* to `true` to emulate what happens in environments where `<details>` tags are not supported.
190195
*/
191196
public fun AnyFrame.toStaticHtml(
192197
configuration: DisplayConfiguration = DisplayConfiguration.DEFAULT,
@@ -206,6 +211,7 @@ public fun AnyFrame.toStaticHtml(
206211

207212
// Limit for number of rows in dataframes inside frame columns
208213
val nestedRowsLimit = configuration.nestedRowsLimit
214+
val rowsLimit = configuration.rowsLimit
209215

210216
// Adds the given tag to the html, with the given attributes and contents
211217
fun StringBuilder.emitTag(tag: String, attributes: String = "", tagContents: StringBuilder.() -> Unit) {
@@ -239,34 +245,42 @@ public fun AnyFrame.toStaticHtml(
239245
}
240246

241247
// Adds a single cell to the html. DataRows from column groups already need to be split up into separate cells.
242-
fun StringBuilder.emitCell(cellValue: Any?, borders: Set<Border>): Unit =
248+
fun StringBuilder.emitCell(cellValue: Any?, row: AnyRow, col: ColumnWithPath<*>, borders: Set<Border>): Unit =
243249
emitTag("td", "${borders.toClass()} style=\"vertical-align:top\"") {
244-
when (cellValue) {
250+
when (col) {
245251
// uses the <details> and <summary> to create a collapsible cell for dataframes
246-
is AnyFrame ->
252+
is FrameColumn<*> -> {
253+
cellValue as AnyFrame
247254
emitTag("details", if (openNestedDfs) "open" else "") {
248255
emitTag("summary") {
249256
append("DataFrame [${cellValue.size}]")
250257
}
258+
259+
// nestedRowsLimit becomes the rowsLimit for nested DFs
260+
// while the new nested rows limit is halved, keeping at least 1
261+
val newRowsLimit = nestedRowsLimit
262+
val newNestedRowsLimit = nestedRowsLimit?.let { ceil(it / 2.0).toInt() }
263+
251264
// add the dataframe as a nested table limiting the number of rows if needed
252265
// CSS will not be included here, as it is already included in the main table
253-
append(
254-
cellValue.take(nestedRowsLimit ?: Int.MAX_VALUE)
255-
.toStaticHtml(
256-
configuration,
257-
cellRenderer,
258-
includeCss = false,
259-
openNestedDfs = openNestedDfs
260-
)
261-
.body
262-
)
266+
cellValue.toStaticHtml(
267+
configuration = configuration.copy(
268+
rowsLimit = newRowsLimit,
269+
nestedRowsLimit = newNestedRowsLimit,
270+
),
271+
cellRenderer = cellRenderer,
272+
includeCss = false,
273+
openNestedDfs = openNestedDfs,
274+
).let { append(it.body) }
275+
263276
val size = cellValue.rowsCount()
264-
if (size > (nestedRowsLimit ?: Int.MAX_VALUE)) {
277+
if (size > newRowsLimit ?: Int.MAX_VALUE) {
265278
emitTag("p") {
266-
append("... showing only top $nestedRowsLimit of $size rows")
279+
append("... showing only top $newRowsLimit of $size rows")
267280
}
268281
}
269282
}
283+
}
270284

271285
// Else use the default cell renderer
272286
else ->
@@ -283,13 +297,13 @@ public fun AnyFrame.toStaticHtml(
283297
border += Border.RIGHT
284298
}
285299
val cell = row[col.path()]
286-
emitCell(cell, border)
300+
emitCell(cell, row, col, border)
287301
}
288302
}
289303

290304
// Adds the body of the html. This body contains all the cols and rows of the dataframe.
291305
fun StringBuilder.emitBody() = emitTag("tbody") {
292-
val rowsCountToRender = minOf(rowsCount(), configuration.rowsLimit ?: Int.MAX_VALUE)
306+
val rowsCountToRender = minOf(rowsCount(), rowsLimit ?: Int.MAX_VALUE)
293307
for (rowIndex in 0..<rowsCountToRender) {
294308
emitRow(df[rowIndex])
295309
}

0 commit comments

Comments
 (0)