Skip to content

Make rendering of Plot github-view-compatible #155

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
package org.jetbrains.kotlinx.kandy.letsplot.jupyter

import jetbrains.datalore.plot.PlotHtmlHelper
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import org.jetbrains.kotlinx.jupyter.api.HTML
import org.jetbrains.kotlinx.jupyter.api.MimeTypedResultEx
import org.jetbrains.kotlinx.jupyter.api.Notebook
Expand All @@ -19,24 +17,23 @@ import org.jetbrains.kotlinx.kandy.letsplot.multiplot.model.PlotBunch
import org.jetbrains.kotlinx.kandy.letsplot.multiplot.model.PlotGrid
import org.jetbrains.kotlinx.kandy.letsplot.translator.toLetsPlot
import org.jetbrains.kotlinx.kandy.letsplot.translator.wrap
import org.jetbrains.kotlinx.kandy.util.serialization.serializeSpec
import org.jetbrains.letsPlot.Figure
import org.jetbrains.letsPlot.GGBunch
import org.jetbrains.kotlinx.kandy.letsplot.util.NotebookRenderingContext
import org.jetbrains.kotlinx.kandy.letsplot.util.figureToMimeResult
import org.jetbrains.letsPlot.LetsPlot
import org.jetbrains.letsPlot.frontend.NotebookFrontendContext
import org.jetbrains.letsPlot.intern.figure.SubPlotsFigure
import org.jetbrains.letsPlot.intern.toSpec

