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() useCommonJs()
browser { browser {
commonWebpackConfig { commonWebpackConfig {
sourceMaps = false
cssSupport.enabled = 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.Page
import space.kscience.visionforge.html.embedVisionFragment import space.kscience.visionforge.html.embedVisionFragment
import space.kscience.visionforge.plotly.PlotlyPlugin 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.solid.Solids
import space.kscience.visionforge.visionManager import space.kscience.visionforge.visionManager
@ -80,7 +80,7 @@ internal class VisionForgePlayGroundForJupyter : JupyterIntegration() {
render<Plot> { plot -> render<Plot> { plot ->
val fragment = HtmlVisionFragment { val fragment = HtmlVisionFragment {
vision(plot.toVision()) vision(plot.asVision())
} }
HTML(produceHtmlVisionString(fragment)) 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.CENTRAL_LAYER_Z
import ru.mipt.npm.muon.monitor.Monitor.LOWER_LAYER_Z import ru.mipt.npm.muon.monitor.Monitor.LOWER_LAYER_Z
import ru.mipt.npm.muon.monitor.Monitor.UPPER_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.removeAll
import space.kscience.visionforge.root
import space.kscience.visionforge.solid.* import space.kscience.visionforge.solid.*
import kotlin.math.PI import kotlin.math.PI
class Model { class Model(val manager: VisionManager) {
private val map = HashMap<String, SolidGroup>() private val map = HashMap<String, SolidGroup>()
private val events = HashSet<Event>() private val events = HashSet<Event>()
@ -34,6 +36,7 @@ class Model {
var tracks: SolidGroup var tracks: SolidGroup
val root: SolidGroup = SolidGroup().apply { val root: SolidGroup = SolidGroup().apply {
root(this@Model.manager)
rotationX = PI / 2 rotationX = PI / 2
group("bottom") { group("bottom") {
Monitor.detectors.filter { it.center.z == LOWER_LAYER_Z }.forEach { Monitor.detectors.filter { it.center.z == LOWER_LAYER_Z }.forEach {
@ -78,7 +81,5 @@ class Model {
} }
} }
companion object { fun encodeToString(): String = manager.encodeToString(this.root)
fun buildGeometry() = Model().root
}
} }

View File

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

View File

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

View File

@ -15,8 +15,9 @@ group = "ru.mipt.npm"
dependencies{ dependencies{
implementation(project(":visionforge-threejs:visionforge-threejs-server")) implementation(project(":visionforge-threejs:visionforge-threejs-server"))
implementation("ch.qos.logback:logback-classic:1.2.3")
} }
application { 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 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.Meta
import space.kscience.dataforge.meta.invoke import space.kscience.dataforge.meta.invoke
import space.kscience.dataforge.names.toName import space.kscience.dataforge.names.toName
@ -73,7 +76,7 @@ fun VisionLayout<Solid>.showcase() {
//override color for this cube //override color for this cube
color(1530) color(1530)
GlobalScope.launch(Dispatchers.Main) { launch(Dispatchers.Main) {
while (isActive) { while (isActive) {
delay(500) delay(500)
visible = !(visible ?: false) visible = !(visible ?: false)
@ -82,7 +85,7 @@ fun VisionLayout<Solid>.showcase() {
} }
} }
GlobalScope.launch(Dispatchers.Main) { launch(Dispatchers.Main) {
val random = Random(111) val random = Random(111)
while (isActive) { while (isActive) {
delay(1000) delay(1000)

View File

@ -19,6 +19,7 @@ import space.kscience.visionforge.VisionLayout
import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solid
import space.kscience.visionforge.solid.three.ThreeCanvas import space.kscience.visionforge.solid.three.ThreeCanvas
import space.kscience.visionforge.solid.three.ThreePlugin import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.solid.three.configure
class ThreeDemoGrid(element: Element) : VisionLayout<Solid> { class ThreeDemoGrid(element: Element) : VisionLayout<Solid> {
private lateinit var navigationElement: HTMLElement 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") val element = document.getElementById("output-$name") ?: error("Element not found")
three.createCanvas(element, canvasOptions) three.getOrCreateCanvas(element).also { it.configure(canvasOptions) }
}.render(vision) }.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.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.ThreeCanvas import space.kscience.visionforge.solid.three.ThreeCanvas
import space.kscience.visionforge.solid.three.ThreePlugin import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.solid.three.configure
import styled.css import styled.css
import styled.styledDiv import styled.styledDiv
@ -36,12 +37,15 @@ public val ThreeCanvasComponent: FunctionalComponent<ThreeCanvasProps> = functio
val elementRef = useRef<Element>(null) val elementRef = useRef<Element>(null)
var canvas by useState<ThreeCanvas?>(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)) { useEffect(listOf(props.obj, props.options, elementRef)) {
if (canvas == null) { if (canvas == null) {
val element = elementRef.current as? HTMLElement ?: error("Canvas element not found") 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) props.canvasCallback?.invoke(newCanvas)
canvas = 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.asName
import space.kscience.dataforge.names.toName import space.kscience.dataforge.names.toName
import space.kscience.visionforge.Vision.Companion.TYPE import space.kscience.visionforge.Vision.Companion.TYPE
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
/** /**
* A root type for display hierarchy * A root type for display hierarchy
*/ */
@Type(TYPE) @Type(TYPE)
public interface Vision : Described { public interface Vision : Described, CoroutineScope {
/** /**
* The parent object of this one. If null, this one is a root. * The parent object of this one. If null, this one is a root.
*/ */
public var parent: VisionGroup? public var parent: VisionGroup?
public val manager: VisionManager? get() = parent?.manager
override val coroutineContext: CoroutineContext
get() = manager?.context?.coroutineContext ?: EmptyCoroutineContext
/** /**
* Get property. * Get property.
* @param inherit toggles parent node property lookup. Null means inference from descriptor. Default is false. * @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 package space.kscience.visionforge
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -30,14 +28,14 @@ internal data class PropertyListener(
*/ */
@Serializable @Serializable
@SerialName("vision") @SerialName("vision")
public open class VisionBase( public open class VisionBase : Vision {
internal var properties: Config? = null, protected var properties: Config? = null
@Transient override var parent: VisionGroup? = null,
@Transient public val coroutineScope: CoroutineScope = GlobalScope, @Transient
) : Vision { override var parent: VisionGroup? = null
@Synchronized @Synchronized
protected fun getOrCreateConfig(): Config { protected fun getOrCreateProperties(): Config {
if (properties == null) { if (properties == null) {
val newProperties = Config() val newProperties = Config()
properties = newProperties properties = newProperties
@ -77,7 +75,7 @@ public open class VisionBase(
} }
override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) { override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
getOrCreateConfig().setItem(name, item) getOrCreateProperties().setItem(name, item)
if (notify) { if (notify) {
invalidateProperty(name) invalidateProperty(name)
} }
@ -104,7 +102,7 @@ public open class VisionBase(
get() = propertyInvalidationFlow get() = propertyInvalidationFlow
override fun invalidateProperty(propertyName: Name) { override fun invalidateProperty(propertyName: Name) {
coroutineScope.launch { launch {
if (propertyName == STYLE_KEY) { if (propertyName == STYLE_KEY) {
updateStyles(styles) 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 = public operator fun <V : Vision> VisionContainerBuilder<V>.set(token: NameToken, child: V?): Unit =
set(token.asName(), child) 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 } 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 * Propagate children change event upwards
*/ */
private fun childrenChanged(name: NameToken, before: Vision?, after: Vision?) { private fun childrenChanged(name: NameToken, before: Vision?, after: Vision?) {
coroutineScope.launch { launch {
_structureChanges.emit(MutableVisionGroup.StructureChange(name, before, after)) _structureChanges.emit(MutableVisionGroup.StructureChange(name, before, after))
} }
} }
@ -142,3 +142,15 @@ public open class VisionGroupBase(
super.update(change) 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, fragment: HtmlVisionFragment,
): Map<Name, Vision> { ): Map<Name, Vision> {
val visionMap = HashMap<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) { override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
visionMap[name] = vision visionMap[name] = vision
script { script {
@ -34,13 +34,13 @@ public fun FlowContent.embedVisionFragment(
manager: VisionManager, manager: VisionManager,
idPrefix: String? = null, idPrefix: String? = null,
fragment: HtmlVisionFragment, 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" internal const val RENDER_FUNCTION_NAME = "renderAllVisionsById"
@DFExperimental @DFExperimental
public fun TagConsumer<*>.embedAndRenderVisionFragment(manager: VisionManager, id: Any, fragment: HtmlVisionFragment){ public fun TagConsumer<*>.embedAndRenderVisionFragment(manager: VisionManager, id: Any, fragment: HtmlVisionFragment) {
div { div {
div { div {
this.id = id.toString() this.id = id.toString()

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@ public interface ElementVisionRenderer {
/** /**
* Display the [vision] inside a given [element] replacing its current content * 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 companion object {
public const val TYPE: String = "elementVisionRenderer" public const val TYPE: String = "elementVisionRenderer"

View File

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

View File

@ -1,5 +1,6 @@
package space.kscience.visionforge.plotly package space.kscience.visionforge.plotly
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.Config import space.kscience.dataforge.meta.Config
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
@ -7,13 +8,23 @@ import space.kscience.plotly.Plot
import space.kscience.plotly.Plotly import space.kscience.plotly.Plotly
import space.kscience.visionforge.VisionBase import space.kscience.visionforge.VisionBase
import space.kscience.visionforge.html.VisionOutput import space.kscience.visionforge.html.VisionOutput
import space.kscience.visionforge.root
@Serializable @Serializable
public class VisionOfPlotly(private val plotConfig: Config) : VisionBase(plotConfig){ @SerialName("vision.plotly")
public val plot: Plot get() = Plot(plotConfig) 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 @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) { override fun render(element: Element, vision: Vision, meta: Meta) {
val plot = (vision as? VisionOfPlotly)?.plot ?: error("VisionOfPlotly expected but ${vision::class} found") val plot = (vision as? VisionOfPlotly)?.plot ?: error("VisionOfPlotly expected but ${vision::class} found")
val config = PlotlyConfig.read(meta) val config = PlotlyConfig.read(meta)
println(plot.config)
println(plot.data[0].toMeta())
element.plot(plot, config) element.plot(plot, config)
} }

View File

@ -43,12 +43,6 @@ import java.net.URI
import kotlin.collections.set import kotlin.collections.set
import kotlin.time.Duration import kotlin.time.Duration
public enum class VisionServerDataMode {
EMBED,
FETCH,
CONNECT
}
/** /**
* A ktor plugin container with given [routing] * A ktor plugin container with given [routing]
@ -61,7 +55,9 @@ public class VisionServer internal constructor(
override val config: Config = Config() override val config: Config = Config()
public var updateInterval: Long by config.long(300, key = UPDATE_INTERVAL_KEY) public var updateInterval: Long by config.long(300, key = UPDATE_INTERVAL_KEY)
public var cacheFragments: Boolean by config.boolean(true) 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 * a list of headers that should be applied to all pages
@ -79,25 +75,26 @@ public class VisionServer internal constructor(
): Map<Name, Vision> { ): Map<Name, Vision> {
val visionMap = HashMap<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) { override fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta) {
visionMap[name] = vision visionMap[name] = vision
// Toggle update mode // Toggle update mode
when (dataMode) { if (dataConnect) {
VisionServerDataMode.EMBED -> { attributes[OUTPUT_CONNECT_ATTRIBUTE] = "auto"
script { }
attributes["class"] = OUTPUT_DATA_CLASS
unsafe { if (dataFetch) {
+"\n${visionManager.encodeToString(vision)}\n" 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 @VisionBuilder
@DFExperimental @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 org.w3c.dom.events.MouseEvent
import space.kscience.dataforge.context.info import space.kscience.dataforge.context.info
import space.kscience.dataforge.context.logger import space.kscience.dataforge.context.logger
import space.kscience.dataforge.meta.get import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.meta.useProperty
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.*
import space.kscience.visionforge.Colors import space.kscience.visionforge.Colors
import space.kscience.visionforge.solid.Solid import space.kscience.visionforge.solid.Solid
@ -41,8 +39,8 @@ import kotlin.math.sin
*/ */
public class ThreeCanvas( public class ThreeCanvas(
public val three: ThreePlugin, public val three: ThreePlugin,
public val options: Canvas3DOptions,
) { ) {
public val options = Canvas3DOptions()
private var boundingBox: Box3? = null private var boundingBox: Box3? = null
private var root: Object3D? = null private var root: Object3D? = null
@ -316,3 +314,10 @@ public class ThreeCanvas(
private const val CLIP_HELPER_NAME = "@clipping" 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 org.w3c.dom.HTMLElement
import space.kscience.dataforge.context.* import space.kscience.dataforge.context.*
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.update
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.*
import space.kscience.visionforge.ElementVisionRenderer import space.kscience.visionforge.ElementVisionRenderer
import space.kscience.visionforge.Vision 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, element: Element,
options: Canvas3DOptions = Canvas3DOptions.empty(), ): ThreeCanvas = canvasCache.getOrPut(element){ThreeCanvas(this).apply {
): ThreeCanvas = ThreeCanvas(this, options).apply {
attach(element) attach(element)
} }}
override fun content(target: String): Map<Name, Any> { override fun content(target: String): Map<Name, Any> {
return when (target) { return when (target) {
@ -128,11 +130,10 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
override fun rateVision(vision: Vision): Int = override fun rateVision(vision: Vision): Int =
if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING if (vision is Solid) ElementVisionRenderer.DEFAULT_RATING else ElementVisionRenderer.ZERO_RATING
public fun renderSolid( fun renderSolid(
element: Element, element: Element,
vision: Solid, vision: Solid,
options: Canvas3DOptions, ): ThreeCanvas = getOrCreateCanvas(element).apply {
): ThreeCanvas = createCanvas(element, options).apply {
render(vision) render(vision)
} }
@ -140,8 +141,9 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
renderSolid( renderSolid(
element, element,
vision as? Solid ?: error("Solid expected but ${vision::class} found"), vision as? Solid ?: error("Solid expected but ${vision::class} found"),
Canvas3DOptions.read(meta) ).apply {
) options.update(meta)
}
} }
public companion object : PluginFactory<ThreePlugin> { public companion object : PluginFactory<ThreePlugin> {
@ -154,8 +156,10 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
public fun ThreePlugin.render( public fun ThreePlugin.render(
element: HTMLElement, element: HTMLElement,
obj: Solid, obj: Solid,
options: Canvas3DOptions.() -> Unit = {}, optionsBuilder: Canvas3DOptions.() -> Unit = {},
): ThreeCanvas = renderSolid(element, obj, Canvas3DOptions(options)) ): ThreeCanvas = renderSolid(element, obj).apply {
options.apply(optionsBuilder)
}
internal operator fun Object3D.set(token: NameToken, object3D: Object3D) { internal operator fun Object3D.set(token: NameToken, object3D: Object3D) {
object3D.name = token.toString() object3D.name = token.toString()