Skip to content

Commit

Permalink
Better separation charts & notebooks
Browse files Browse the repository at this point in the history
  • Loading branch information
jbaron committed Aug 12, 2023
1 parent fef0adc commit 0fab9e8
Show file tree
Hide file tree
Showing 26 changed files with 211 additions and 313 deletions.
92 changes: 5 additions & 87 deletions roboquant-charts/src/main/kotlin/org/roboquant/charts/Chart.kt
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,14 @@ private class TripleAdapter : JsonSerializer<Triple<*, *, *>> {
/**
* Base class all roboquant charts. Subclasses should implement at least the [getOption] method.
*/
abstract class Chart : HTMLOutput() {
abstract class Chart {

/**
* Does the generated option JSON string contain JavaScript. If true, additional code will be generated to parse
* this into a Javascript function.
* Does the generated option JSON string contain JavaScript.
* If true, additional client-side code might be generated to parse the JSON part into a Javascript function.
*/
private var containsJavaScript: Boolean = false
var containsJavaScript: Boolean = false
protected set

/**
* Height for charts, default being 500 pixels. Subclasses can override this value
Expand All @@ -135,23 +136,6 @@ abstract class Chart : HTMLOutput() {
const val JSURL =
"https://cdn.jsdelivr.net/gh/neurallayer/roboquant-jupyter-js@$COMMIT/echarts.min.js?version=$COMMIT"

/**
* Used to ensure the output divs have a unique id. This is only used in classic notebooks since then the
* JavaScript cannot access the DIV by document.currentScript.parentElement and will fall back to the id.
*/
internal var counter = 0

/**
* The theme to use for charts, valid options are auto, light and dark with the default being auto. When set
* to auto, roboquant will try to match the chart theme to the notebook theme.
*/
var theme = "auto"
get() = if (field == "auto") notebookTheme ?: field else field
set(value) {
require(value in setOf("auto", "light", "dark")) { "valid options are auto, light and dark" }
field = value
}

/**
* Maximum number of samples to plot in a chart. Certain types of charts can become very large and as
* a result make your browser unresponsive. By lowering this value (default is [Int.MAX_VALUE])
Expand Down Expand Up @@ -214,72 +198,6 @@ abstract class Chart : HTMLOutput() {
}
}

/**
* Generates the HTML snippet required to draw a chart. This is an HTML snippet and not a full HTML page,
* and it is suitable to be rendered in the cell output of a Jupyter notebook.
*/
override fun asHTML(): String {
val fragment = getOption().renderJson().trimStart()
val id = "roboquant-${counter++}"
val convertor = if (containsJavaScript)
"option.tooltip.formatter = new Function('p', option.tooltip.formatter);"
else
""

val themeStatement = if (theme == "auto") """
let theme = document.body.dataset.jpThemeLight === 'false' ? 'dark' : 'light'
""".trimIndent() else """
let theme='$theme'
""".trimIndent()

return """
<div style="width:100%;height:${height}px;" class="rqcharts" id="$id">
<script type="text/javascript">
(function () {
let elem = document.currentScript.parentElement;
if (elem.tagName === "HEAD") {
elem = document.getElementById("$id");
}
const option = $fragment;$convertor
window.call_echarts(
function () {
$themeStatement;
let myChart = echarts.init(elem, theme);
myChart.setOption(option);
let resizeObserver = new ResizeObserver(() => myChart.resize());
resizeObserver.observe(elem);
}
);
})()
</script>
</div>
""".trimIndent()
}

/**
* Generates a standalone HTML page for the chart. This page can be saved and, for example, viewed in a
* standalone browser.
*/
override fun asHTMLPage(): String {
val fragment = asHTML()
val script = getScript()

return """
<html lang="en">
<head>
<title>roboquant chart</title>
$script
<style media='screen'>
html { margin: 0; padding: 0; min-height: ${height}px;}
body { margin: 0; padding: 10px; min-height: ${height}px;}
</style>
</head>
<body>
$fragment
</body>
</html>
""".trimIndent()
}

/**
* Return a standard toolbox that can be included in a chart
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package org.roboquant.charts

import org.roboquant.Roboquant
import org.roboquant.common.TimeSeries
import java.io.File
import java.nio.charset.StandardCharsets

/**
Expand All @@ -29,7 +30,7 @@ import java.nio.charset.StandardCharsets
class MetricsReport(
private val roboquant: Roboquant,
maxSamples: Int = 10_000
) : HTMLOutput() {
) {

init {
Chart.maxSamples = maxSamples
Expand All @@ -56,6 +57,30 @@ class MetricsReport(
}


/**
* Generates the HTML snippet required to draw a chart.
*/
private fun Chart.asHTML(id: String): String {
var fragment = getOption().renderJson().trimStart()
if (containsJavaScript) fragment += "option.tooltip.formatter = new Function('p', option.tooltip.formatter);"

return """
<div style="width:100%;height:${height}px;" class="rqcharts" id="$id">
<script type="text/javascript">
(function() {
let elem = document.getElementById("$id");
const option = $fragment
let myChart = echarts.init(elem, theme);
myChart.setOption(option);
let resizeObserver = new ResizeObserver(() => myChart.resize());
resizeObserver.observe(elem);
})();
</script>
</div>
""".trimIndent()
}


private fun metricsToHTML(): String {
val metricsMap = logger.metricNames.map { it to logger.getMetric(it) }
val result = StringBuffer()
Expand All @@ -76,16 +101,18 @@ class MetricsReport(


private fun chartsToHTML(): String {
var id = 0
return charts.joinToString {
"""<div class="flex-item" style="flex: 700px;">
<div class="chart">
${it().asHTML()}
${it().asHTML("chart-${id++}")}
</div>
</div>""".trimIndent()
}

}

override fun asHTML(): String {
private fun asHTML(): String {
return """
<div class="flex-container">
<h2>Metrics</h2>
Expand All @@ -103,7 +130,7 @@ class MetricsReport(
return String(stream.readAllBytes(), StandardCharsets.UTF_8)
}

override fun asHTMLPage(): String {
private fun asHTMLPage(): String {
return """
<!doctype html>
<html lang="en">
Expand All @@ -121,6 +148,15 @@ class MetricsReport(
""".trimIndent()
}

/**
* Save HTML output to a file with name [filename] on the server.
*/
fun toHTMLFile(filename: String) {
val content = asHTMLPage()
val f = File(filename)
f.writeText(content)
}

private operator fun StringBuffer.plusAssign(s: String) {
this.append(s)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ internal class AllocationChartTest {
fun test() {
val account = TestData.usAccount()
val chart = AllocationChart(account.positions)
assertTrue(chart.asHTML().isNotBlank())
assertTrue(chart.getOption().renderJson().isNotBlank())
}

@Test
fun testPerAssetClass() {
val account = TestData.usAccount()
val chart = AllocationChart(account.positions, includeAssetClass = true)
assertTrue(chart.asHTML().isNotBlank())
assertTrue(chart.getOption().renderJson().isNotBlank())
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ internal class BoxChartTest {
val logger = MemoryLogger()
val data = logger.getMetric("test")
val chart = BoxChart(data)
assertTrue(chart.asHTML().isNotBlank())
assertTrue(chart.getOption().renderJson().isNotBlank())
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ internal class CalendarChartTest {
fun test() {
val data = TestData.data
val chart = CalendarChart(data)
assertTrue(chart.asHTML().isNotBlank())
assertTrue(chart.getOption().renderJson().isNotBlank())

Chart.counter = 0
TestData.testFile(chart, "calendarchart.txt")
TestData.testFile(chart, "calendarchart")
}

}
18 changes: 2 additions & 16 deletions roboquant-charts/src/test/kotlin/org/roboquant/charts/ChartTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,15 @@ package org.roboquant.charts
import org.icepear.echarts.Option
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.io.TempDir
import org.roboquant.common.USD
import org.roboquant.feeds.RandomWalkFeed
import java.io.File
import java.time.Instant
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertTrue

internal class ChartTest {

@TempDir
lateinit var folder: File

private class MyChart : Chart() {

init {
Expand All @@ -54,20 +49,15 @@ internal class ChartTest {
fun test() {
val f = RandomWalkFeed.lastYears(1, 1, generateBars = true)
val asset = f.assets.first()
Chart.counter = 0
val chart = PriceBarChart(f, asset)
val html = chart.asHTML()
val html = chart.getOption().renderJson()
assertTrue(html.isNotBlank())
assertEquals(700, chart.height)
assertContains(html, asset.symbol)

Chart.counter = 0
val chart2 = PriceBarChart(f, asset.symbol)
assertEquals(html, chart2.asHTML())
assertEquals(html, chart2.getOption().renderJson())

val file = File(folder, "test.html")
chart.toHTMLFile(file.toString())
assertTrue(file.exists())

}

Expand Down Expand Up @@ -100,10 +90,6 @@ internal class ChartTest {
chart.getOption().renderJson()
}


val code = chart.asHTML()
assertContains(code, "123px")
assertContains(code, "echarts.init")
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ internal class CorrelationChartTest {
fun test() {
val feed = RandomWalkFeed.lastYears(1, 5)
val chart = CorrelationChart(feed, feed.assets)
assertTrue(chart.asHTML().isNotBlank())
assertTrue(chart.getOption().renderJson().isNotBlank())
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ internal class HistogramChartTest {
fun test() {
val data = TestData.data
val chart = HistogramChart(data)
assertTrue(chart.asHTML().isNotBlank())
assertTrue(chart.getOption().renderJson().isNotBlank())
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ internal class OrderChartTest {
fun test() {
val account = TestData.fullAccount
val chart = OrderChart(account.closedOrders)
assertTrue(chart.asHTML().isNotBlank())
assertTrue(chart.getOption().renderJson().isNotBlank())
}

}
Loading

0 comments on commit 0fab9e8

Please sign in to comment.