1
1
package org.jetbrains.kotlinx.dataframe.io
2
2
3
+ import org.intellij.lang.annotations.Language
3
4
import org.jetbrains.kotlinx.dataframe.AnyCol
4
5
import org.jetbrains.kotlinx.dataframe.AnyFrame
5
6
import org.jetbrains.kotlinx.dataframe.AnyRow
@@ -21,11 +22,14 @@ import org.jetbrains.kotlinx.dataframe.jupyter.RenderedContent
21
22
import org.jetbrains.kotlinx.dataframe.name
22
23
import org.jetbrains.kotlinx.dataframe.nrow
23
24
import org.jetbrains.kotlinx.dataframe.size
24
- import org.jetbrains.kotlinx.jupyter.api.HtmlData
25
+ import java.awt.Desktop
26
+ import java.io.File
25
27
import java.io.InputStreamReader
26
28
import java.net.URL
29
+ import java.nio.file.Path
27
30
import java.util.LinkedList
28
31
import java.util.Random
32
+ import kotlin.io.path.writeText
29
33
30
34
internal val tooltipLimit = 1000
31
35
@@ -116,7 +120,7 @@ internal fun nextTableId() = sessionId + (tableInSessionId++)
116
120
internal fun AnyFrame.toHtmlData (
117
121
configuration : DisplayConfiguration = DisplayConfiguration .DEFAULT ,
118
122
cellRenderer : CellRenderer ,
119
- ): HtmlData {
123
+ ): DataFrameHtmlData {
120
124
val scripts = mutableListOf<String >()
121
125
val queue = LinkedList <Pair <AnyFrame , Int >>()
122
126
@@ -165,30 +169,31 @@ internal fun AnyFrame.toHtmlData(
165
169
}
166
170
val body = getResourceText(" /table.html" , " ID" to rootId)
167
171
val script = scripts.joinToString(" \n " ) + " \n " + getResourceText(" /renderTable.js" , " ___ID___" to rootId)
168
- return HtmlData (" " , body, script)
172
+ return DataFrameHtmlData (" " , body, script)
169
173
}
170
174
171
- internal fun HtmlData.print () = println (this )
172
-
173
- internal fun initHtml (
174
- includeJs : Boolean = true,
175
- includeCss : Boolean = true,
176
- useDarkColorScheme : Boolean = false,
177
- ): HtmlData =
178
- HtmlData (
179
- style = if (includeCss) getResources(" /table.css" ) else " " ,
180
- script = if (includeJs) getResourceText(" /init.js" ) else " " ,
181
- body = " " ,
182
- )
175
+ internal fun DataFrameHtmlData.print () = println (this )
176
+
177
+ @Deprecated(" Clarify difference with .toHTML()" , ReplaceWith (" this.toStandaloneHTML().toString()" , " org.jetbrains.kotlinx.dataframe.io.toStandaloneHTML" ))
178
+ public fun <T > DataFrame<T>.html (): String = toStandaloneHTML().toString()
183
179
184
- public fun <T > DataFrame<T>.html (): String = toHTML(extraHtml = initHtml()).toString()
180
+ /* *
181
+ * @return DataFrameHtmlData with table script and css definitions. Can be saved as an *.html file and displayed in the browser
182
+ */
183
+ public fun <T > DataFrame<T>.toStandaloneHTML (
184
+ configuration : DisplayConfiguration = DisplayConfiguration .DEFAULT ,
185
+ cellRenderer : CellRenderer = org.jetbrains.kotlinx.dataframe.jupyter.DefaultCellRenderer ,
186
+ getFooter : (DataFrame <T >) -> String = { "DataFrame [${it.size}]" },
187
+ ): DataFrameHtmlData = toHTML(configuration, cellRenderer, getFooter).withTableDefinitions()
185
188
189
+ /* *
190
+ * @return DataFrameHtmlData without additional definitions. Can be rendered in Jupyter kernel environments
191
+ */
186
192
public fun <T > DataFrame<T>.toHTML (
187
193
configuration : DisplayConfiguration = DisplayConfiguration .DEFAULT ,
188
- extraHtml : HtmlData ? = null,
189
194
cellRenderer : CellRenderer = org.jetbrains.kotlinx.dataframe.jupyter.DefaultCellRenderer ,
190
195
getFooter : (DataFrame <T >) -> String = { "DataFrame [${it.size}]" },
191
- ): HtmlData {
196
+ ): DataFrameHtmlData {
192
197
val limit = configuration.rowsLimit ? : Int .MAX_VALUE
193
198
194
199
val footer = getFooter(this )
@@ -204,11 +209,79 @@ public fun <T> DataFrame<T>.toHTML(
204
209
}
205
210
206
211
val tableHtml = toHtmlData(configuration, cellRenderer)
207
- val html = tableHtml + HtmlData (" " , bodyFooter, " " )
208
212
209
- return if (extraHtml != null ) extraHtml + html else html
213
+ return tableHtml + DataFrameHtmlData (" " , bodyFooter, " " )
214
+ }
215
+
216
+ /* *
217
+ * Container for HTML page data in form of String
218
+ * Can be used to compose rendered dataframe tables with additional HTML elements
219
+ */
220
+ public data class DataFrameHtmlData (val style : String = " " , val body : String = " " , val script : String = " " ) {
221
+ @Language(" html" )
222
+ override fun toString (): String = """
223
+ <html>
224
+ <head>
225
+ <style type="text/css">
226
+ $style
227
+ </style>
228
+ </head>
229
+ <body>
230
+ $body
231
+ </body>
232
+ <script>
233
+ $script
234
+ </script>
235
+ </html>
236
+ """ .trimIndent()
237
+
238
+ public operator fun plus (other : DataFrameHtmlData ): DataFrameHtmlData =
239
+ DataFrameHtmlData (
240
+ style + " \n " + other.style,
241
+ body + " \n " + other.body,
242
+ script + " \n " + other.script,
243
+ )
244
+
245
+ public fun writeHTML (destination : File ) {
246
+ destination.writeText(toString())
247
+ }
248
+
249
+ public fun writeHTML (destination : Path ) {
250
+ destination.writeText(toString())
251
+ }
252
+
253
+ public fun openInBrowser () {
254
+ val file = File .createTempFile(" df_rendering" , " .html" )
255
+ writeHTML(file)
256
+ val uri = file.toURI()
257
+ val desktop = Desktop .getDesktop()
258
+ desktop.browse(uri)
259
+ }
260
+
261
+ public fun withTableDefinitions (): DataFrameHtmlData = tableDefinitions() + this
262
+
263
+ public companion object {
264
+ /* *
265
+ * @return CSS and JS required to render DataFrame tables
266
+ * Can be used as a starting point to create page with multiple tables
267
+ * @see DataFrame.toHTML
268
+ * @see DataFrameHtmlData.plus
269
+ */
270
+ public fun tableDefinitions (
271
+ includeJs : Boolean = true,
272
+ includeCss : Boolean = true,
273
+ ): DataFrameHtmlData = DataFrameHtmlData (
274
+ style = if (includeCss) getResources(" /table.css" ) else " " ,
275
+ script = if (includeJs) getResourceText(" /init.js" ) else " " ,
276
+ body = " " ,
277
+ )
278
+ }
210
279
}
211
280
281
+ /* *
282
+ * @param rowsLimit null to disable rows limit
283
+ * @param cellContentLimit -1 to disable content trimming
284
+ */
212
285
public data class DisplayConfiguration (
213
286
var rowsLimit : Int? = 20 ,
214
287
var nestedRowsLimit : Int? = 5 ,
@@ -452,7 +525,7 @@ internal class DataFrameFormatter(
452
525
" </a>"
453
526
)
454
527
455
- is HtmlData -> RenderedContent .text(value.body)
528
+ is DataFrameHtmlData -> RenderedContent .text(value.body)
456
529
else -> renderer.content(value, configuration)
457
530
}
458
531
if (result != null && result.textLength > configuration.cellContentLimit) return null
0 commit comments