@JupyterLibrary
internal class Integration(
private val notebook: Notebook,
private val options: MutableMap<String, String?>,
) : JupyterIntegration() {

lateinit var frontendContext: NotebookFrontendContext

private val jsVersion = "3.1.0"

private lateinit var initHtml: MimeTypedResultEx
private val frontendContext = LetsPlot.setupNotebook(jsVersion, true) {
initHtml = HTML(it)
}
private val config = JupyterConfig()

override fun Builder.onLoaded() {

resources {
Expand All @@ -46,9 +43,7 @@ internal class Integration(
}

onLoaded {
frontendContext = LetsPlot.setupNotebook("3.1.0", true) {
display(HTML(it), null)
}
display(initHtml, null)
LetsPlot.apiVersion = "4.3.0"
//display(HTML(frontendContext.getConfigureHtml()), null)
}
Expand All @@ -74,50 +69,14 @@ internal class Integration(
import("org.jetbrains.kotlinx.kandy.letsplot.util.font.*")
// import("org.jetbrains.kotlinx.kandy.letsplot.util.statParameters.*")

/*val applyColorScheme: Boolean = options["applyColorScheme"]?.toBooleanStrictOrNull() ?: true*/

fun Figure.toHTML(): String {
return when (this) {
is org.jetbrains.letsPlot.intern.Plot -> frontendContext.getHtml(this)
is SubPlotsFigure -> frontendContext.getHtml(this)
is GGBunch -> frontendContext.getHtml(this)
else -> error("Unsupported Figure")
}
}

val config = JupyterConfig()

onLoaded {
declare("kandyConfig" to config)
}

fun Figure.toMimeResult(): MimeTypedResultEx {
val spec = toSpec()
/*when (this) {
is org.jetbrains.letsPlot.intern.Plot -> spec.applyColorSchemeToPlotSpec()
is SubPlotsFigure -> spec.applyColorSchemeToPlotGrid()
is GGBunch -> spec.applyColorSchemeToGGBunch()
else -> error("Unsupported Figure")
}*/
val html = toHTML()
return MimeTypedResultEx(
buildJsonObject {
put("text/html", JsonPrimitive(html))
put("application/plot+json", buildJsonObject {
put("output_type", JsonPrimitive("lets_plot_spec"))
put("output", serializeSpec(spec))
put("apply_color_scheme", JsonPrimitive(config.applyColorScheme))
put("swing_enabled", JsonPrimitive(config.swingEnabled))
})
}
)
with(NotebookRenderingContext(frontendContext, config)) {
render<Plot> { figureToMimeResult(it.toLetsPlot()) }
render<PlotBunch> { figureToMimeResult(it.wrap()) }
render<PlotGrid> { figureToMimeResult(it.wrap()) }
}


render<Plot> { it.toLetsPlot().toMimeResult() }
render<PlotBunch> { it.wrap().toMimeResult() }
render<PlotGrid> { it.wrap().toMimeResult() }
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.jetbrains.kotlinx.kandy.letsplot.util

import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract

internal fun <K, V: Any> Map<K, V>.extendedBy(other: Map<K, V>, join: (V, V) -> V): Map<K, V> {
return (this + other).mapValues { (k, v) ->
if (k in this && k in other)
join(this[k]!!, other[k]!!)
else
v
}
}

@OptIn(ExperimentalContracts::class)
private inline fun <reified T> bothOfType(a: Any, b: Any): Boolean {
contract { returns(true) implies (a is T && b is T) }
return a is T && b is T
}

@OptIn(ExperimentalContracts::class)
private inline fun <reified T> bothOfTypeAnd(a: Any, b: Any, condition: (T) -> Boolean): Boolean {
contract { returns(true) implies (a is T && b is T) }
return a is T && b is T && condition(a) && condition(b)
}

internal infix fun Map<String, JsonElement>.extendedByJson(other: Map<String, JsonElement>): JsonObject {
val map = this.extendedBy(other) { a, b ->
// This logic might be enhanced
when {
bothOfTypeAnd<JsonPrimitive>(a, b) { it.isString } -> JsonPrimitive(a.content + b.content)
bothOfType<JsonArray>(a, b) -> JsonArray(a + b)
bothOfType<JsonObject>(a, b) -> JsonObject(a + b)
else -> a
}
}
return JsonObject(map)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.jetbrains.kotlinx.kandy.letsplot.util

import jetbrains.datalore.plot.PlotSvgExport
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import org.jetbrains.kotlinx.jupyter.api.MimeTypedResultEx
import org.jetbrains.kotlinx.jupyter.api.MimeTypes
import org.jetbrains.kotlinx.kandy.letsplot.jupyter.JupyterConfig
import org.jetbrains.kotlinx.kandy.util.serialization.serializeSpec
import org.jetbrains.letsPlot.Figure
import org.jetbrains.letsPlot.GGBunch
import org.jetbrains.letsPlot.frontend.NotebookFrontendContext
import org.jetbrains.letsPlot.intern.figure.SubPlotsFigure
import org.jetbrains.letsPlot.intern.toSpec
import java.util.*

internal class NotebookRenderingContext(
val frontendContext: NotebookFrontendContext,
val config: JupyterConfig,
)

internal fun NotebookRenderingContext.figureToHtml(figure: Figure): String {
return when (figure) {
is org.jetbrains.letsPlot.intern.Plot -> frontendContext.getHtml(figure)
is SubPlotsFigure -> frontendContext.getHtml(figure)
is GGBunch -> frontendContext.getHtml(figure)
else -> error("Unsupported Figure")
}
}

internal fun NotebookRenderingContext.figureToMimeJson(figure: Figure): JsonObject {
val spec = figure.toSpec()
val html = figureToHtml(figure)
return buildJsonObject {
put(MimeTypes.HTML, JsonPrimitive(html))
put("application/plot+json", buildJsonObject {
put("output_type", JsonPrimitive("lets_plot_spec"))
put("output", serializeSpec(spec))
put("apply_color_scheme", JsonPrimitive(config.applyColorScheme))
put("swing_enabled", JsonPrimitive(config.swingEnabled))
})
}
}

internal fun NotebookRenderingContext.figureToMimeResult(figure: Figure): MimeTypedResultEx {
val basicResult = figureToMimeJson(figure)

val plotSVG = PlotSvgExport.buildSvgImageFromRawSpecs(figure.toSpec())
val id = UUID.randomUUID().toString()
val svgWithID = with(plotSVG) {
take(4) + " id=$id" + drop(4)
}
val extraHTML = """

$svgWithID
<script>document.getElementById("$id").style.display = "none";</script>
""".trimIndent()

val extraResult = mapOf(MimeTypes.HTML to JsonPrimitive(extraHTML))
return MimeTypedResultEx(basicResult extendedByJson extraResult)
}