From 429b289323c0c328777cf36067d1f420d89a58a5 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Thu, 22 Jul 2021 21:12:11 +0300 Subject: [PATCH 1/4] [WIP] migration to DF 0.5 --- CHANGELOG.md | 3 ++- build.gradle.kts | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- plotlykt-core/api/plotlykt-core.api | 6 +++--- .../kotlin/space/kscience/plotly/Plot.kt | 6 +++--- .../kscience/plotly/PlotSerializationTest.kt | 4 ++-- plotlykt-server/api/plotlykt-server.api | 2 +- .../plotly/server/MetaChangeCollector.kt | 16 +++++++--------- .../space/kscience/plotly/server/PlotlyServer.kt | 4 +--- settings.gradle.kts | 4 ++-- 10 files changed, 24 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb60f014..0be994c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - build tools 0.10.0 -- +- DataForge 0.5.0 + ### Deprecated ### Removed diff --git a/build.gradle.kts b/build.gradle.kts index aceed2b6..1ed7a193 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,11 +2,11 @@ plugins { id("ru.mipt.npm.gradle.project") } -val dataforgeVersion by extra("0.4.3") +val dataforgeVersion by extra("0.5.0-dev-2") allprojects { group = "space.kscience" - version = "0.4.4-dev" + version = "0.4.5" } apiValidation { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 0f80bbf5..05679dc3 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/plotlykt-core/api/plotlykt-core.api b/plotlykt-core/api/plotlykt-core.api index f649c1f4..253bf084 100644 --- a/plotlykt-core/api/plotlykt-core.api +++ b/plotlykt-core/api/plotlykt-core.api @@ -37,10 +37,10 @@ public final class space/kscience/plotly/OrcaFormat : java/lang/Enum { public final class space/kscience/plotly/Plot : space/kscience/dataforge/meta/Configurable, space/kscience/dataforge/meta/MetaRepr { public fun ()V - public fun (Lspace/kscience/dataforge/meta/Config;)V - public synthetic fun (Lspace/kscience/dataforge/meta/Config;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lspace/kscience/dataforge/meta/ObservableMeta;)V + public synthetic fun (Lspace/kscience/dataforge/meta/ObservableMeta;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun addTrace (Lspace/kscience/plotly/models/Trace;)V - public fun getConfig ()Lspace/kscience/dataforge/meta/Config; + public fun getConfig ()Lspace/kscience/dataforge/meta/ObservableMeta; public final fun getData ()Ljava/util/List; public final fun getLayout ()Lspace/kscience/plotly/models/Layout; public fun toMeta ()Lspace/kscience/dataforge/meta/Meta; diff --git a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/Plot.kt b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/Plot.kt index dd81d762..c5e4fdd4 100644 --- a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/Plot.kt +++ b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/Plot.kt @@ -13,7 +13,7 @@ import space.kscience.plotly.models.Trace */ @DFBuilder public class Plot( - override val config: Config = Config(), + override val config: ObservableMeta = ObservableMeta(), ) : Configurable, MetaRepr { /** @@ -28,10 +28,10 @@ public class Plot( public fun addTrace(trace: Trace) { val traceRoot = trace.rootNode - if (traceRoot is Config) { + if (traceRoot is ObservableMeta) { config.append("data", traceRoot) } else { - val traceConfig = Config() + val traceConfig = ObservableMeta() trace.rootNode?.let { traceConfig.update(it) } trace.retarget(traceConfig) config.append("data", traceConfig) diff --git a/plotlykt-core/src/jvmTest/kotlin/space/kscience/plotly/PlotSerializationTest.kt b/plotlykt-core/src/jvmTest/kotlin/space/kscience/plotly/PlotSerializationTest.kt index ae837f19..5df41611 100644 --- a/plotlykt-core/src/jvmTest/kotlin/space/kscience/plotly/PlotSerializationTest.kt +++ b/plotlykt-core/src/jvmTest/kotlin/space/kscience/plotly/PlotSerializationTest.kt @@ -2,7 +2,7 @@ package space.kscience.plotly import org.junit.jupiter.api.Test import space.kscience.dataforge.meta.Meta -import space.kscience.dataforge.meta.toConfig +import space.kscience.dataforge.meta.asObservable import space.kscience.plotly.models.TraceType import kotlin.test.assertEquals @@ -18,7 +18,7 @@ class PlotSerializationTest { } } - val plot = Plot(meta.toConfig()) + val plot = Plot(meta.asObservable()) assertEquals(1, plot.data.size) assertEquals(TraceType.scatter, plot.data[0].type) assertEquals(1.0, plot.data[0].x.doubles[0]) diff --git a/plotlykt-server/api/plotlykt-server.api b/plotlykt-server/api/plotlykt-server.api index 2c93b990..3461eb65 100644 --- a/plotlykt-server/api/plotlykt-server.api +++ b/plotlykt-server/api/plotlykt-server.api @@ -12,7 +12,7 @@ public final class space/kscience/plotly/server/PlotlyServer : space/kscience/da public static final field Companion Lspace/kscience/plotly/server/PlotlyServer$Companion; public static final field DEFAULT_PAGE Ljava/lang/String; public final fun getApplication ()Lio/ktor/application/Application; - public fun getConfig ()Lspace/kscience/dataforge/meta/Config; + public fun getConfig ()Lspace/kscience/dataforge/meta/ObservableMeta; public final fun getEmbedData ()Z public final fun getUpdateInterval ()I public final fun getUpdateMode ()Lspace/kscience/plotly/server/PlotlyUpdateMode; diff --git a/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/MetaChangeCollector.kt b/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/MetaChangeCollector.kt index ba6b4f80..e295432b 100644 --- a/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/MetaChangeCollector.kt +++ b/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/MetaChangeCollector.kt @@ -19,7 +19,7 @@ import space.kscience.plotly.Plot */ public class MetaChangeCollector { private val mutex = Mutex() - private var state = Config() + private var state = MetaBuilder() public suspend fun collect(name: Name, newItem: MetaItem?) { mutex.withLock { @@ -31,7 +31,7 @@ public class MetaChangeCollector { return if (!state.isEmpty()) { mutex.withLock { state.seal().also { - state = Config() + state = MetaBuilder() } } } else { @@ -40,17 +40,15 @@ public class MetaChangeCollector { } } -private fun Config.collectChanges(scope: CoroutineScope): MetaChangeCollector { - return MetaChangeCollector().apply { - onChange(this) { name, _, newItem -> - scope.launch { - collect(name, newItem) - } +private fun ObservableMeta.collectChanges(scope: CoroutineScope): MetaChangeCollector = MetaChangeCollector().apply { + onChange(this) { name, _, newItem -> + scope.launch { + collect(name, newItem) } } } -private fun Config.flowChanges(scope: CoroutineScope, updateInterval: Int): Flow { +private fun ObservableMeta.flowChanges(scope: CoroutineScope, updateInterval: Int): Flow { val collector = collectChanges(scope) return flow { while (true) { diff --git a/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/PlotlyServer.kt b/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/PlotlyServer.kt index 794c3122..e68dbf15 100644 --- a/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/PlotlyServer.kt +++ b/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/PlotlyServer.kt @@ -14,7 +14,6 @@ import io.ktor.response.respondText import io.ktor.routing.* import io.ktor.server.engine.ApplicationEngine import io.ktor.server.engine.embeddedServer -import io.ktor.util.KtorExperimentalAPI import io.ktor.websocket.WebSockets import io.ktor.websocket.application import io.ktor.websocket.webSocket @@ -107,7 +106,7 @@ internal class ServerPlotlyRenderer( public class PlotlyServer internal constructor( private val routing: Routing, private val rootRoute: String, ) : Configurable { - override val config: Config = Config() + override val config: ObservableMeta = ObservableMeta() public var updateMode: PlotlyUpdateMode by config.enum(PlotlyUpdateMode.NONE, key = UPDATE_MODE_KEY) public var updateInterval: Int by config.int(300, key = UPDATE_INTERVAL_KEY) public var embedData: Boolean by config.boolean(false) @@ -290,7 +289,6 @@ public fun PlotlyServer.pullUpdates(interval: Int = 1000): PlotlyServer = apply /** * Start static server (updates via reload) */ -@OptIn(KtorExperimentalAPI::class) public fun Plotly.serve( scope: CoroutineScope = GlobalScope, host: String = "localhost", diff --git a/settings.gradle.kts b/settings.gradle.kts index 19b07edc..49fb0cc3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,5 @@ +rootProject.name = "plotly-kt" + pluginManagement { val toolsVersion = "0.10.0" @@ -17,8 +19,6 @@ pluginManagement { } } -rootProject.name = "plotlykt" - include( ":plotlykt-core", ":plotlykt-jupyter", From 789386c503871cfd98638c340d6bb1ad1af98c3a Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sat, 31 Jul 2021 18:30:16 +0300 Subject: [PATCH 2/4] WIP Migration to DF 0.5 --- build.gradle.kts | 7 +- .../kotlin/candlestick/basicCandleStick.kt | 4 +- .../kotlin/candlestick/dynamicCandleStick.kt | 3 +- .../src/main/kotlin/complexDynamicServer.kt | 4 +- examples/src/main/kotlin/plots3d/scatter3d.kt | 6 +- examples/src/main/kotlin/plots3d/surface3d.kt | 6 +- examples/src/main/kotlin/simpleServer.kt | 2 - .../src/main/kotlin/unsupportedFeature.kt | 15 +-- plotlykt-core/api/plotlykt-core.api | 16 ++- .../kotlin/space/kscience/plotly/Plot.kt | 26 ++--- .../kotlin/space/kscience/plotly/Plotly.kt | 11 +- .../kotlin/space/kscience/plotly/dfExt.kt | 105 +++++++++++------- .../space/kscience/plotly/models/Axis.kt | 12 +- .../kscience/plotly/models/CandleStick.kt | 11 +- .../space/kscience/plotly/models/Layout.kt | 11 +- .../space/kscience/plotly/models/Trace.kt | 4 +- .../space/kscience/plotly/plotlyElement.kt | 16 +-- .../kscience/plotly/PlotSerializationTest.kt | 9 +- plotlykt-server/api/plotlykt-server.api | 5 +- .../plotly/server/MetaChangeCollector.kt | 23 ++-- .../kscience/plotly/server/PlotlyServer.kt | 22 ++-- .../plotly/server/PlotlyServerIntegration.kt | 6 +- .../space/kscience/plotly/server/Update.kt | 8 +- settings.gradle.kts | 3 +- 24 files changed, 183 insertions(+), 152 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 1ed7a193..9d26ff80 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,11 +2,14 @@ plugins { id("ru.mipt.npm.gradle.project") } -val dataforgeVersion by extra("0.5.0-dev-2") +val dataforgeVersion by extra("0.5.0-dev-5") allprojects { group = "space.kscience" - version = "0.4.5" + version = "0.5.0" + repositories{ + mavenLocal() + } } apiValidation { diff --git a/examples/src/main/kotlin/candlestick/basicCandleStick.kt b/examples/src/main/kotlin/candlestick/basicCandleStick.kt index b17dbd23..4ed9413a 100644 --- a/examples/src/main/kotlin/candlestick/basicCandleStick.kt +++ b/examples/src/main/kotlin/candlestick/basicCandleStick.kt @@ -1,12 +1,12 @@ package candlestick import space.kscience.dataforge.meta.invoke -import space.kscience.dataforge.meta.set import space.kscience.plotly.Plotly import space.kscience.plotly.layout import space.kscience.plotly.makeFile import space.kscience.plotly.models.AxisType import space.kscience.plotly.models.CandleStick +import space.kscience.plotly.models.DragMode internal val candleStickTrace = CandleStick { x.strings = listOf( @@ -190,7 +190,7 @@ fun main() { Plotly.plot { traces(candleStickTrace) layout { - set("dragmode", "zoom") + dragmode = DragMode.zoom margin { r = 10 t = 25 diff --git a/examples/src/main/kotlin/candlestick/dynamicCandleStick.kt b/examples/src/main/kotlin/candlestick/dynamicCandleStick.kt index 673c092d..78a51880 100644 --- a/examples/src/main/kotlin/candlestick/dynamicCandleStick.kt +++ b/examples/src/main/kotlin/candlestick/dynamicCandleStick.kt @@ -1,6 +1,5 @@ package candlestick -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch @@ -41,7 +40,7 @@ fun main() { } } - GlobalScope.launch { + launch { while (isActive) { delay(400) candleStickTrace.open.numbers = candleStickTrace.open.doubles.map { it + Random.nextDouble() - 0.5 } diff --git a/examples/src/main/kotlin/complexDynamicServer.kt b/examples/src/main/kotlin/complexDynamicServer.kt index 2a69df77..c22249e9 100644 --- a/examples/src/main/kotlin/complexDynamicServer.kt +++ b/examples/src/main/kotlin/complexDynamicServer.kt @@ -86,7 +86,7 @@ fun main() { } trace { val flow: Flow> = sinFlow.windowed(100) - GlobalScope.launch { + launch { updateFrom(Trace.Y_AXIS, flow) } } @@ -101,7 +101,7 @@ fun main() { } trace { val flow: Flow> = cosFlow.windowed(100) - GlobalScope.launch { + launch { updateFrom(Trace.Y_AXIS, flow) } } diff --git a/examples/src/main/kotlin/plots3d/scatter3d.kt b/examples/src/main/kotlin/plots3d/scatter3d.kt index 13cbaafc..3ea12d97 100644 --- a/examples/src/main/kotlin/plots3d/scatter3d.kt +++ b/examples/src/main/kotlin/plots3d/scatter3d.kt @@ -1,6 +1,6 @@ package plots3d -import space.kscience.dataforge.meta.set +import space.kscience.dataforge.meta.configure import space.kscience.plotly.Plotly import space.kscience.plotly.makeFile import space.kscience.plotly.trace @@ -8,7 +8,9 @@ import space.kscience.plotly.trace fun main() { val plot = Plotly.plot { trace { - set("type","scatter3d") + configure { + "type" put "scatter3d" + } x(1,2,3) y(1,2,3) z(1,2,3) diff --git a/examples/src/main/kotlin/plots3d/surface3d.kt b/examples/src/main/kotlin/plots3d/surface3d.kt index c2f09b79..106a2b8b 100644 --- a/examples/src/main/kotlin/plots3d/surface3d.kt +++ b/examples/src/main/kotlin/plots3d/surface3d.kt @@ -1,6 +1,6 @@ package plots3d -import space.kscience.dataforge.meta.set +import space.kscience.dataforge.meta.configure import space.kscience.dataforge.values.asValue import space.kscience.plotly.Plotly import space.kscience.plotly.makeFile @@ -28,7 +28,9 @@ fun main() { l(8.99, 8.99, 8.98, 9.18, 9.2, 9.19), l(8.93, 8.97, 8.97, 9.18, 9.2, 9.18) ).asValue() - set("type", "surface") + configure { + "type" put "surface" + } } } plot.makeFile() diff --git a/examples/src/main/kotlin/simpleServer.kt b/examples/src/main/kotlin/simpleServer.kt index 6407bed6..fc3f3a03 100644 --- a/examples/src/main/kotlin/simpleServer.kt +++ b/examples/src/main/kotlin/simpleServer.kt @@ -1,4 +1,3 @@ -import io.ktor.util.KtorExperimentalAPI import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.html.a import kotlinx.html.div @@ -17,7 +16,6 @@ import kotlin.math.PI import kotlin.math.cos import kotlin.math.sin -@KtorExperimentalAPI @ExperimentalCoroutinesApi fun main() { val server = Plotly.serve { diff --git a/examples/src/main/kotlin/unsupportedFeature.kt b/examples/src/main/kotlin/unsupportedFeature.kt index ae885b28..611c5a91 100644 --- a/examples/src/main/kotlin/unsupportedFeature.kt +++ b/examples/src/main/kotlin/unsupportedFeature.kt @@ -1,10 +1,12 @@ import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.configure import space.kscience.dataforge.meta.invoke -import space.kscience.dataforge.meta.set +import space.kscience.dataforge.values.ListValue import space.kscience.plotly.Plotly import space.kscience.plotly.makeFile import space.kscience.plotly.models.Trace import space.kscience.plotly.models.invoke +import space.kscience.plotly.set fun main() { @@ -17,7 +19,7 @@ fun main() { * We are applying it directly to configuration. * It is still observable in the same way as other properties but is not type safe. */ - set("text", x.map { "label for $it" }) + meta["text"] = x.map { "label for $it"} } val plot = Plotly.plot { @@ -26,14 +28,13 @@ fun main() { title = "Plot with labels" xaxis { title = "x axis name" - set( - "rangebreaks", - listOf( + configure { + "rangebreaks" putIndexed listOf( Meta { - "values" put listOf(2.0, 3.0) + "values" put ListValue(2.0, 3.0) } ) - ) + } } yaxis { title = "y axis name" } } diff --git a/plotlykt-core/api/plotlykt-core.api b/plotlykt-core/api/plotlykt-core.api index 7688f02c..28837165 100644 --- a/plotlykt-core/api/plotlykt-core.api +++ b/plotlykt-core/api/plotlykt-core.api @@ -2,6 +2,11 @@ public final class space/kscience/plotly/BootstrapHeadersKt { public static final fun getCdnBootstrap ()Lspace/kscience/plotly/PlotlyHtmlFragment; } +public final class space/kscience/plotly/DfExtKt { + public static final fun set (Lspace/kscience/dataforge/meta/MutableMeta;Ljava/lang/String;Ljava/lang/Object;)V + public static final fun set (Lspace/kscience/dataforge/meta/Scheme;Ljava/lang/String;Ljava/lang/Object;)V +} + public final class space/kscience/plotly/FileExportKt { public static final fun display (Lspace/kscience/plotly/Plotly;Lkotlin/jvm/functions/Function2;)V public static final fun export (Lspace/kscience/plotly/Plot;Ljava/nio/file/Path;Lspace/kscience/plotly/OrcaFormat;)V @@ -37,12 +42,13 @@ public final class space/kscience/plotly/OrcaFormat : java/lang/Enum { public final class space/kscience/plotly/Plot : space/kscience/dataforge/meta/Configurable, space/kscience/dataforge/meta/MetaRepr { public fun ()V - public fun (Lspace/kscience/dataforge/meta/ObservableMeta;)V - public synthetic fun (Lspace/kscience/dataforge/meta/ObservableMeta;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun (Lspace/kscience/dataforge/meta/ObservableMutableMeta;)V + public synthetic fun (Lspace/kscience/dataforge/meta/ObservableMutableMeta;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun addTrace (Lspace/kscience/plotly/models/Trace;)V - public fun getConfig ()Lspace/kscience/dataforge/meta/ObservableMeta; public final fun getData ()Ljava/util/List; public final fun getLayout ()Lspace/kscience/plotly/models/Layout; + public synthetic fun getMeta ()Lspace/kscience/dataforge/meta/MutableMeta; + public fun getMeta ()Lspace/kscience/dataforge/meta/ObservableMutableMeta; public fun toMeta ()Lspace/kscience/dataforge/meta/Meta; public final fun traces (Ljava/util/Collection;)V public final fun traces ([Lspace/kscience/plotly/models/Trace;)V @@ -496,6 +502,7 @@ public final class space/kscience/plotly/models/Calendar : java/lang/Enum { public final class space/kscience/plotly/models/CandleStick : space/kscience/plotly/models/Trace { public static final field Companion Lspace/kscience/plotly/models/CandleStick$Companion; public fun ()V + public final fun getCandleStickMeta ()Lspace/kscience/dataforge/values/Value; public final fun getClose ()Lspace/kscience/plotly/models/TraceValues; public final fun getDecreasing ()Lspace/kscience/plotly/models/CandleStickLine; public final fun getHigh ()Lspace/kscience/plotly/models/TraceValues; @@ -504,7 +511,6 @@ public final class space/kscience/plotly/models/CandleStick : space/kscience/plo public final fun getIncreasing ()Lspace/kscience/plotly/models/CandleStickLine; public final fun getLineWidth ()D public final fun getLow ()Lspace/kscience/plotly/models/TraceValues; - public final fun getMeta ()Lspace/kscience/dataforge/values/Value; public final fun getOpen ()Lspace/kscience/plotly/models/TraceValues; public final fun getWhiskerwidth ()D public final fun getXaxis ()Ljava/lang/String; @@ -512,11 +518,11 @@ public final class space/kscience/plotly/models/CandleStick : space/kscience/plo public final fun getXperiod0 ()Lspace/kscience/dataforge/values/Value; public final fun getXperiodalignment ()Lspace/kscience/plotly/models/XPeriodAlignment; public final fun getYaxis ()Ljava/lang/String; + public final fun setCandleStickMeta (Lspace/kscience/dataforge/values/Value;)V public final fun setDecreasing (Lspace/kscience/plotly/models/CandleStickLine;)V public final fun setIds (Ljava/util/List;)V public final fun setIncreasing (Lspace/kscience/plotly/models/CandleStickLine;)V public final fun setLineWidth (D)V - public final fun setMeta (Lspace/kscience/dataforge/values/Value;)V public final fun setWhiskerwidth (D)V public final fun setXaxis (Ljava/lang/String;)V public final fun setXperiod (Lspace/kscience/dataforge/values/Value;)V diff --git a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/Plot.kt b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/Plot.kt index c5e4fdd4..c71543cc 100644 --- a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/Plot.kt +++ b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/Plot.kt @@ -9,33 +9,25 @@ import space.kscience.plotly.models.Layout import space.kscience.plotly.models.Trace /** - * The main plot class. The changes to plot could be observed by attaching listener to root [config] property. + * The main plot class. The changes to plot could be observed by attaching listener to root [meta] property. */ @DFBuilder public class Plot( - override val config: ObservableMeta = ObservableMeta(), + override val meta: ObservableMutableMeta = MutableMeta(), ) : Configurable, MetaRepr { /** * Ordered list ot traces in the plot */ - public val data: List by config.list(Trace) + public val data: List by list(Trace) /** * Layout specification for th plot */ - public val layout: Layout by config.spec(Layout) + public val layout: Layout by meta.spec(Layout) public fun addTrace(trace: Trace) { - val traceRoot = trace.rootNode - if (traceRoot is ObservableMeta) { - config.append("data", traceRoot) - } else { - val traceConfig = ObservableMeta() - trace.rootNode?.let { traceConfig.update(it) } - trace.retarget(traceConfig) - config.append("data", traceConfig) - } + meta.append("data", trace.meta) } /** @@ -57,17 +49,17 @@ public class Plot( */ @UnstablePlotlyAPI internal fun removeTrace(index: Int) { - config.remove("data[$index]") + meta.remove("data[$index]") } - override fun toMeta(): Meta = config + override fun toMeta(): Meta = meta } private fun Plot.toJson(): JsonObject = buildJsonObject { - layout.rootNode?.let { put("layout", it.toJson()) } + put("layout", layout.meta.toJson()) put("data", buildJsonArray { data.forEach { traceData -> - traceData.rootNode?.let { add(it.toJson()) } + add(traceData.meta.toJson()) } }) } diff --git a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/Plotly.kt b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/Plotly.kt index b6a182f4..166b1624 100644 --- a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/Plotly.kt +++ b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/Plotly.kt @@ -1,10 +1,9 @@ package space.kscience.plotly import kotlinx.serialization.json.JsonArray -import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.buildJsonArray import space.kscience.dataforge.meta.* -import space.kscience.dataforge.names.toName +import space.kscience.dataforge.names.Name import kotlin.js.JsName /** @@ -20,15 +19,13 @@ public object Plotly { public inline fun plot(block: Plot.() -> Unit): Plot = Plot().apply(block) } -private fun Scheme.toJson(): JsonObject = rootNode?.toJson() ?: JsonObject(emptyMap()) - /** * Convert any type-safe configurator to json string */ -public fun Scheme.toJsonString(): String = toJson().toString() +public fun Scheme.toJsonString(): String = meta.toJson().toString() private fun List.toJson(): JsonArray = buildJsonArray { - forEach { add(it.toJson()) } + forEach { add(it.meta.toJson()) } } /** @@ -47,7 +44,7 @@ public class PlotlyConfig : Scheme() { public var showEditInChartStudio: Boolean? by boolean() public var plotlyServerURL: String? by string() public var responsive: Boolean? by boolean() - public var imageFormat: String? by string("toImageButtonOptions.format".toName()) + public var imageFormat: String? by string(Name.parse("toImageButtonOptions.format")) public fun withEditorButton() { showEditInChartStudio = true diff --git a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/dfExt.kt b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/dfExt.kt index cffc9a19..a17545ae 100644 --- a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/dfExt.kt +++ b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/dfExt.kt @@ -15,17 +15,17 @@ import kotlin.time.toDuration //extensions for DataForge -private fun MutableItemProvider.getIndexedProviders(name: Name): Map { - val parent = getItem(name.cutLast()).node ?: return emptyMap() +private fun MutableMeta.getIndexedProviders(name: Name): Map { + val parent = getMeta(name.cutLast()) ?: return emptyMap() return parent.items.keys.filter { it.body == name.lastOrNull()?.body }.map { it.index }.associate { index -> if (index == null) { - "" to getChild(name) + "" to getOrCreate(name) } else { - index to getChild(name.withIndex(index)) + index to getOrCreate(name.withIndex(index)) } } } @@ -34,56 +34,56 @@ private fun MutableItemProvider.getIndexedProviders(name: Name): Map MutableItemProvider.list( +internal fun Configurable.list( spec: Specification, key: Name? = null, ): ReadWriteProperty> = object : ReadWriteProperty> { override fun getValue(thisRef: Any?, property: KProperty<*>): List { val name = key ?: property.name.asName() - return getIndexedProviders(name).values.map { item -> + return meta.getIndexedProviders(name).values.map { item -> spec.write(item) } } override fun setValue(thisRef: Any?, property: KProperty<*>, value: List) { val name = key ?: property.name.asName() - setIndexed(name, value.mapNotNull { it.rootNode }) + meta.setIndexed(name, value.map { it.meta }) } } /** * List of values delegate */ -internal fun MutableItemProvider.listOfValues( +internal fun Scheme.listOfValues( key: Name? = null, ): ReadWriteProperty> = object : ReadWriteProperty> { override fun getValue(thisRef: Any?, property: KProperty<*>): List { val name = key ?: property.name.asName() - return getItem(name).value?.list ?: emptyList() + return meta[name]?.value?.list ?: emptyList() } override fun setValue(thisRef: Any?, property: KProperty<*>, value: List) { val name = key ?: property.name.asName() - set(name, value.asValue()) + meta[name] = value.asValue() } } /** * A safe [Double] range */ -internal fun MutableItemProvider.doubleInRange( +internal fun Scheme.doubleInRange( range: ClosedFloatingPointRange, key: Name? = null, ): ReadWriteProperty = object : ReadWriteProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): Double { val name = key ?: property.name.asName() - return this@doubleInRange[name].double ?: Double.NaN + return meta[name].double ?: Double.NaN } override fun setValue(thisRef: Any?, property: KProperty<*>, value: Double) { val name = key ?: property.name.asName() if (value in range) { - this@doubleInRange[name] = value + meta[name] = value } else { error("$value not in range $range") } @@ -93,19 +93,19 @@ internal fun MutableItemProvider.doubleInRange( /** * A safe [Double] ray */ -internal fun MutableItemProvider.doubleGreaterThan( +internal fun Scheme.doubleGreaterThan( minValue: Double, key: Name? = null, ): ReadWriteProperty = object : ReadWriteProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): Double { val name = key ?: property.name.asName() - return this@doubleGreaterThan[name].double ?: Double.NaN + return meta[name].double ?: Double.NaN } override fun setValue(thisRef: Any?, property: KProperty<*>, value: Double) { val name = key ?: property.name.asName() if (value >= minValue) { - this@doubleGreaterThan[name] = value + meta[name] = value } else { error("$value less than $minValue") } @@ -116,19 +116,19 @@ internal fun MutableItemProvider.doubleGreaterThan( /** * A safe [Int] ray */ -internal fun MutableItemProvider.intGreaterThan( +internal fun Scheme.intGreaterThan( minValue: Int, key: Name? = null, ): ReadWriteProperty = object : ReadWriteProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): Int { val name = key ?: property.name.asName() - return this@intGreaterThan[name].int ?: minValue + return meta[name].int ?: minValue } override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) { val name = key ?: property.name.asName() if (value >= minValue) { - this@intGreaterThan[name] = value + meta[name] = value } else { error("$value less than $minValue") } @@ -138,19 +138,19 @@ internal fun MutableItemProvider.intGreaterThan( /** * A safe [Int] range */ -internal fun Configurable.intInRange( +internal fun Scheme.intInRange( range: ClosedRange, key: Name? = null, ): ReadWriteProperty = object : ReadWriteProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): Int { val name = key ?: property.name.asName() - return config[name].int ?: 0 + return meta[name].int ?: 0 } override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) { val name = key ?: property.name.asName() if (value in range) { - config[name] = value + meta[name] = value } else { error("$value not in range $range") } @@ -160,19 +160,19 @@ internal fun Configurable.intInRange( /** * A safe [Number] ray */ -internal fun MutableItemProvider.numberGreaterThan( +internal fun Scheme.numberGreaterThan( minValue: Number, key: Name? = null, ): ReadWriteProperty = object : ReadWriteProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): Number { val name = key ?: property.name.asName() - return this@numberGreaterThan[name].number ?: minValue + return meta[name].number ?: minValue } override fun setValue(thisRef: Any?, property: KProperty<*>, value: Number) { val name = key ?: property.name.asName() if (value.toDouble() >= minValue.toDouble()) { - this@numberGreaterThan[name] = value + meta[name] = value } else { error("$value less than $minValue") } @@ -182,19 +182,19 @@ internal fun MutableItemProvider.numberGreaterThan( /** * A safe [Number] range */ -internal fun MutableItemProvider.numberInRange( +internal fun Scheme.numberInRange( range: ClosedRange, key: Name? = null, ): ReadWriteProperty = object : ReadWriteProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): Number { val name = key ?: property.name.asName() - return this@numberInRange[name].int ?: 0 + return meta[name].int ?: 0 } override fun setValue(thisRef: Any?, property: KProperty<*>, value: Number) { val name = key ?: property.name.asName() if (value.toDouble() in range) { - this@numberInRange[name] = value + meta[name] = value } else { error("$value not in range $range") } @@ -202,30 +202,53 @@ internal fun MutableItemProvider.numberInRange( } @OptIn(DFExperimental::class) -internal fun MutableItemProvider.duration( +internal fun Scheme.duration( default: Duration? = null, key: Name? = null, ): ReadWriteProperty = object : ReadWriteProperty { override fun getValue(thisRef: Any?, property: KProperty<*>): Duration? { val name = key ?: property.name.asName() - return when (val item = getItem(name)) { - null -> default - is MetaItemValue -> Duration.milliseconds(item.value.long) - is MetaItemNode -> { - val value = item.node["value"].long ?: error("Duration value is not defined") - val unit = item.node["unit"].enum() ?: DurationUnit.MILLISECONDS - value.toDuration(unit) - } - } + val value = meta[name]?.value + val units = meta[name + "unit"]?.enum() ?: DurationUnit.MILLISECONDS + return value?.long?.toDuration(units) ?: default } override fun setValue(thisRef: Any?, property: KProperty<*>, value: Duration?) { val name = key ?: property.name.asName() if (value == null) { - remove(name) + meta.remove(name) } else { - set(name + "value", value.toDouble(DurationUnit.MILLISECONDS)) - set(name + "unit", DurationUnit.MILLISECONDS) + meta.value = value.toDouble(DurationUnit.MILLISECONDS).asValue() + meta[name + "unit"] = DurationUnit.MILLISECONDS.name } } +} + +/** + * Infer type of value and set + */ +public operator fun MutableMeta.set(name: String, value: Any?) { + when (value) { + null -> this[name] = null + is Meta -> this[name] = value + is Value -> set(Name.parse(name), value) + is String -> this[name] = value + is Number -> this[name] = value + is Boolean -> this[name] = value + is Iterable<*> -> { + if (value.all { it is Meta }) { + this.setIndexed(Name.parse(name), value.map { it as Meta }) + } else { + this[name] = value.map { Value.of(it) }.asValue() + } + } + } +} + +/** + * A patch to cover API no longer existing in DataForge + */ +@Deprecated("Use specialized meta methods instead") +public operator fun Scheme.set(name: String, value: Any?) { + meta[name] = value } \ No newline at end of file diff --git a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/Axis.kt b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/Axis.kt index d3354223..8b086b44 100644 --- a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/Axis.kt +++ b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/Axis.kt @@ -42,9 +42,9 @@ public class Axis : Scheme() { * Sets the title of this axis. */ public var title: String? - get() = this["title.text"].string ?: this["title"].string + get() = meta["title.text"].string ?: meta["title"].string set(value) { - this["title"] = value + meta["title"] = value?.asValue() } /** @@ -134,16 +134,16 @@ public class Axis : Scheme() { */ @Deprecated("Use range() instead") public var range: ClosedFloatingPointRange? - get() = this["range"]?.value?.doubleArray?.let { it[0]..it[1] } + get() = meta["range"]?.value?.doubleArray?.let { it[0]..it[1] } set(value) { - this["range"] = value?.let { ListValue(listOf(value.start.asValue(), value.endInclusive.asValue())) } + meta["range"] = value?.let { ListValue(listOf(value.start.asValue(), value.endInclusive.asValue())) } } /** * Set the range using arbitrary values */ public fun range(from: Value, to: Value) { - this["range"] = ListValue(listOf(from, to)) + meta["range"] = ListValue(listOf(from, to)) } /** @@ -263,7 +263,7 @@ public class Axis : Scheme() { public var position: Number by numberInRange(0.0..1.0) public fun title(block: Title.() -> Unit) { - val spec = Title.write(getChild("title")) + val spec = Title.write(meta.getOrCreate("title")) spec.apply(block) } diff --git a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/CandleStick.kt b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/CandleStick.kt index 747424ef..8316a1df 100644 --- a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/CandleStick.kt +++ b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/CandleStick.kt @@ -1,7 +1,8 @@ package space.kscience.plotly.models import space.kscience.dataforge.meta.* -import space.kscience.dataforge.names.toName +import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.asName import space.kscience.dataforge.values.Value import space.kscience.dataforge.values.asValue import space.kscience.plotly.doubleInRange @@ -15,8 +16,8 @@ public enum class XPeriodAlignment{ public class CandleStickLine: Scheme(){ public val fillcolor: Color by color() - public val lineColor: Color by color("line.color".toName()) - public var lineWidth: Double by double(2.0, key = "line.width".toName()) + public val lineColor: Color by color(Name.parse("line.color")) + public var lineWidth: Double by double(2.0, key = Name.parse("line.width")) public companion object: SchemeSpec(::CandleStickLine) } @@ -52,7 +53,7 @@ public class CandleStick : Trace() { public val hovertext: TraceValues by axis - public var meta: Value? by value() + public var candleStickMeta: Value? by value("meta".asName()) /** * Sets a reference between this trace's x coordinates and a 2D cartesian x axis. If "x" (the default value), @@ -68,7 +69,7 @@ public class CandleStick : Trace() { public var yaxis: String by string("y") - public var lineWidth: Double by double(2.0, "line.width".toName()) + public var lineWidth: Double by double(2.0, Name.parse("line.width")) public var increasing: CandleStickLine by spec(CandleStickLine) public var decreasing: CandleStickLine by spec(CandleStickLine) diff --git a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/Layout.kt b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/Layout.kt index 30bed413..83ec479d 100644 --- a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/Layout.kt +++ b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/Layout.kt @@ -3,6 +3,7 @@ package space.kscience.plotly.models import space.kscience.dataforge.meta.* +import space.kscience.dataforge.values.asValue import space.kscience.plotly.list import space.kscience.plotly.numberGreaterThan import space.kscience.plotly.numberInRange @@ -111,9 +112,9 @@ public class Layout : Scheme() { * Sets the plot's title. */ public var title: String? - get() = this["title.text"].string ?: this["title"].string + get() = meta["title.text"].string ?: meta["title"].string set(value) { - this["title.text"] = value + meta["title.text"] = value?.asValue() } public var xaxis: Axis by spec(Axis) @@ -272,7 +273,7 @@ public class Layout : Scheme() { } public fun title(block: Title.() -> Unit) { - Title.write(getChild("title")).apply(block) + Title.write(meta.getOrCreate("title")).apply(block) } //TODO moe title to parameter block @@ -285,7 +286,7 @@ public class Layout : Scheme() { } public fun annotation(an: Text) { - append("annotations", an) + meta.append("annotations", an.meta) } public fun annotation(anBuilder: Text.() -> Unit) { @@ -293,7 +294,7 @@ public class Layout : Scheme() { } public fun figure(sh: Shape) { - append("shapes", sh) + meta.append("shapes", sh.meta) } public fun figure(shBuilder: Shape.() -> Unit) { diff --git a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/Trace.kt b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/Trace.kt index d89f4af2..7206ada4 100644 --- a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/Trace.kt +++ b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/Trace.kt @@ -3,8 +3,8 @@ package space.kscience.plotly.models import space.kscience.dataforge.meta.* +import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.asName -import space.kscience.dataforge.names.toName import space.kscience.dataforge.values.Value import space.kscience.dataforge.values.asValue import space.kscience.plotly.* @@ -696,7 +696,7 @@ public class Hoverlabel : Scheme() { * A base class for Plotly traces */ public open class Trace : Scheme() { - public fun axis(axisName: String): TraceValues = TraceValues(this, axisName.toName()) + public fun axis(axisName: String): TraceValues = TraceValues(this, Name.parse(axisName)) public val axis: ReadOnlyProperty = ReadOnlyProperty { thisRef, property -> TraceValues(thisRef, property.name.asName()) diff --git a/plotlykt-core/src/jsMain/kotlin/space/kscience/plotly/plotlyElement.kt b/plotlykt-core/src/jsMain/kotlin/space/kscience/plotly/plotlyElement.kt index 8618695c..87495bde 100644 --- a/plotlykt-core/src/jsMain/kotlin/space/kscience/plotly/plotlyElement.kt +++ b/plotlykt-core/src/jsMain/kotlin/space/kscience/plotly/plotlyElement.kt @@ -9,15 +9,12 @@ import org.w3c.dom.Element import org.w3c.dom.HTMLElement import space.kscience.dataforge.meta.MetaSerializer import space.kscience.dataforge.meta.Scheme -import space.kscience.dataforge.meta.rootNode import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.firstOrNull import space.kscience.dataforge.names.startsWith @OptIn(ExperimentalSerializationApi::class) -private fun Scheme.toDynamic(): dynamic = rootNode?.let { - Json.encodeToDynamic(MetaSerializer, it) -} ?: js("{}") +private fun Scheme.toDynamic(): dynamic = Json.encodeToDynamic(MetaSerializer, meta) private fun List.toDynamic(): Array = map { it.toDynamic() }.toTypedArray() @@ -38,9 +35,9 @@ public fun Element.plot(plot: Plot, plotlyConfig: PlotlyConfig = PlotlyConfig()) PlotlyJs.react(this, tracesData, layout, plotlyConfig.toDynamic()) - plot.config.onChange(this) { name, _, _ -> + plot.meta.onChange(this) { name -> if (name.startsWith(plot::layout.name.asName())) { - PlotlyJs.relayout(this, plot.layout.toDynamic()) + PlotlyJs.relayout(this@plot, plot.layout.toDynamic()) } else if (name.firstOrNull()?.body == "data") { val traceName = name.firstOrNull()!! val traceIndex = traceName.index?.toInt() ?: 0 @@ -61,7 +58,7 @@ public fun Element.plot(plot: Plot, plotlyConfig: PlotlyConfig = PlotlyConfig()) if (traceData.text != null) { traceData.text = arrayOf(traceData.text) } - PlotlyJs.restyle(this, traceData, arrayOf(traceIndex)) + PlotlyJs.restyle(this@plot, traceData, arrayOf(traceIndex)) } } } @@ -70,6 +67,9 @@ public inline fun Element.plot(plotlyConfig: PlotlyConfig = PlotlyConfig(), plot plot(Plot().apply(plotBuilder), plotlyConfig) } -public inline fun TagConsumer.plot(plotlyConfig: PlotlyConfig = PlotlyConfig(), plotBuilder: Plot.() -> Unit) { +public inline fun TagConsumer.plot( + plotlyConfig: PlotlyConfig = PlotlyConfig(), + plotBuilder: Plot.() -> Unit +) { div { }.plot(plotlyConfig, plotBuilder) } diff --git a/plotlykt-core/src/jvmTest/kotlin/space/kscience/plotly/PlotSerializationTest.kt b/plotlykt-core/src/jvmTest/kotlin/space/kscience/plotly/PlotSerializationTest.kt index 5df41611..b689d28e 100644 --- a/plotlykt-core/src/jvmTest/kotlin/space/kscience/plotly/PlotSerializationTest.kt +++ b/plotlykt-core/src/jvmTest/kotlin/space/kscience/plotly/PlotSerializationTest.kt @@ -1,8 +1,9 @@ package space.kscience.plotly import org.junit.jupiter.api.Test -import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.MutableMeta import space.kscience.dataforge.meta.asObservable +import space.kscience.dataforge.values.ListValue import space.kscience.plotly.models.TraceType import kotlin.test.assertEquals @@ -10,10 +11,10 @@ import kotlin.test.assertEquals class PlotSerializationTest { @Test fun deserialization() { - val meta = Meta { + val meta = MutableMeta { "data" put { - "x" put listOf(1, 2, 3) - "y" put listOf(5, 6, 7) + "x" put ListValue(1, 2, 3) + "y" put ListValue(5, 6, 7) "type" put "scatter" } } diff --git a/plotlykt-server/api/plotlykt-server.api b/plotlykt-server/api/plotlykt-server.api index 3461eb65..d94baf29 100644 --- a/plotlykt-server/api/plotlykt-server.api +++ b/plotlykt-server/api/plotlykt-server.api @@ -1,6 +1,6 @@ public final class space/kscience/plotly/server/MetaChangeCollector { public fun ()V - public final fun collect (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/meta/TypedMetaItem;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public final fun collect (Lspace/kscience/dataforge/names/Name;Lspace/kscience/dataforge/meta/Meta;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun read (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -12,8 +12,9 @@ public final class space/kscience/plotly/server/PlotlyServer : space/kscience/da public static final field Companion Lspace/kscience/plotly/server/PlotlyServer$Companion; public static final field DEFAULT_PAGE Ljava/lang/String; public final fun getApplication ()Lio/ktor/application/Application; - public fun getConfig ()Lspace/kscience/dataforge/meta/ObservableMeta; public final fun getEmbedData ()Z + public synthetic fun getMeta ()Lspace/kscience/dataforge/meta/MutableMeta; + public fun getMeta ()Lspace/kscience/dataforge/meta/ObservableMutableMeta; public final fun getUpdateInterval ()I public final fun getUpdateMode ()Lspace/kscience/plotly/server/PlotlyUpdateMode; public final fun header (Lkotlin/jvm/functions/Function1;)V diff --git a/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/MetaChangeCollector.kt b/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/MetaChangeCollector.kt index e295432b..e0d6e3f2 100644 --- a/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/MetaChangeCollector.kt +++ b/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/MetaChangeCollector.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import space.kscience.dataforge.meta.* import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.asName import space.kscience.plotly.Plot @@ -19,11 +20,11 @@ import space.kscience.plotly.Plot */ public class MetaChangeCollector { private val mutex = Mutex() - private var state = MetaBuilder() + private var state = MutableMeta() - public suspend fun collect(name: Name, newItem: MetaItem?) { + public suspend fun collect(name: Name, newItem: Meta?) { mutex.withLock { - state[name] = newItem + state.setMeta(name, newItem) } } @@ -31,7 +32,7 @@ public class MetaChangeCollector { return if (!state.isEmpty()) { mutex.withLock { state.seal().also { - state = MetaBuilder() + state = MutableMeta() } } } else { @@ -41,9 +42,9 @@ public class MetaChangeCollector { } private fun ObservableMeta.collectChanges(scope: CoroutineScope): MetaChangeCollector = MetaChangeCollector().apply { - onChange(this) { name, _, newItem -> + onChange(this) { name -> scope.launch { - collect(name, newItem) + collect(name, get(name)) } } } @@ -65,11 +66,9 @@ public fun Plot.collectUpdates( plotId: String, scope: CoroutineScope, updateInterval: Int, -): Flow = config.flowChanges(scope, updateInterval).transform { change -> - change["layout"].node?.let { emit(Update.Layout(plotId, it)) } - change.getIndexed("data").forEach { (index, metaItem) -> - if (metaItem is MetaItemNode) { - emit(Update.Trace(plotId, index?.toInt() ?: 0, metaItem.node)) - } +): Flow = meta.flowChanges(scope, updateInterval).transform { change -> + change["layout"]?.let{ emit(Update.Layout(plotId, it))} + change.getIndexed("data".asName()).forEach { (index, metaItem) -> + emit(Update.Trace(plotId, index?.toInt() ?: 0, metaItem)) } } \ No newline at end of file diff --git a/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/PlotlyServer.kt b/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/PlotlyServer.kt index 43c05a86..942e392d 100644 --- a/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/PlotlyServer.kt +++ b/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/PlotlyServer.kt @@ -21,16 +21,15 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.collect import kotlinx.html.* -import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonObject import space.kscience.dataforge.meta.* import space.kscience.dataforge.names.Name -import space.kscience.dataforge.names.toName import space.kscience.plotly.* import space.kscience.plotly.server.PlotlyServer.Companion.DEFAULT_PAGE import java.awt.Desktop import java.net.URI import kotlin.collections.set +import kotlin.coroutines.CoroutineContext public enum class PlotlyUpdateMode { NONE, @@ -107,11 +106,14 @@ internal class ServerPlotlyRenderer( public class PlotlyServer internal constructor( private val routing: Routing, private val rootRoute: String, -) : Configurable { - override val config: ObservableMeta = ObservableMeta() - public var updateMode: PlotlyUpdateMode by config.enum(PlotlyUpdateMode.NONE, key = UPDATE_MODE_KEY) - public var updateInterval: Int by config.int(300, key = UPDATE_INTERVAL_KEY) - public var embedData: Boolean by config.boolean(false) +) : Configurable, CoroutineScope { + + override val coroutineContext: CoroutineContext get() = routing.application.coroutineContext + + override val meta: ObservableMutableMeta = MutableMeta() + public var updateMode: PlotlyUpdateMode by meta.enum(PlotlyUpdateMode.NONE, key = UPDATE_MODE_KEY) + public var updateInterval: Int by meta.int(300, key = UPDATE_INTERVAL_KEY) + public var embedData: Boolean by meta.boolean(false) internal val root by lazy { routing.createRouteFromPath(rootRoute) } @@ -134,7 +136,7 @@ public class PlotlyServer internal constructor( val plot = plots[plotId] ?: error("Plot with id='$plotId' not registered") try { - plot.collectUpdates(plotId, this, updateInterval).collect { update -> + plot.collectUpdates(plotId, this, updateInterval).collect { update: Update -> val json = update.toJson() outgoing.send(Frame.Text(JsonObject(json).toString())) } @@ -235,8 +237,8 @@ public class PlotlyServer internal constructor( public companion object { public const val DEFAULT_PAGE: String = "/" - public val UPDATE_MODE_KEY: Name = "update.mode".toName() - public val UPDATE_INTERVAL_KEY: Name = "update.interval".toName() + public val UPDATE_MODE_KEY: Name = Name.parse("update.mode") + public val UPDATE_INTERVAL_KEY: Name = Name.parse("update.interval") } } diff --git a/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/PlotlyServerIntegration.kt b/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/PlotlyServerIntegration.kt index 76ac4c4b..0f75d358 100644 --- a/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/PlotlyServerIntegration.kt +++ b/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/PlotlyServerIntegration.kt @@ -91,9 +91,9 @@ internal class PlotlyServerIntegration : JupyterIntegration() { } } } - config.onChange(this) { name, oldItem, newItem -> - if (name.toString() != PlotlyServerConfiguration::legacyMode.name && oldItem != newItem) { - logger.info("Plotly server config parameter $name changed to $newItem") + config.meta.onChange(this) { name -> + if (name.toString() != PlotlyServerConfiguration::legacyMode.name ) { + logger.info("Plotly server config parameter $name changed") doStart() } } diff --git a/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/Update.kt b/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/Update.kt index 17b06c56..63cc466f 100644 --- a/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/Update.kt +++ b/plotlykt-server/src/main/kotlin/space/kscience/plotly/server/Update.kt @@ -8,8 +8,9 @@ import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.toJson -private val coordinateNames = - listOf("x", "y", "z", "text", "close", "high", "low", "open") +private val coordinateNames = listOf( + "x", "y", "z", "text", "close", "high", "low", "open" +) /** * An update message for both data and layout @@ -23,7 +24,8 @@ public sealed class Update(public val id: String) { put("contentType", "trace") put("trace", trace) //patch json to adhere to plotly array in array specification - val contentJson = content.toJson() + val contentJson: JsonObject = content.toJson() as? JsonObject + ?: buildJsonObject { put("@value", content.toJson()) } val patchedJson = contentJson + coordinateNames.associateWith { contentJson[it] } .filter { it.value != null } .mapValues { JsonArray(listOf(it.value!!)) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 6b04521c..dac9e600 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,9 +1,10 @@ rootProject.name = "plotly-kt" pluginManagement { - val toolsVersion = "0.10.0" + val toolsVersion = "0.10.2" repositories { + mavenLocal() maven("https://repo.kotlin.link") mavenCentral() gradlePluginPortal() From 0927c59fb1441f689e53ded4d5c4b1a95c2a590d Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 1 Aug 2021 20:37:08 +0300 Subject: [PATCH 3/4] Migration to DF 0.5 --- build.gradle.kts | 4 ++-- .../kscience/plotly/fx/PlotlyFXController.kt | 2 ++ examples/js-demo/build.gradle.kts | 2 +- .../space/kscience/plotly/jsdemo/main.kt | 17 ++++++++--------- .../src/main/kotlin/complexDynamicServer.kt | 3 +-- examples/src/main/kotlin/dynamicServer.kt | 6 ++---- examples/src/main/kotlin/simpleServer.kt | 18 +++++++++--------- .../kotlin/space/kscience/plotly/Plot.kt | 2 +- .../kotlin/space/kscience/plotly/dfExt.kt | 15 +++++++++++++++ .../space/kscience/plotly/models/Layout.kt | 5 +++-- plotlykt-server/api/plotlykt-server.api | 3 ++- 11 files changed, 46 insertions(+), 31 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 9d26ff80..dd6dd253 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,11 +2,11 @@ plugins { id("ru.mipt.npm.gradle.project") } -val dataforgeVersion by extra("0.5.0-dev-5") +val dataforgeVersion by extra("0.5.0-dev-7") allprojects { group = "space.kscience" - version = "0.5.0" + version = "0.5.0-dev-1" repositories{ mavenLocal() } diff --git a/examples/fx-demo/src/main/kotlin/space/kscience/plotly/fx/PlotlyFXController.kt b/examples/fx-demo/src/main/kotlin/space/kscience/plotly/fx/PlotlyFXController.kt index 8b3d8014..c0dc21be 100644 --- a/examples/fx-demo/src/main/kotlin/space/kscience/plotly/fx/PlotlyFXController.kt +++ b/examples/fx-demo/src/main/kotlin/space/kscience/plotly/fx/PlotlyFXController.kt @@ -3,6 +3,7 @@ package space.kscience.plotly.fx import io.ktor.server.engine.ApplicationEngine import javafx.beans.property.SimpleIntegerProperty import javafx.beans.property.SimpleStringProperty +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch @@ -15,6 +16,7 @@ class PlotlyFXController : Controller() { private var server: ApplicationEngine? = null + @OptIn(DelicateCoroutinesApi::class) fun startServer() { GlobalScope.launch(Dispatchers.Default) { log.info("Starting server") diff --git a/examples/js-demo/build.gradle.kts b/examples/js-demo/build.gradle.kts index dec1f7f3..01b301f9 100644 --- a/examples/js-demo/build.gradle.kts +++ b/examples/js-demo/build.gradle.kts @@ -16,5 +16,5 @@ kotlin { dependencies { implementation(project(":plotlykt-core")) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.5.0") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1") } \ No newline at end of file diff --git a/examples/js-demo/src/main/kotlin/space/kscience/plotly/jsdemo/main.kt b/examples/js-demo/src/main/kotlin/space/kscience/plotly/jsdemo/main.kt index 0061f0b8..9a1a6f2c 100644 --- a/examples/js-demo/src/main/kotlin/space/kscience/plotly/jsdemo/main.kt +++ b/examples/js-demo/src/main/kotlin/space/kscience/plotly/jsdemo/main.kt @@ -2,10 +2,7 @@ package space.kscience.plotly.jsdemo import kotlinx.browser.document -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import kotlinx.html.TagConsumer import kotlinx.html.dom.append import kotlinx.html.h1 @@ -33,6 +30,7 @@ private fun withCanvas(block: TagConsumer.() -> Unit) = onDomLoaded } +@OptIn(DelicateCoroutinesApi::class) fun main(): Unit = withCanvas { div { style = "height:50%; width=100%;" @@ -43,7 +41,7 @@ fun main(): Unit = withCanvas { name = "Random data" GlobalScope.launch { while (isActive) { - x.numbers = List(500){rnd.nextDouble()} + x.numbers = List(500) { rnd.nextDouble() } delay(300) } } @@ -93,10 +91,11 @@ fun main(): Unit = withCanvas { y(10, 15, 13, 17) mode = ScatterMode.lines type = TraceType.scatter - marker.apply { - GlobalScope.launch { - while (isActive) { - delay(500) + + GlobalScope.launch { + while (isActive) { + delay(500) + marker { if (Random.nextBoolean()) { color("magenta") } else { diff --git a/examples/src/main/kotlin/complexDynamicServer.kt b/examples/src/main/kotlin/complexDynamicServer.kt index c22249e9..3a7dd352 100644 --- a/examples/src/main/kotlin/complexDynamicServer.kt +++ b/examples/src/main/kotlin/complexDynamicServer.kt @@ -1,4 +1,3 @@ -import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collect @@ -119,7 +118,7 @@ fun main() { trace { name = "non-synchronized" val flow: Flow>> = sinCosFlow.windowed(30) - GlobalScope.launch { + launch { updateXYFrom(flow) } } diff --git a/examples/src/main/kotlin/dynamicServer.kt b/examples/src/main/kotlin/dynamicServer.kt index f793a14f..11a75e37 100644 --- a/examples/src/main/kotlin/dynamicServer.kt +++ b/examples/src/main/kotlin/dynamicServer.kt @@ -1,7 +1,4 @@ -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch +import kotlinx.coroutines.* import kotlinx.html.a import kotlinx.html.h1 import space.kscience.dataforge.meta.invoke @@ -18,6 +15,7 @@ import kotlin.math.cos import kotlin.math.sin +@OptIn(DelicateCoroutinesApi::class) fun main() { val freq = 1.0 / 1000 diff --git a/examples/src/main/kotlin/simpleServer.kt b/examples/src/main/kotlin/simpleServer.kt index fc3f3a03..1ed21ca1 100644 --- a/examples/src/main/kotlin/simpleServer.kt +++ b/examples/src/main/kotlin/simpleServer.kt @@ -26,7 +26,14 @@ fun main() { val trace1 = Trace(x, y1) { name = "sin" } val trace2 = Trace(x, y2) { name = "cos" } - lateinit var plot1: Plot + val plot1: Plot = Plotly.plot{ + traces(trace1, trace2) + layout { + title = "First graph, row: 1, size: 8/12" + xaxis { title = "x axis name" } + yaxis { title = "y axis name" } + } + } //root level plots go to default page page { @@ -36,14 +43,7 @@ fun main() { style = "display: flex; align-items: stretch; " div { style = "width: 64%;" - plot1 = plot { - traces(trace1, trace2) - layout { - title = "First graph, row: 1, size: 8/12" - xaxis { title = "x axis name" } - yaxis { title = "y axis name" } - } - } + plot(plot1) } div { style = "width: 32%;" diff --git a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/Plot.kt b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/Plot.kt index c71543cc..ad17581e 100644 --- a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/Plot.kt +++ b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/Plot.kt @@ -27,7 +27,7 @@ public class Plot( public val layout: Layout by meta.spec(Layout) public fun addTrace(trace: Trace) { - meta.append("data", trace.meta) + meta.appendAndAttach("data", trace.meta) } /** diff --git a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/dfExt.kt b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/dfExt.kt index a17545ae..45556c33 100644 --- a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/dfExt.kt +++ b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/dfExt.kt @@ -251,4 +251,19 @@ public operator fun MutableMeta.set(name: String, value: Any?) { @Deprecated("Use specialized meta methods instead") public operator fun Scheme.set(name: String, value: Any?) { meta[name] = value +} + +/** + * Append the observable note to same-name-siblings and observe its changes. + */ +internal fun ObservableMutableMeta.appendAndAttach(key: String, meta: ObservableMutableMeta) { + val name = Name.parse(key) + require(!name.isEmpty()) { "Name could not be empty for append operation" } + val newIndex = name.lastOrNull()!!.index + if (newIndex != null) { + attach(name, meta) + } else { + val index = (getIndexed(name).keys.mapNotNull { it?.toIntOrNull() }.maxOrNull() ?: -1) + 1 + attach(name.withIndex(index.toString()), meta) + } } \ No newline at end of file diff --git a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/Layout.kt b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/Layout.kt index 83ec479d..f1bf46b2 100644 --- a/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/Layout.kt +++ b/plotlykt-core/src/commonMain/kotlin/space/kscience/plotly/models/Layout.kt @@ -4,6 +4,7 @@ package space.kscience.plotly.models import space.kscience.dataforge.meta.* import space.kscience.dataforge.values.asValue +import space.kscience.plotly.appendAndAttach import space.kscience.plotly.list import space.kscience.plotly.numberGreaterThan import space.kscience.plotly.numberInRange @@ -286,7 +287,7 @@ public class Layout : Scheme() { } public fun annotation(an: Text) { - meta.append("annotations", an.meta) + meta.appendAndAttach("annotations", an.meta) } public fun annotation(anBuilder: Text.() -> Unit) { @@ -294,7 +295,7 @@ public class Layout : Scheme() { } public fun figure(sh: Shape) { - meta.append("shapes", sh.meta) + meta.appendAndAttach("shapes", sh.meta) } public fun figure(shBuilder: Shape.() -> Unit) { diff --git a/plotlykt-server/api/plotlykt-server.api b/plotlykt-server/api/plotlykt-server.api index d94baf29..ddfe0d57 100644 --- a/plotlykt-server/api/plotlykt-server.api +++ b/plotlykt-server/api/plotlykt-server.api @@ -8,10 +8,11 @@ public final class space/kscience/plotly/server/MetaChangeCollectorKt { public static final fun collectUpdates (Lspace/kscience/plotly/Plot;Ljava/lang/String;Lkotlinx/coroutines/CoroutineScope;I)Lkotlinx/coroutines/flow/Flow; } -public final class space/kscience/plotly/server/PlotlyServer : space/kscience/dataforge/meta/Configurable { +public final class space/kscience/plotly/server/PlotlyServer : kotlinx/coroutines/CoroutineScope, space/kscience/dataforge/meta/Configurable { public static final field Companion Lspace/kscience/plotly/server/PlotlyServer$Companion; public static final field DEFAULT_PAGE Ljava/lang/String; public final fun getApplication ()Lio/ktor/application/Application; + public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public final fun getEmbedData ()Z public synthetic fun getMeta ()Lspace/kscience/dataforge/meta/MutableMeta; public fun getMeta ()Lspace/kscience/dataforge/meta/ObservableMutableMeta; From afecd3a85bf88f0a354078a5f41eb70119f04df9 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Fri, 13 Aug 2021 21:30:47 +0300 Subject: [PATCH 4/4] Migration to DF 0.5 --- CHANGELOG.md | 1 + README.md | 8 ++++---- build.gradle.kts | 7 ++----- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 583d5fab..e9bce802 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ### Changed +- Switch to DataForge 0.5 ### Deprecated diff --git a/README.md b/README.md index c48dc986..8937a3c5 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Dev builds and intermediate artifacts are available via `https://repo.kotlin.link` maven repository. ## Compatibility note -The current `0.4.4` version of the library is compatible with kotlin 1.4 with JS-IR and kotlinx-serialization 1.1.0. The JVM part requires JVM 11 to run. +The current `0.5.0` version of the library is compatible with kotlin 1.4 with JS-IR and kotlinx-serialization 1.1.0. The JVM part requires JVM 11 to run. # TL;DR See [examples](./examples/src/main/kotlin). @@ -55,8 +55,8 @@ The examples of the notebooks are shown in [notebooks](./examples/notebooks) dir ```kotlin @file:Repository("https://repo.kotlin.link") -@file:DependsOn("space.kscience:plotlykt-jupyter:0.4.4") -//@file:DependsOn("space.kscience:plotlykt-server:0.4.4") // Use this one for sever integration. +@file:DependsOn("space.kscience:plotlykt-jupyter:0.5.0") +//@file:DependsOn("space.kscience:plotlykt-server:0.5.0") // Use this one for sever integration. ``` The module `plotly` allows rendering static plots in Jupyter. Jupyter lab is currently supported. Jupyter notebook (classic) is able to render only `PlotlyPage` objects, so one must convert plots to pages to be able to use notebook (see [demo notebook](./notebooks/plotlykt-demo-classic.ipynb)). @@ -92,7 +92,7 @@ repositories { } dependencies { - implementation("space.kscience:plotlykt-server:0.4.4") + implementation("space.kscience:plotlykt-server:0.5.0") } ``` diff --git a/build.gradle.kts b/build.gradle.kts index dd6dd253..b739ade4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,14 +2,11 @@ plugins { id("ru.mipt.npm.gradle.project") } -val dataforgeVersion by extra("0.5.0-dev-7") +val dataforgeVersion by extra("0.5.0") allprojects { group = "space.kscience" - version = "0.5.0-dev-1" - repositories{ - mavenLocal() - } + version = "0.5.0" } apiValidation {