Fix canvas update

This commit is contained in:
Alexander Nozik 2021-06-05 22:31:16 +03:00
parent fb3ff4d6fc
commit 359eb05f83
32 changed files with 136 additions and 396 deletions

View File

@ -20,7 +20,6 @@ kotlin {
useCommonJs()
browser {
commonWebpackConfig {
sourceMaps = false
cssSupport.enabled = false
}
}

View File

@ -18,7 +18,7 @@ import space.kscience.visionforge.html.HtmlVisionFragment
import space.kscience.visionforge.html.Page
import space.kscience.visionforge.html.embedVisionFragment
import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.plotly.toVision
import space.kscience.visionforge.plotly.asVision
import space.kscience.visionforge.solid.Solids
import space.kscience.visionforge.visionManager
@ -80,7 +80,7 @@ internal class VisionForgePlayGroundForJupyter : JupyterIntegration() {
render<Plot> { plot ->
val fragment = HtmlVisionFragment {
vision(plot.toVision())
vision(plot.asVision())
}
HTML(produceHtmlVisionString(fragment))

View File

@ -3,11 +3,13 @@ package ru.mipt.npm.muon.monitor
import ru.mipt.npm.muon.monitor.Monitor.CENTRAL_LAYER_Z
import ru.mipt.npm.muon.monitor.Monitor.LOWER_LAYER_Z
import ru.mipt.npm.muon.monitor.Monitor.UPPER_LAYER_Z
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.removeAll
import space.kscience.visionforge.root
import space.kscience.visionforge.solid.*
import kotlin.math.PI
class Model {
class Model(val manager: VisionManager) {
private val map = HashMap<String, SolidGroup>()
private val events = HashSet<Event>()
@ -34,6 +36,7 @@ class Model {
var tracks: SolidGroup
val root: SolidGroup = SolidGroup().apply {
root(this@Model.manager)
rotationX = PI / 2
group("bottom") {
Monitor.detectors.filter { it.center.z == LOWER_LAYER_Z }.forEach {
@ -78,7 +81,5 @@ class Model {
}
}
companion object {
fun buildGeometry() = Model().root
}
fun encodeToString(): String = manager.encodeToString(this.root)
}

View File

@ -7,13 +7,16 @@ import kotlinx.browser.document
import react.child
import react.dom.render
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.fetch
import space.kscience.visionforge.Application
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.bootstrap.useBootstrap
import space.kscience.visionforge.startApplication
private class MMDemoApp : Application {
private val model = Model()
private val visionManager = Global.fetch(VisionManager)
private val model = Model(visionManager)
private val connection = HttpClient {
install(JsonFeature) {

View File

@ -52,7 +52,7 @@ fun Application.module(context: Context = Global) {
}
get("/geometry") {
call.respondText(
solidManager.visionManager.encodeToString(Model.buildGeometry()),
Model(solidManager.visionManager).encodeToString(),
contentType = ContentType.Application.Json,
status = HttpStatusCode.OK
)

View File

@ -15,8 +15,9 @@ group = "ru.mipt.npm"
dependencies{
implementation(project(":visionforge-threejs:visionforge-threejs-server"))
implementation("ch.qos.logback:logback-classic:1.2.3")
}
application {
mainClass.set("ru.mipt.npm.gradle.sat.SatServerKt")
mainClass.set("ru.mipt.npm.sat.SatServerKt")
}

View File

@ -1,6 +1,9 @@
package space.kscience.visionforge.solid.demo
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.invoke
import space.kscience.dataforge.names.toName
@ -73,7 +76,7 @@ fun VisionLayout<Solid>.showcase() {
//override color for this cube
color(1530)
GlobalScope.launch(Dispatchers.Main) {
launch(Dispatchers.Main) {
while (isActive) {
delay(500)
visible = !(visible ?: false)
@ -82,7 +85,7 @@ fun VisionLayout<Solid>.showcase() {
}
}
GlobalScope.launch(Dispatchers.Main) {
launch(Dispatchers.Main) {
val random = Random(111)
while (isActive) {
delay(1000)

View File

@ -19,6 +19,7 @@ import space.kscience.visionforge.VisionLayout
import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.three.ThreeCanvas
import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.solid.three.configure
class ThreeDemoGrid(element: Element) : VisionLayout<Solid> {
private lateinit var navigationElement: HTMLElement
@ -70,7 +71,7 @@ class ThreeDemoGrid(element: Element) : VisionLayout<Solid> {
}
}
val element = document.getElementById("output-$name") ?: error("Element not found")
three.createCanvas(element, canvasOptions)
three.getOrCreateCanvas(element).also { it.configure(canvasOptions) }
}.render(vision)
}
}

View File

@ -1,15 +0,0 @@
plugins {
id("ru.mipt.npm.gradle.js")
}
val dataforgeVersion: String by rootProject.extra
dependencies{
api(project(":ui:react"))
api("subroh0508.net.kotlinmaterialui:core:0.4.5")
api("subroh0508.net.kotlinmaterialui:lab:0.4.5")
api(npm("@material-ui/core","4.9.14"))
api(npm("@material-ui/lab","4.0.0-alpha.51"))
//api(npm("@material-ui/icons","4.9.1"))
}

View File

@ -1,11 +0,0 @@
package space.kscience.visionforge.material
//@JsModule("@material-ui/icons/ExpandMore")
//external class ExpandMoreIcon : Component<RProps, RState>{
// override fun render(): dynamic
//}
//
//@JsModule("@material-ui/icons/ChevronRight")
//external class ChevronRightIcon : Component<RProps, RState>{
// override fun render(): dynamic
//}

View File

@ -1,97 +0,0 @@
package space.kscience.visionforge.material
import space.kscience.visionforge.react.component
import space.kscience.visionforge.react.state
import kotlinx.html.DIV
import materialui.components.card.card
import materialui.components.cardcontent.cardContent
import materialui.components.cardheader.cardHeader
import materialui.components.container.container
import materialui.components.container.enums.ContainerMaxWidth
import materialui.components.expansionpanel.expansionPanel
import materialui.components.expansionpaneldetails.expansionPanelDetails
import materialui.components.expansionpanelsummary.expansionPanelSummary
import materialui.components.grid.GridElementBuilder
import materialui.components.grid.enums.GridDirection
import materialui.components.grid.enums.GridStyle
import materialui.components.grid.grid
import materialui.components.paper.paper
import materialui.components.typography.typographyH5
import react.RBuilder
import react.RProps
import react.child
import react.dom.RDOMBuilder
fun accordionComponent(elements: List<Pair<String, RDOMBuilder<DIV>.() -> Unit>>) =
component<RProps> {
val expandedIndex: Int? by state { null }
container {
attrs {
maxWidth = ContainerMaxWidth.`false`
}
elements.forEachIndexed { index, (header, body) ->
expansionPanel {
attrs {
expanded = index == expandedIndex
}
expansionPanelSummary {
typographyH5 {
+header
}
}
expansionPanelDetails {
this.body()
}
}
}
}
}
typealias RAccordionBuilder = MutableList<Pair<String, RDOMBuilder<DIV>.() -> Unit>>
fun RAccordionBuilder.entry(title: String, builder: RDOMBuilder<DIV>.() -> Unit) {
add(title to builder)
}
fun RBuilder.accordion(builder: RAccordionBuilder.() -> Unit) {
val list: List<Pair<String, RDOMBuilder<DIV>.() -> Unit>> =
ArrayList<Pair<String, RDOMBuilder<DIV>.() -> Unit>>().apply(builder)
child(accordionComponent(list))
}
fun RBuilder.materialCard(title: String, block: RBuilder.() -> Unit) {
card {
cardHeader {
attrs {
this.title = typographyH5 {
+title
}
}
}
cardContent {
paper {
this.block()
}
}
}
}
fun RBuilder.column(vararg classMap: Pair<GridStyle, String>, block: GridElementBuilder<DIV>.() -> Unit) =
grid(*classMap) {
attrs {
container = true
direction = GridDirection.column
}
block()
}
fun RBuilder.row(vararg classMap: Pair<GridStyle, String>, block: GridElementBuilder<DIV>.() -> Unit) =
grid(*classMap) {
attrs {
container = true
direction = GridDirection.row
}
block()
}

View File

@ -1,94 +0,0 @@
package space.kscience.visionforge.material
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.plus
import space.kscience.dataforge.names.toName
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.isEmpty
import space.kscience.visionforge.react.component
import space.kscience.visionforge.react.state
import kotlinx.html.UL
import materialui.lab.components.treeItem.treeItem
import materialui.lab.components.treeView.SingleSelectTreeViewElementBuilder
import materialui.lab.components.treeView.treeView
import react.FunctionalComponent
import react.RBuilder
import react.RProps
import react.child
import react.dom.span
interface ObjectTreeProps : RProps {
var name: Name
var selected: Name?
var obj: Vision
var clickCallback: (Name?) -> Unit
}
private fun RBuilder.treeBranch(name: Name, obj: Vision): Unit {
treeItem {
val token = name.last()?.toString() ?: "World"
attrs {
nodeId = name.toString()
label {
span {
+token
}
}
}
if (obj is VisionGroup) {
obj.children.entries
.filter { !it.key.toString().startsWith("@") } // ignore statics and other hidden children
.sortedBy { (it.value as? VisionGroup)?.isEmpty ?: true }
.forEach { (childToken, child) ->
treeBranch(name + childToken, child)
}
}
}
}
val ObjectTree: FunctionalComponent<ObjectTreeProps> = component { props ->
var selected: String? by state { props.selected.toString() }
treeView {
this as SingleSelectTreeViewElementBuilder<UL>
attrs {
this.selected = selected
this.onNodeSelect{ _, selectedItem ->
selected = selectedItem
val itemName = selected?.toName()
props.clickCallback(itemName)
Unit
}
defaultCollapseIcon {
span{
+"-"
}
//child(ExpandMoreIcon::class) {}
}//{<ExpandMoreIcon />}
defaultExpandIcon {
span{
+"+"
}
//child(ChevronRightIcon::class) {}
}//{<ChevronRightIcon />}
}
treeBranch(props.name, props.obj)
}
}
fun RBuilder.objectTree(
vision: Vision,
selected: Name? = null,
clickCallback: (Name?) -> Unit = {}
) {
child(ObjectTree) {
attrs {
this.name = Name.EMPTY
this.obj = vision
this.selected = selected
this.clickCallback = clickCallback
}
}
}

View File

@ -1,37 +0,0 @@
package materialui.components.slider
import kotlinx.html.DIV
import kotlinx.html.Tag
import materialui.components.MaterialElementBuilder
import materialui.components.getValue
import materialui.components.inputbase.enums.InputBaseStyle
import materialui.components.setValue
import react.RClass
import react.ReactElement
class SliderElementBuilder<Props: SliderProps> internal constructor(
type: RClass<Props>,
classMap: List<Pair<Enum<*>, String>>
) : MaterialElementBuilder<DIV, Props>(type, classMap, { DIV(mapOf(), it) }) {
fun Tag.classes(vararg classMap: Pair<InputBaseStyle, String>) {
classes(classMap.toList())
}
var Tag.defaultValue: Number? by materialProps
var Tag.disabled: Boolean? by materialProps// = false
var Tag.getAriaLabel: String? by materialProps
var Tag.getAriaValueText: String? by materialProps
var Tag.marks: Array<String>? by materialProps
var Tag.max: Number? by materialProps// = 100
var Tag.min: Number? by materialProps// = 0,
var Tag.name: String? by materialProps
var Tag.onChange: ((dynamic, Number) -> Unit)? by materialProps
var Tag.onChangeCommitted: ((dynamic, Number) -> Unit)? by materialProps
var Tag.orientation: SliderOrientation? by materialProps
var Tag.scale: ((Number) -> Number)? by materialProps// {it}
var Tag.step: Number? by materialProps// = 1,
//ThumbComponent = 'span',
var Tag.track: SliderTrack by materialProps
var Tag.value: Number? by materialProps
var Tag.ValueLabelComponent: ReactElement? by materialProps
var Tag.valueLabelDisplay: SliderValueLabelDisplay by materialProps
}

View File

@ -1,18 +0,0 @@
package materialui.components.slider
enum class SliderOrientation {
horizontal,
vertical
}
enum class SliderTrack{
normal,
`false`,
inverted,
}
enum class SliderValueLabelDisplay{
on,
auto,
off
}

View File

@ -1,38 +0,0 @@
package materialui.components.slider
import materialui.components.StandardProps
import materialui.components.input.enums.InputStyle
import react.RBuilder
import react.RClass
import react.ReactElement
@JsModule("@material-ui/core/Slider")
private external val sliderModule: dynamic
external interface SliderProps : StandardProps {
var defaultValue: Number?
var disabled: Boolean?// = false
var getAriaLabel: String?
var getAriaValueText: String?
var marks: Array<String>?
var max: Number?// = 100
var min: Number?// = 0,
var name: String?
var onChange: ((dynamic, Number) -> Unit)?
var onChangeCommitted: ((dynamic, Number) -> Unit)?
var orientation: SliderOrientation?
var scale: ((Number) -> Number)?// {it}
var step: Number? // = 1,
//ThumbComponent = 'span',
var track: SliderTrack
var value: Number?
var ValueLabelComponent: ReactElement?
var valueLabelDisplay: SliderValueLabelDisplay
//valueLabelFormat = Identity,
}
@Suppress("UnsafeCastFromDynamic")
private val sliderComponent: RClass<SliderProps> = sliderModule.default
fun RBuilder.slider(vararg classMap: Pair<InputStyle, String>, block: SliderElementBuilder<SliderProps>.() -> Unit) =
child(SliderElementBuilder(sliderComponent, classMap.toList()).apply(block).create())

View File

@ -14,6 +14,7 @@ import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.ThreeCanvas
import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.solid.three.configure
import styled.css
import styled.styledDiv
@ -36,12 +37,15 @@ public val ThreeCanvasComponent: FunctionalComponent<ThreeCanvasProps> = functio
val elementRef = useRef<Element>(null)
var canvas by useState<ThreeCanvas?>(null)
val three: ThreePlugin = useMemo({props.context.fetch(ThreePlugin)}, arrayOf(props.context))
val three: ThreePlugin = useMemo({ props.context.fetch(ThreePlugin) }, arrayOf(props.context))
useEffect(listOf(props.obj, props.options, elementRef)) {
if (canvas == null) {
val element = elementRef.current as? HTMLElement ?: error("Canvas element not found")
val newCanvas: ThreeCanvas = three.createCanvas(element, props.options ?: Canvas3DOptions.empty())
val newCanvas: ThreeCanvas = three.getOrCreateCanvas(element)
props.options?.let {
newCanvas.configure(it)
}
props.canvasCallback?.invoke(newCanvas)
canvas = newCanvas
}

View File

@ -13,18 +13,25 @@ import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.toName
import space.kscience.visionforge.Vision.Companion.TYPE
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
/**
* A root type for display hierarchy
*/
@Type(TYPE)
public interface Vision : Described {
public interface Vision : Described, CoroutineScope {
/**
* The parent object of this one. If null, this one is a root.
*/
public var parent: VisionGroup?
public val manager: VisionManager? get() = parent?.manager
override val coroutineContext: CoroutineContext
get() = manager?.context?.coroutineContext ?: EmptyCoroutineContext
/**
* Get property.
* @param inherit toggles parent node property lookup. Null means inference from descriptor. Default is false.

View File

@ -1,7 +1,5 @@
package space.kscience.visionforge
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
@ -30,14 +28,14 @@ internal data class PropertyListener(
*/
@Serializable
@SerialName("vision")
public open class VisionBase(
internal var properties: Config? = null,
@Transient override var parent: VisionGroup? = null,
@Transient public val coroutineScope: CoroutineScope = GlobalScope,
) : Vision {
public open class VisionBase : Vision {
protected var properties: Config? = null
@Transient
override var parent: VisionGroup? = null
@Synchronized
protected fun getOrCreateConfig(): Config {
protected fun getOrCreateProperties(): Config {
if (properties == null) {
val newProperties = Config()
properties = newProperties
@ -77,7 +75,7 @@ public open class VisionBase(
}
override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
getOrCreateConfig().setItem(name, item)
getOrCreateProperties().setItem(name, item)
if (notify) {
invalidateProperty(name)
}
@ -104,7 +102,7 @@ public open class VisionBase(
get() = propertyInvalidationFlow
override fun invalidateProperty(propertyName: Name) {
coroutineScope.launch {
launch {
if (propertyName == STYLE_KEY) {
updateStyles(styles)
}

View File

@ -79,6 +79,7 @@ public operator fun <V : Vision> VisionContainer<V>.get(str: String): V? = get(s
public operator fun <V : Vision> VisionContainerBuilder<V>.set(token: NameToken, child: V?): Unit =
set(token.asName(), child)
public operator fun <V : Vision> VisionContainerBuilder<V>.set(key: String?, child: V?): Unit = set(key?.toName(), child)
public operator fun <V : Vision> VisionContainerBuilder<V>.set(key: String?, child: V?): Unit =
set(key?.toName(), child)
public fun MutableVisionGroup.removeAll(): Unit = children.keys.map { it.asName() }.forEach { this[it] = null }

View File

@ -48,7 +48,7 @@ public open class VisionGroupBase(
* Propagate children change event upwards
*/
private fun childrenChanged(name: NameToken, before: Vision?, after: Vision?) {
coroutineScope.launch {
launch {
_structureChanges.emit(MutableVisionGroup.StructureChange(name, before, after))
}
}
@ -141,4 +141,16 @@ public open class VisionGroupBase(
}
super.update(change)
}
}
/**
* Non-serializable root group used to propagate manager to its children
*/
internal class RootVisionGroup(override val manager: VisionManager) : VisionGroupBase()
/**
* Designate this [VisionGroup] as a root group and assign a [VisionManager] as its parent
*/
public fun Vision.root(manager: VisionManager){
parent = RootVisionGroup(manager)
}

View File

@ -14,7 +14,7 @@ public fun TagConsumer<*>.embedVisionFragment(
fragment: HtmlVisionFragment,
): Map<Name, Vision> {
val visionMap = HashMap<Name, Vision>()
val consumer = object : VisionTagConsumer<Any?>(this@embedVisionFragment, idPrefix) {
val consumer = object : VisionTagConsumer<Any?>(this@embedVisionFragment, manager, idPrefix) {
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
visionMap[name] = vision
script {
@ -34,13 +34,13 @@ public fun FlowContent.embedVisionFragment(
manager: VisionManager,
idPrefix: String? = null,
fragment: HtmlVisionFragment,
): Map<Name, Vision> = consumer.embedVisionFragment(manager, idPrefix, fragment)
): Map<Name, Vision> = consumer.embedVisionFragment(manager, idPrefix, fragment)
internal const val RENDER_FUNCTION_NAME = "renderAllVisionsById"
@DFExperimental
public fun TagConsumer<*>.embedAndRenderVisionFragment(manager: VisionManager, id: Any, fragment: HtmlVisionFragment){
public fun TagConsumer<*>.embedAndRenderVisionFragment(manager: VisionManager, id: Any, fragment: HtmlVisionFragment) {
div {
div {
this.id = id.toString()

View File

@ -17,7 +17,7 @@ import kotlin.collections.set
* A placeholder object to attach inline vision builders.
*/
@DFExperimental
public class VisionOutput @PublishedApi internal constructor() {
public class VisionOutput @PublishedApi internal constructor(public val manager: VisionManager) {
public var meta: Meta = Meta.EMPTY
//TODO expose a way to define required plugins.
@ -32,6 +32,7 @@ public class VisionOutput @PublishedApi internal constructor() {
*/
public abstract class VisionTagConsumer<R>(
private val root: TagConsumer<R>,
public val manager:VisionManager,
private val idPrefix: String? = null,
) : TagConsumer<R> by root {
@ -62,7 +63,7 @@ public abstract class VisionTagConsumer<R>(
script {
attributes["class"] = OUTPUT_META_CLASS
unsafe {
+VisionManager.defaultJson.encodeToString(MetaSerializer, outputMeta)
+manager.jsonFormat.encodeToString(MetaSerializer, outputMeta)
}
}
}
@ -76,7 +77,7 @@ public abstract class VisionTagConsumer<R>(
name: Name,
visionProvider: VisionOutput.() -> Vision,
): T {
val output = VisionOutput()
val output = VisionOutput(manager)
val vision = output.visionProvider()
return vision(name, vision, output.meta)
}

View File

@ -2,14 +2,13 @@ package space.kscience.visionforge.html
import kotlinx.html.*
import kotlinx.html.stream.createHTML
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.set
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.Name
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionBase
import space.kscience.visionforge.configure
import space.kscience.visionforge.meta
import space.kscience.visionforge.*
import kotlin.test.Test
typealias HtmlVisionRenderer = FlowContent.(name: Name, vision: Vision, meta: Meta) -> Unit
@ -20,7 +19,7 @@ fun FlowContent.renderVisionFragment(
fragment: HtmlVisionFragment,
): Map<Name, Vision> {
val visionMap = HashMap<Name, Vision>()
val consumer = object : VisionTagConsumer<Any?>(consumer, idPrefix) {
val consumer = object : VisionTagConsumer<Any?>(consumer, Global.fetch(VisionManager), idPrefix) {
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
visionMap[name] = vision
renderer(name, vision, outputMeta)
@ -37,7 +36,7 @@ class HtmlTagTest {
fun VisionOutput.base(block: VisionBase.() -> Unit) =
VisionBase().apply(block)
val fragment: HtmlVisionFragment = {
val fragment: HtmlVisionFragment = {
div {
h1 { +"Head" }
vision("ddd") {
@ -72,7 +71,7 @@ class HtmlTagTest {
@Test
fun testStringRender() {
println(
createHTML().div{
createHTML().div {
renderVisionFragment(simpleVisionRenderer, fragment = fragment)
}
)

View File

@ -128,7 +128,7 @@ public class VisionClient : AbstractPlugin() {
logger.info { "Updating vision data from $wsUrl" }
val ws = WebSocket(wsUrl.toString()).apply {
WebSocket(wsUrl.toString()).apply {
onmessage = { messageEvent ->
val stringData: String? = messageEvent.data as? String
if (stringData != null) {

View File

@ -17,7 +17,7 @@ public interface ElementVisionRenderer {
/**
* Display the [vision] inside a given [element] replacing its current content
*/
public fun render(element: Element, vision: Vision, meta: Meta = Meta.EMPTY): Unit
public fun render(element: Element, vision: Vision, meta: Meta = Meta.EMPTY)
public companion object {
public const val TYPE: String = "elementVisionRenderer"

View File

@ -2,7 +2,7 @@ plugins {
id("ru.mipt.npm.gradle.mpp")
}
val plotlyVersion = "0.4.2"
val plotlyVersion = "0.4.3"
kscience {
useSerialization()

View File

@ -1,5 +1,6 @@
package space.kscience.visionforge.plotly
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.Config
import space.kscience.dataforge.misc.DFExperimental
@ -7,13 +8,23 @@ import space.kscience.plotly.Plot
import space.kscience.plotly.Plotly
import space.kscience.visionforge.VisionBase
import space.kscience.visionforge.html.VisionOutput
import space.kscience.visionforge.root
@Serializable
public class VisionOfPlotly(private val plotConfig: Config) : VisionBase(plotConfig){
public val plot: Plot get() = Plot(plotConfig)
@SerialName("vision.plotly")
public class VisionOfPlotly private constructor() : VisionBase() {
public constructor(plot: Plot) : this() {
properties = plot.config
}
public val plot: Plot get() = Plot(properties ?: Config())
}
public fun Plot.toVision(): VisionOfPlotly = VisionOfPlotly(config)
public fun Plot.asVision(): VisionOfPlotly = VisionOfPlotly(this)
@DFExperimental
public inline fun VisionOutput.plotly(block: Plot.() -> Unit): VisionOfPlotly = VisionOfPlotly(Plotly.plot(block).config)
public inline fun VisionOutput.plotly(
block: Plot.() -> Unit,
): VisionOfPlotly = VisionOfPlotly(Plotly.plot(block)).apply {
root(this@plotly.manager)
}

View File

@ -31,6 +31,8 @@ public actual class PlotlyPlugin : VisionPlugin(), ElementVisionRenderer {
override fun render(element: Element, vision: Vision, meta: Meta) {
val plot = (vision as? VisionOfPlotly)?.plot ?: error("VisionOfPlotly expected but ${vision::class} found")
val config = PlotlyConfig.read(meta)
println(plot.config)
println(plot.data[0].toMeta())
element.plot(plot, config)
}

View File

@ -43,12 +43,6 @@ import java.net.URI
import kotlin.collections.set
import kotlin.time.Duration
public enum class VisionServerDataMode {
EMBED,
FETCH,
CONNECT
}
/**
* A ktor plugin container with given [routing]
@ -61,7 +55,9 @@ public class VisionServer internal constructor(
override val config: Config = Config()
public var updateInterval: Long by config.long(300, key = UPDATE_INTERVAL_KEY)
public var cacheFragments: Boolean by config.boolean(true)
public var dataMode: VisionServerDataMode = VisionServerDataMode.CONNECT
public var dataEmbed: Boolean by config.boolean(true, "data.embed".toName())
public var dataFetch: Boolean by config.boolean(false, "data.fetch".toName())
public var dataConnect: Boolean by config.boolean(true, "data.connect".toName())
/**
* a list of headers that should be applied to all pages
@ -79,25 +75,26 @@ public class VisionServer internal constructor(
): Map<Name, Vision> {
val visionMap = HashMap<Name, Vision>()
val consumer = object : VisionTagConsumer<Any?>(consumer) {
val consumer = object : VisionTagConsumer<Any?>(consumer, visionManager) {
override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
visionMap[name] = vision
// Toggle update mode
when (dataMode) {
VisionServerDataMode.EMBED -> {
script {
attributes["class"] = OUTPUT_DATA_CLASS
unsafe {
+"\n${visionManager.encodeToString(vision)}\n"
}
if (dataConnect) {
attributes[OUTPUT_CONNECT_ATTRIBUTE] = "auto"
}
if (dataFetch) {
attributes[OUTPUT_FETCH_ATTRIBUTE] = "auto"
}
if (dataEmbed) {
script {
type = "text/json"
attributes["class"] = OUTPUT_DATA_CLASS
unsafe {
+"\n${visionManager.encodeToString(vision)}\n"
}
}
VisionServerDataMode.FETCH -> {
attributes[OUTPUT_FETCH_ATTRIBUTE] = "auto"
}
VisionServerDataMode.CONNECT -> {
attributes[OUTPUT_CONNECT_ATTRIBUTE] = "auto"
}
}
}
}

View File

@ -68,4 +68,5 @@ public class Solids(meta: Meta) : VisionPlugin(meta) {
@VisionBuilder
@DFExperimental
public inline fun VisionOutput.solid(block: SolidGroup.() -> Unit): SolidGroup = SolidGroup().apply(block)
public inline fun VisionOutput.solid(block: SolidGroup.() -> Unit): SolidGroup =
SolidGroup().apply(block).apply { root(this@solid.manager) }

View File

@ -24,9 +24,7 @@ import org.w3c.dom.Node
import org.w3c.dom.events.MouseEvent
import space.kscience.dataforge.context.info
import space.kscience.dataforge.context.logger
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.meta.useProperty
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.names.*
import space.kscience.visionforge.Colors
import space.kscience.visionforge.solid.Solid
@ -41,8 +39,8 @@ import kotlin.math.sin
*/
public class ThreeCanvas(
public val three: ThreePlugin,
public val options: Canvas3DOptions,
) {
public val options = Canvas3DOptions()
private var boundingBox: Box3? = null
private var root: Object3D? = null
@ -315,4 +313,11 @@ public class ThreeCanvas(
private const val AXES_NAME = "@axes"
private const val CLIP_HELPER_NAME = "@clipping"
}
}
public fun ThreeCanvas.configure(options: Canvas3DOptions) {
this.options.update(options.toMeta())
options.onChange(this) { name, _, newItem ->
options[name] = newItem
}
}

View File

@ -8,6 +8,7 @@ import org.w3c.dom.Element
import org.w3c.dom.HTMLElement
import space.kscience.dataforge.context.*
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.update
import space.kscience.dataforge.names.*
import space.kscience.visionforge.ElementVisionRenderer
import space.kscience.visionforge.Vision
@ -111,12 +112,13 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
}
}
public fun createCanvas(
private val canvasCache = HashMap<Element, ThreeCanvas>()
public fun getOrCreateCanvas(
element: Element,
options: Canvas3DOptions = Canvas3DOptions.empty(),
): ThreeCanvas = ThreeCanvas(this, options).apply {
): ThreeCanvas = canvasCache.getOrPut(element){ThreeCanvas(this).apply {
attach(element)
}
}}
override fun content(target: String): Map<Name, Any> {
return when (target) {
@ -128,11 +130,10 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
override fun rateVision(vision: Vision): Int =
if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING
public fun renderSolid(
fun renderSolid(
element: Element,
vision: Solid,
options: Canvas3DOptions,
): ThreeCanvas = createCanvas(element, options).apply {
): ThreeCanvas = getOrCreateCanvas(element).apply {
render(vision)
}
@ -140,8 +141,9 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
renderSolid(
element,
vision as? Solid ?: error("Solid expected but ${vision::class} found"),
Canvas3DOptions.read(meta)
)
).apply {
options.update(meta)
}
}
public companion object : PluginFactory<ThreePlugin> {
@ -154,8 +156,10 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
public fun ThreePlugin.render(
element: HTMLElement,
obj: Solid,
options: Canvas3DOptions.() -> Unit = {},
): ThreeCanvas = renderSolid(element, obj, Canvas3DOptions(options))
optionsBuilder: Canvas3DOptions.() -> Unit = {},
): ThreeCanvas = renderSolid(element, obj).apply {
options.apply(optionsBuilder)
}
internal operator fun Object3D.set(token: NameToken, object3D: Object3D) {
object3D.name = token.toString()