Compare commits
22 Commits
v0.5.0
...
refactor/c
| Author | SHA1 | Date | |
|---|---|---|---|
| 16bfb5b005 | |||
| 56061d1da0 | |||
| 99862f52b8 | |||
| e1fbfc7aa3 | |||
| ca9db2585d | |||
| a4bd3bdb58 | |||
| 144058a881 | |||
| 5079bb448c | |||
| 4da02e29ce | |||
| 9389d3a032 | |||
| 7e3a51d4a4 | |||
| e708caf584 | |||
| bae747b601 | |||
| 7f05e5a556 | |||
| 29ab796807 | |||
| 953c696956 | |||
| 5f285ff7a1 | |||
| 67dea7c3f1 | |||
| a42edb44f9 | |||
| 447831707d | |||
| c70f5a1704 | |||
| 198b3707c2 |
@@ -5,12 +5,14 @@
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
- Replaced `VisionTagConsumer` with a proper `VisionHtmlContext` as a context parameter.
|
||||
|
||||
### Deprecated
|
||||
|
||||
### Removed
|
||||
|
||||
### Fixed
|
||||
- Fix the problem where property listeners do not react on property child node changa
|
||||
|
||||
### Security
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
|
||||
import space.kscience.gradle.useApache2Licence
|
||||
import space.kscience.gradle.useSPCTeam
|
||||
|
||||
@@ -11,23 +11,25 @@ val dataforgeVersion by extra("0.10.1")
|
||||
|
||||
allprojects {
|
||||
group = "space.kscience"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0-dev-1"
|
||||
}
|
||||
|
||||
subprojects {
|
||||
if (name.startsWith("visionforge")) apply<MavenPublishPlugin>()
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven("https://repo.kotlin.link")
|
||||
mavenCentral()
|
||||
maven("https://maven.jzy3d.org/releases")
|
||||
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
|
||||
}
|
||||
// repositories {
|
||||
// mavenLocal()
|
||||
// maven("https://repo.kotlin.link")
|
||||
// mavenCentral()
|
||||
// maven("https://maven.jzy3d.org/releases")
|
||||
// maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
|
||||
// }
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.addAll("-Xcontext-parameters")
|
||||
plugins.withId("org.jetbrains.kotlin.multiplatform") {
|
||||
extensions.configure<KotlinMultiplatformExtension>{
|
||||
compilerOptions{
|
||||
freeCompilerArgs.addAll("-Xcontext-parameters")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ group = "ru.mipt.npm"
|
||||
kscience {
|
||||
fullStack(
|
||||
"muon-monitor.js",
|
||||
development = true,
|
||||
development = false,
|
||||
jvmConfig = {
|
||||
binaries {
|
||||
executable {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
kotlin("jupyter.api")
|
||||
alias(spclibs.plugins.kotlin.jupyter.api)
|
||||
id("com.gradleup.shadow") version "8.3.6"
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import space.kscience.dataforge.meta.*
|
||||
import space.kscience.visionforge.Colors
|
||||
import space.kscience.visionforge.html.ResourceLocation
|
||||
import space.kscience.visionforge.html.meta
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.solid.*
|
||||
import java.util.zip.ZipInputStream
|
||||
import kotlin.io.path.Path
|
||||
|
||||
@@ -10,6 +10,7 @@ import space.kscience.plotly.models.scatter
|
||||
import space.kscience.plotly.plotly
|
||||
import space.kscience.tables.ColumnHeader
|
||||
import space.kscience.visionforge.html.ResourceLocation
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.markup.markdown
|
||||
import space.kscience.visionforge.solid.axes
|
||||
import space.kscience.visionforge.solid.box
|
||||
|
||||
@@ -14,6 +14,7 @@ import space.kscience.kmath.geometry.euclidean3d.RotationOrder
|
||||
import space.kscience.kmath.geometry.euclidean3d.fromEuler
|
||||
import space.kscience.kmath.geometry.euclidean3d.fromRotation
|
||||
import space.kscience.visionforge.Colors
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.solid.*
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
|
||||
@@ -3,6 +3,7 @@ package space.kscience.visionforge.examples
|
||||
import space.kscience.kmath.geometry.euclidean3d.Float64Space3D
|
||||
import space.kscience.kmath.geometry.radians
|
||||
import space.kscience.visionforge.html.ResourceLocation
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.solid.*
|
||||
import kotlin.math.PI
|
||||
|
||||
|
||||
@@ -15,8 +15,10 @@ import space.kscience.plotly.PlotlyPlugin
|
||||
import space.kscience.plotly.layout
|
||||
import space.kscience.plotly.models.Trace
|
||||
import space.kscience.plotly.models.invoke
|
||||
import space.kscience.plotly.plot
|
||||
import space.kscience.plotly.plotly
|
||||
import space.kscience.visionforge.html.VisionPage
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.server.openInBrowser
|
||||
import space.kscience.visionforge.server.visionPage
|
||||
import kotlin.math.PI
|
||||
@@ -51,15 +53,14 @@ suspend fun main() {
|
||||
|
||||
h1 { +"This is the plot page" }
|
||||
a("/other") { +"The other page" }
|
||||
vision {
|
||||
plotly {
|
||||
traces(sinTrace, cosTrace)
|
||||
layout {
|
||||
title = "Other dynamic plot"
|
||||
xaxis.title = "x axis name"
|
||||
yaxis.title = "y axis name"
|
||||
}
|
||||
plot {
|
||||
traces(sinTrace, cosTrace)
|
||||
layout {
|
||||
title = "Other dynamic plot"
|
||||
xaxis.title = "x axis name"
|
||||
yaxis.title = "y axis name"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package space.kscience.visionforge.examples
|
||||
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.solid.ambientLight
|
||||
import space.kscience.visionforge.solid.extruded
|
||||
import space.kscience.visionforge.solid.polygon
|
||||
|
||||
@@ -10,6 +10,7 @@ import space.kscience.dataforge.context.request
|
||||
import space.kscience.visionforge.VisionManager
|
||||
import space.kscience.visionforge.html.VisionOfHtmlForm
|
||||
import space.kscience.visionforge.html.VisionPage
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.html.visionOfForm
|
||||
import space.kscience.visionforge.onPropertyChange
|
||||
import space.kscience.visionforge.server.close
|
||||
|
||||
@@ -3,6 +3,7 @@ package space.kscience.visionforge.examples
|
||||
import space.kscience.gdml.GdmlShowCase
|
||||
import space.kscience.visionforge.gdml.toVision
|
||||
import space.kscience.visionforge.html.ResourceLocation
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.solid.Solids
|
||||
|
||||
fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
|
||||
|
||||
@@ -5,6 +5,7 @@ package space.kscience.visionforge.examples
|
||||
import space.kscience.gdml.*
|
||||
import space.kscience.visionforge.gdml.toVision
|
||||
import space.kscience.visionforge.html.ResourceLocation
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.solid.Solids
|
||||
import space.kscience.visionforge.solid.color
|
||||
import space.kscience.visionforge.solid.invoke
|
||||
@@ -12,229 +13,230 @@ import space.kscience.visionforge.visible
|
||||
import java.nio.file.Path
|
||||
|
||||
fun main() = makeVisionFile(Path.of("curves.html"), resourceLocation = ResourceLocation.EMBED) {
|
||||
vision("canvas") {
|
||||
requirePlugin(Solids)
|
||||
Gdml {
|
||||
// geometry variables
|
||||
val worldSize = 500
|
||||
vision("canvas") {
|
||||
requirePlugin(Solids)
|
||||
Gdml {
|
||||
// geometry variables
|
||||
val worldSize = 500
|
||||
// chamber
|
||||
val chamberHeight = 30 // length of the chamber
|
||||
val chamberDiameter = 102 // inner diameter of the copper chamber
|
||||
val chamberOuterSquareSide = 134 // chamber has a square footprint
|
||||
val chamberBackplateThickness = 15 // thickness of the backplate of the chamber
|
||||
// teflon disk
|
||||
val cathodeTeflonDiskHoleRadius = 15
|
||||
val cathodeTeflonDiskThickness = 5
|
||||
val cathodeCopperSupportOuterRadius = 45
|
||||
val cathodeCopperSupportInnerRadius = 8.5
|
||||
val cathodeCopperSupportThickness = 1.0
|
||||
// mylar cathode
|
||||
val mylarCathodeThickness = 0.004
|
||||
// patern
|
||||
val cathodePatternLineWidth = 0.3
|
||||
val cathodePatternDiskRadius = 4.25
|
||||
// readout
|
||||
val chamberTeflonWallThickness = 1
|
||||
val readoutKaptonThickness = 0.5
|
||||
val readoutCopperThickness = 0.2
|
||||
val readoutPlaneSide = 60
|
||||
|
||||
structure {
|
||||
val worldMaterial = materials.composite("G4_AIR")
|
||||
val worldBox = solids.box(worldSize, worldSize, worldSize, name = "world")
|
||||
|
||||
val shieldingMaterial = materials.composite("G4_Pb")
|
||||
val scintillatorMaterial = materials.composite("BC408")
|
||||
val captureMaterial = materials.composite("G4_Cd")
|
||||
|
||||
// chamber
|
||||
val chamberHeight = 30 // length of the chamber
|
||||
val chamberDiameter = 102 // inner diameter of the copper chamber
|
||||
val chamberOuterSquareSide = 134 // chamber has a square footprint
|
||||
val chamberBackplateThickness = 15 // thickness of the backplate of the chamber
|
||||
// teflon disk
|
||||
val cathodeTeflonDiskHoleRadius = 15
|
||||
val cathodeTeflonDiskThickness = 5
|
||||
val cathodeCopperSupportOuterRadius = 45
|
||||
val cathodeCopperSupportInnerRadius = 8.5
|
||||
val cathodeCopperSupportThickness = 1.0
|
||||
// mylar cathode
|
||||
val mylarCathodeThickness = 0.004
|
||||
// patern
|
||||
val cathodePatternLineWidth = 0.3
|
||||
val cathodePatternDiskRadius = 4.25
|
||||
// readout
|
||||
val chamberTeflonWallThickness = 1
|
||||
val readoutKaptonThickness = 0.5
|
||||
val readoutCopperThickness = 0.2
|
||||
val readoutPlaneSide = 60
|
||||
|
||||
structure {
|
||||
val worldMaterial = materials.composite("G4_AIR")
|
||||
val worldBox = solids.box(worldSize, worldSize, worldSize, name = "world")
|
||||
|
||||
val shieldingMaterial = materials.composite("G4_Pb")
|
||||
val scintillatorMaterial = materials.composite("BC408")
|
||||
val captureMaterial = materials.composite("G4_Cd")
|
||||
|
||||
// chamber
|
||||
val copperMaterial = materials.composite("G4_Cu")
|
||||
val chamberSolidBase = solids.box(chamberOuterSquareSide, chamberOuterSquareSide, chamberHeight)
|
||||
val chamberSolidHole = solids.tube(chamberDiameter / 2, chamberHeight)
|
||||
val chamberSolid = solids.subtraction(chamberSolidBase, chamberSolidHole)
|
||||
val chamberBodyVolume = volume(copperMaterial, chamberSolid)
|
||||
val chamberBackplateSolid =
|
||||
solids.box(chamberOuterSquareSide, chamberOuterSquareSide, chamberBackplateThickness)
|
||||
val chamberBackplateVolume = volume(copperMaterial, chamberBackplateSolid)
|
||||
// chamber teflon walls
|
||||
val teflonMaterial = materials.composite("G4_TEFLON")
|
||||
val chamberTeflonWallSolid = solids.tube(chamberDiameter / 2, chamberHeight) {
|
||||
rmin = chamberDiameter / 2.0 - chamberTeflonWallThickness
|
||||
}
|
||||
val chamberTeflonWallVolume = volume(teflonMaterial, chamberTeflonWallSolid)
|
||||
// cathode
|
||||
val cathodeCopperDiskMaterial = materials.composite("G4_Cu")
|
||||
val cathodeWindowMaterial = materials.composite("G4_MYLAR")
|
||||
|
||||
val cathodeTeflonDiskSolidBase =
|
||||
solids.tube(chamberOuterSquareSide / 2, cathodeTeflonDiskThickness) {
|
||||
rmin = cathodeTeflonDiskHoleRadius
|
||||
}
|
||||
val cathodeCopperDiskSolid =
|
||||
solids.tube(cathodeCopperSupportOuterRadius, cathodeCopperSupportThickness) {
|
||||
rmin = cathodeCopperSupportInnerRadius
|
||||
}
|
||||
|
||||
val cathodeTeflonDiskSolid = solids.subtraction(cathodeTeflonDiskSolidBase, cathodeCopperDiskSolid)
|
||||
val cathodeTeflonDiskVolume = volume(teflonMaterial, cathodeTeflonDiskSolid)
|
||||
|
||||
val cathodeWindowSolid = solids.tube(cathodeTeflonDiskHoleRadius, mylarCathodeThickness)
|
||||
val cathodeWindowVolume = volume(cathodeWindowMaterial, cathodeWindowSolid)
|
||||
|
||||
val cathodeFillingMaterial = materials.composite("G4_Galactic")
|
||||
val cathodeFillingSolidBase = solids.tube(cathodeTeflonDiskHoleRadius, cathodeTeflonDiskThickness)
|
||||
|
||||
val cathodeFillingSolid = solids.subtraction(cathodeFillingSolidBase, cathodeCopperDiskSolid) {
|
||||
position(z = chamberHeight / 2 - mylarCathodeThickness / 2)
|
||||
}
|
||||
val cathodeFillingVolume = volume(cathodeFillingMaterial, cathodeFillingSolid)
|
||||
|
||||
// pattern
|
||||
val cathodePatternLineAux = solids.box(
|
||||
cathodePatternLineWidth,
|
||||
cathodeCopperSupportInnerRadius * 2,
|
||||
cathodeCopperSupportThickness
|
||||
)
|
||||
val cathodePatternCentralHole = solids.tube(
|
||||
cathodePatternDiskRadius - 0 * cathodePatternLineWidth,
|
||||
cathodeCopperSupportThickness * 1.1
|
||||
)
|
||||
val cathodePatternLine = solids.subtraction(cathodePatternLineAux, cathodePatternCentralHole)
|
||||
|
||||
val cathodePatternDisk = solids.tube(
|
||||
cathodePatternDiskRadius,
|
||||
cathodeCopperSupportThickness
|
||||
) { rmin = cathodePatternDiskRadius - cathodePatternLineWidth }
|
||||
|
||||
|
||||
val cathodeCopperDiskSolidAux0 =
|
||||
solids.union(cathodeCopperDiskSolid, cathodePatternLine) {
|
||||
rotation(x = 0, y = 0, z = 0)
|
||||
}
|
||||
val cathodeCopperDiskSolidAux1 =
|
||||
solids.union(cathodeCopperDiskSolidAux0, cathodePatternLine) {
|
||||
rotation = GdmlRotation(
|
||||
"cathodePatternRotation1", x = 0, y = 0, z = 45
|
||||
)
|
||||
}
|
||||
val cathodeCopperDiskSolidAux2 =
|
||||
solids.union(cathodeCopperDiskSolidAux1, cathodePatternLine) {
|
||||
rotation = GdmlRotation(
|
||||
"cathodePatternRotation2", x = 0, y = 0, z = 90
|
||||
)
|
||||
}
|
||||
val cathodeCopperDiskSolidAux3 =
|
||||
solids.union(cathodeCopperDiskSolidAux2, cathodePatternLine) {
|
||||
rotation = GdmlRotation(
|
||||
"cathodePatternRotation3", x = 0, y = 0, z = 135
|
||||
)
|
||||
}
|
||||
|
||||
val cathodeCopperDiskFinal =
|
||||
solids.union(cathodeCopperDiskSolidAux3, cathodePatternDisk)
|
||||
|
||||
|
||||
val cathodeCopperDiskVolume =
|
||||
volume(cathodeCopperDiskMaterial, cathodeCopperDiskFinal)
|
||||
|
||||
val gasSolidOriginal = solids.tube(
|
||||
chamberDiameter / 2 - chamberTeflonWallThickness,
|
||||
chamberHeight
|
||||
)
|
||||
|
||||
val kaptonReadoutMaterial = materials.composite("G4_KAPTON")
|
||||
val kaptonReadoutSolid = solids.box(
|
||||
chamberOuterSquareSide,
|
||||
chamberOuterSquareSide,
|
||||
readoutKaptonThickness)
|
||||
val kaptonReadoutVolume = volume( kaptonReadoutMaterial, kaptonReadoutSolid)
|
||||
|
||||
val copperReadoutSolid =
|
||||
solids.box(readoutPlaneSide, readoutPlaneSide, readoutCopperThickness)
|
||||
val copperReadoutVolume = volume(copperMaterial, copperReadoutSolid)
|
||||
|
||||
val gasSolidAux =
|
||||
solids.subtraction(gasSolidOriginal, copperReadoutSolid) {
|
||||
position(z = -chamberHeight / 2 + readoutCopperThickness / 2)
|
||||
}
|
||||
|
||||
val gasMaterial = materials.composite("G4_Ar")
|
||||
val gasSolid =
|
||||
solids.subtraction( gasSolidAux, cathodeWindowSolid) {
|
||||
position(z = chamberHeight / 2 - mylarCathodeThickness / 2)
|
||||
rotation(z = 45)
|
||||
}
|
||||
val gasVolume = volume(gasMaterial, gasSolid)
|
||||
|
||||
// world setup
|
||||
world = volume(worldMaterial, worldBox) {
|
||||
physVolume(gasVolume) {
|
||||
name = "gas"
|
||||
}
|
||||
physVolume(kaptonReadoutVolume) {
|
||||
name = "kaptonReadout"
|
||||
position {
|
||||
z = -chamberHeight / 2 - readoutKaptonThickness / 2
|
||||
}
|
||||
}
|
||||
physVolume(copperReadoutVolume) {
|
||||
name = "copperReadout"
|
||||
position {
|
||||
z = -chamberHeight / 2 + readoutCopperThickness / 2
|
||||
}
|
||||
rotation { z = 45 }
|
||||
}
|
||||
physVolume(chamberBodyVolume) {
|
||||
name = "chamberBody"
|
||||
}
|
||||
physVolume(chamberBackplateVolume) {
|
||||
name = "chamberBackplate"
|
||||
position {
|
||||
z = -chamberHeight / 2 - readoutKaptonThickness - chamberBackplateThickness / 2
|
||||
}
|
||||
}
|
||||
physVolume(chamberTeflonWallVolume) {
|
||||
name = "chamberTeflonWall"
|
||||
}
|
||||
physVolume(cathodeTeflonDiskVolume) {
|
||||
name = "cathodeTeflonDisk"
|
||||
position {
|
||||
z = chamberHeight / 2 + cathodeTeflonDiskThickness / 2
|
||||
}
|
||||
}
|
||||
physVolume(cathodeCopperDiskVolume) {
|
||||
name = "cathodeCopperDisk"
|
||||
position {
|
||||
z = chamberHeight / 2 + cathodeCopperSupportThickness / 2
|
||||
}
|
||||
}
|
||||
physVolume(cathodeWindowVolume) {
|
||||
name = "cathodeWindow"
|
||||
position {
|
||||
z = chamberHeight / 2 - mylarCathodeThickness / 2
|
||||
}
|
||||
}
|
||||
physVolume(cathodeFillingVolume) {
|
||||
name = "cathodeFilling"
|
||||
position {
|
||||
z = chamberHeight / 2 + cathodeTeflonDiskThickness / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
val copperMaterial = materials.composite("G4_Cu")
|
||||
val chamberSolidBase = solids.box(chamberOuterSquareSide, chamberOuterSquareSide, chamberHeight)
|
||||
val chamberSolidHole = solids.tube(chamberDiameter / 2, chamberHeight)
|
||||
val chamberSolid = solids.subtraction(chamberSolidBase, chamberSolidHole)
|
||||
val chamberBodyVolume = volume(copperMaterial, chamberSolid)
|
||||
val chamberBackplateSolid =
|
||||
solids.box(chamberOuterSquareSide, chamberOuterSquareSide, chamberBackplateThickness)
|
||||
val chamberBackplateVolume = volume(copperMaterial, chamberBackplateSolid)
|
||||
// chamber teflon walls
|
||||
val teflonMaterial = materials.composite("G4_TEFLON")
|
||||
val chamberTeflonWallSolid = solids.tube(chamberDiameter / 2, chamberHeight) {
|
||||
rmin = chamberDiameter / 2.0 - chamberTeflonWallThickness
|
||||
}
|
||||
}.toVision {
|
||||
solids { _, solid, _ ->
|
||||
//disable visibility for the world box
|
||||
if(solid.name == "world"){
|
||||
visible = false
|
||||
val chamberTeflonWallVolume = volume(teflonMaterial, chamberTeflonWallSolid)
|
||||
// cathode
|
||||
val cathodeCopperDiskMaterial = materials.composite("G4_Cu")
|
||||
val cathodeWindowMaterial = materials.composite("G4_MYLAR")
|
||||
|
||||
val cathodeTeflonDiskSolidBase =
|
||||
solids.tube(chamberOuterSquareSide / 2, cathodeTeflonDiskThickness) {
|
||||
rmin = cathodeTeflonDiskHoleRadius
|
||||
}
|
||||
if(solid.name.startsWith("gas")){
|
||||
color("green")
|
||||
} else {
|
||||
//make all solids semi-transparent
|
||||
transparent()
|
||||
val cathodeCopperDiskSolid =
|
||||
solids.tube(cathodeCopperSupportOuterRadius, cathodeCopperSupportThickness) {
|
||||
rmin = cathodeCopperSupportInnerRadius
|
||||
}
|
||||
|
||||
val cathodeTeflonDiskSolid = solids.subtraction(cathodeTeflonDiskSolidBase, cathodeCopperDiskSolid)
|
||||
val cathodeTeflonDiskVolume = volume(teflonMaterial, cathodeTeflonDiskSolid)
|
||||
|
||||
val cathodeWindowSolid = solids.tube(cathodeTeflonDiskHoleRadius, mylarCathodeThickness)
|
||||
val cathodeWindowVolume = volume(cathodeWindowMaterial, cathodeWindowSolid)
|
||||
|
||||
val cathodeFillingMaterial = materials.composite("G4_Galactic")
|
||||
val cathodeFillingSolidBase = solids.tube(cathodeTeflonDiskHoleRadius, cathodeTeflonDiskThickness)
|
||||
|
||||
val cathodeFillingSolid = solids.subtraction(cathodeFillingSolidBase, cathodeCopperDiskSolid) {
|
||||
position(z = chamberHeight / 2 - mylarCathodeThickness / 2)
|
||||
}
|
||||
val cathodeFillingVolume = volume(cathodeFillingMaterial, cathodeFillingSolid)
|
||||
|
||||
// pattern
|
||||
val cathodePatternLineAux = solids.box(
|
||||
cathodePatternLineWidth,
|
||||
cathodeCopperSupportInnerRadius * 2,
|
||||
cathodeCopperSupportThickness
|
||||
)
|
||||
val cathodePatternCentralHole = solids.tube(
|
||||
cathodePatternDiskRadius - 0 * cathodePatternLineWidth,
|
||||
cathodeCopperSupportThickness * 1.1
|
||||
)
|
||||
val cathodePatternLine = solids.subtraction(cathodePatternLineAux, cathodePatternCentralHole)
|
||||
|
||||
val cathodePatternDisk = solids.tube(
|
||||
cathodePatternDiskRadius,
|
||||
cathodeCopperSupportThickness
|
||||
) { rmin = cathodePatternDiskRadius - cathodePatternLineWidth }
|
||||
|
||||
|
||||
val cathodeCopperDiskSolidAux0 =
|
||||
solids.union(cathodeCopperDiskSolid, cathodePatternLine) {
|
||||
rotation(x = 0, y = 0, z = 0)
|
||||
}
|
||||
val cathodeCopperDiskSolidAux1 =
|
||||
solids.union(cathodeCopperDiskSolidAux0, cathodePatternLine) {
|
||||
rotation = GdmlRotation(
|
||||
"cathodePatternRotation1", x = 0, y = 0, z = 45
|
||||
)
|
||||
}
|
||||
val cathodeCopperDiskSolidAux2 =
|
||||
solids.union(cathodeCopperDiskSolidAux1, cathodePatternLine) {
|
||||
rotation = GdmlRotation(
|
||||
"cathodePatternRotation2", x = 0, y = 0, z = 90
|
||||
)
|
||||
}
|
||||
val cathodeCopperDiskSolidAux3 =
|
||||
solids.union(cathodeCopperDiskSolidAux2, cathodePatternLine) {
|
||||
rotation = GdmlRotation(
|
||||
"cathodePatternRotation3", x = 0, y = 0, z = 135
|
||||
)
|
||||
}
|
||||
|
||||
val cathodeCopperDiskFinal =
|
||||
solids.union(cathodeCopperDiskSolidAux3, cathodePatternDisk)
|
||||
|
||||
|
||||
val cathodeCopperDiskVolume =
|
||||
volume(cathodeCopperDiskMaterial, cathodeCopperDiskFinal)
|
||||
|
||||
val gasSolidOriginal = solids.tube(
|
||||
chamberDiameter / 2 - chamberTeflonWallThickness,
|
||||
chamberHeight
|
||||
)
|
||||
|
||||
val kaptonReadoutMaterial = materials.composite("G4_KAPTON")
|
||||
val kaptonReadoutSolid = solids.box(
|
||||
chamberOuterSquareSide,
|
||||
chamberOuterSquareSide,
|
||||
readoutKaptonThickness
|
||||
)
|
||||
val kaptonReadoutVolume = volume(kaptonReadoutMaterial, kaptonReadoutSolid)
|
||||
|
||||
val copperReadoutSolid =
|
||||
solids.box(readoutPlaneSide, readoutPlaneSide, readoutCopperThickness)
|
||||
val copperReadoutVolume = volume(copperMaterial, copperReadoutSolid)
|
||||
|
||||
val gasSolidAux =
|
||||
solids.subtraction(gasSolidOriginal, copperReadoutSolid) {
|
||||
position(z = -chamberHeight / 2 + readoutCopperThickness / 2)
|
||||
}
|
||||
|
||||
val gasMaterial = materials.composite("G4_Ar")
|
||||
val gasSolid =
|
||||
solids.subtraction(gasSolidAux, cathodeWindowSolid) {
|
||||
position(z = chamberHeight / 2 - mylarCathodeThickness / 2)
|
||||
rotation(z = 45)
|
||||
}
|
||||
val gasVolume = volume(gasMaterial, gasSolid)
|
||||
|
||||
// world setup
|
||||
world = volume(worldMaterial, worldBox) {
|
||||
physVolume(gasVolume) {
|
||||
name = "gas"
|
||||
}
|
||||
physVolume(kaptonReadoutVolume) {
|
||||
name = "kaptonReadout"
|
||||
position {
|
||||
z = -chamberHeight / 2 - readoutKaptonThickness / 2
|
||||
}
|
||||
}
|
||||
physVolume(copperReadoutVolume) {
|
||||
name = "copperReadout"
|
||||
position {
|
||||
z = -chamberHeight / 2 + readoutCopperThickness / 2
|
||||
}
|
||||
rotation { z = 45 }
|
||||
}
|
||||
physVolume(chamberBodyVolume) {
|
||||
name = "chamberBody"
|
||||
}
|
||||
physVolume(chamberBackplateVolume) {
|
||||
name = "chamberBackplate"
|
||||
position {
|
||||
z = -chamberHeight / 2 - readoutKaptonThickness - chamberBackplateThickness / 2
|
||||
}
|
||||
}
|
||||
physVolume(chamberTeflonWallVolume) {
|
||||
name = "chamberTeflonWall"
|
||||
}
|
||||
physVolume(cathodeTeflonDiskVolume) {
|
||||
name = "cathodeTeflonDisk"
|
||||
position {
|
||||
z = chamberHeight / 2 + cathodeTeflonDiskThickness / 2
|
||||
}
|
||||
}
|
||||
physVolume(cathodeCopperDiskVolume) {
|
||||
name = "cathodeCopperDisk"
|
||||
position {
|
||||
z = chamberHeight / 2 + cathodeCopperSupportThickness / 2
|
||||
}
|
||||
}
|
||||
physVolume(cathodeWindowVolume) {
|
||||
name = "cathodeWindow"
|
||||
position {
|
||||
z = chamberHeight / 2 - mylarCathodeThickness / 2
|
||||
}
|
||||
}
|
||||
physVolume(cathodeFillingVolume) {
|
||||
name = "cathodeFilling"
|
||||
position {
|
||||
z = chamberHeight / 2 + cathodeTeflonDiskThickness / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.toVision {
|
||||
solids { _, solid, _ ->
|
||||
//disable visibility for the world box
|
||||
if (solid.name == "world") {
|
||||
visible = false
|
||||
}
|
||||
if (solid.name.startsWith("gas")) {
|
||||
color("green")
|
||||
} else {
|
||||
//make all solids semi-transparent
|
||||
transparent()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package space.kscience.visionforge.examples
|
||||
|
||||
import space.kscience.gdml.GdmlShowCase
|
||||
import space.kscience.visionforge.gdml.gdml
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.solid.Solids
|
||||
import space.kscience.visionforge.solid.solid
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package space.kscience.visionforge.examples
|
||||
|
||||
import space.kscience.visionforge.html.ResourceLocation
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.markup.markdown
|
||||
|
||||
fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import space.kscience.plotly.Plotly
|
||||
import space.kscience.plotly.layout
|
||||
import space.kscience.plotly.models.*
|
||||
import space.kscience.visionforge.html.ResourceLocation
|
||||
import space.kscience.visionforge.html.vision
|
||||
|
||||
fun main() = makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
|
||||
vision {
|
||||
|
||||
@@ -4,6 +4,7 @@ import kotlinx.html.div
|
||||
import kotlinx.html.h1
|
||||
import space.kscience.visionforge.Colors
|
||||
import space.kscience.visionforge.html.ResourceLocation
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.solid.*
|
||||
import java.nio.file.Paths
|
||||
import kotlin.random.Random
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package space.kscience.visionforge.examples
|
||||
|
||||
import space.kscience.visionforge.Colors
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.solid.*
|
||||
import kotlin.math.PI
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package space.kscience.visionforge.examples
|
||||
|
||||
import space.kscience.visionforge.html.ResourceLocation
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.solid.box
|
||||
import space.kscience.visionforge.solid.invoke
|
||||
import space.kscience.visionforge.solid.material
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package space.kscience.visionforge.examples
|
||||
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.solid.ambientLight
|
||||
import space.kscience.visionforge.solid.polygon
|
||||
import space.kscience.visionforge.solid.solid
|
||||
|
||||
@@ -4,6 +4,7 @@ import space.kscience.dataforge.meta.ValueType
|
||||
import space.kscience.tables.ColumnHeader
|
||||
import space.kscience.tables.valueRow
|
||||
import space.kscience.visionforge.html.ResourceLocation
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.tables.table
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import space.kscience.dataforge.names.Name
|
||||
import space.kscience.visionforge.Colors
|
||||
import space.kscience.visionforge.html.VisionPage
|
||||
import space.kscience.visionforge.html.meta
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.server.close
|
||||
import space.kscience.visionforge.server.openInBrowser
|
||||
import space.kscience.visionforge.server.visionPage
|
||||
|
||||
@@ -4,6 +4,7 @@ import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.request
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.visionforge.html.ResourceLocation
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.solid.*
|
||||
import space.kscience.visionforge.three.makeThreeJsFile
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
package space.kscience.visionforge.solid.demo
|
||||
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.*
|
||||
import space.kscience.visionforge.html.startApplication
|
||||
import space.kscience.visionforge.solid.x
|
||||
import space.kscience.visionforge.solid.y
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun main() {
|
||||
startApplication { document ->
|
||||
val element = document.getElementById("demo") ?: error("Element with id 'demo' not found on page")
|
||||
|
||||
@@ -8,4 +8,4 @@ org.gradle.workers.max=4
|
||||
org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
|
||||
kotlin.native.enableKlibsCrossCompilation=true
|
||||
|
||||
toolsVersion=0.17.1-kotlin-2.1.20
|
||||
toolsVersion=0.19.2-kotlin-2.2.20
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Removed
|
||||
|
||||
### Fixed
|
||||
- Fix legend orientation constants
|
||||
|
||||
### Security
|
||||
|
||||
|
||||
@@ -15,12 +15,15 @@ dependencies {
|
||||
}
|
||||
|
||||
kotlin{
|
||||
jvmToolchain(17)
|
||||
jvmToolchain(21)
|
||||
compilerOptions {
|
||||
freeCompilerArgs.add("-Xcontext-parameters")
|
||||
}
|
||||
}
|
||||
|
||||
// A workaround for https://youtrack.jetbrains.com/issue/KT-44101
|
||||
|
||||
val copyPlotlyResources by tasks.creating(Copy::class){
|
||||
val copyPlotlyResources by tasks.registering(Copy::class){
|
||||
dependsOn(":plotly-kt:plotly-kt-server:jvmProcessResources")
|
||||
mustRunAfter(":plotly-kt:plotly-kt-server:jvmTestProcessResources")
|
||||
from(project(":plotly-kt:plotly-kt-server").layout.buildDirectory.file("processedResources/jvm/main"))
|
||||
|
||||
@@ -13,16 +13,17 @@ import space.kscience.plotly.models.geo.json.GeoJsonFeatureCollection
|
||||
import space.kscience.plotly.models.geo.json.combine
|
||||
import space.kscience.plotly.models.geo.openStreetMap
|
||||
import space.kscience.plotly.plot
|
||||
import space.kscience.visionforge.html.HtmlVisionContext
|
||||
import space.kscience.visionforge.plotly.serveSinglePage
|
||||
import space.kscience.visionforge.server.openInBrowser
|
||||
import java.net.URL
|
||||
import java.net.URI
|
||||
import kotlin.random.Random
|
||||
|
||||
suspend fun main() {
|
||||
|
||||
//downloading GeoJson
|
||||
val geoJsonString =
|
||||
URL("https://raw.githubusercontent.com/isellsoap/deutschlandGeoJSON/main/4_kreise/4_niedrig.geo.json").readText()
|
||||
URI("https://raw.githubusercontent.com/isellsoap/deutschlandGeoJSON/main/4_kreise/4_niedrig.geo.json").toURL().readText()
|
||||
|
||||
|
||||
// Filtering GeoJson features and creating new feature set
|
||||
@@ -50,7 +51,7 @@ suspend fun main() {
|
||||
locations.numbers = features.map { it.id!!.int }
|
||||
// Set random values to locations
|
||||
z.numbers = features.map { Random.nextDouble(1.0, 10.0) }
|
||||
context.launch {
|
||||
contextOf<HtmlVisionContext>().context.launch {
|
||||
while (isActive) {
|
||||
delay(300)
|
||||
z.numbers = features.map { Random.nextDouble(1.0, 10.0) }
|
||||
|
||||
@@ -10,7 +10,7 @@ import space.kscience.plotly.models.geo.json.GeoJsonFeatureCollection
|
||||
import space.kscience.plotly.models.geo.json.combine
|
||||
import space.kscience.plotly.models.geo.openStreetMap
|
||||
import space.kscience.plotly.openInBrowser
|
||||
import java.net.URL
|
||||
import java.net.URI
|
||||
import kotlin.random.Random
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@ fun main() {
|
||||
|
||||
//downloading GeoJson
|
||||
val geoJsonString =
|
||||
URL("https://raw.githubusercontent.com/isellsoap/deutschlandGeoJSON/main/4_kreise/4_niedrig.geo.json").readText()
|
||||
URI("https://raw.githubusercontent.com/isellsoap/deutschlandGeoJSON/main/4_kreise/4_niedrig.geo.json")
|
||||
.toURL()
|
||||
.readText()
|
||||
|
||||
|
||||
// Filtering GeoJson features and creating new feature set
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id("space.kscience.gradle.mpp")
|
||||
kotlin("jupyter.api")
|
||||
alias(spclibs.plugins.kotlin.jupyter.api)
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
@@ -31,9 +31,9 @@ kscience {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.processJupyterApiResources {
|
||||
libraryProducers = listOf("space.kscience.plotly.PlotlyIntegration")
|
||||
}
|
||||
//tasks.processJupyterApiResources {
|
||||
// libraryProducers = listOf("space.kscience.plotly.PlotlyIntegration")
|
||||
//}
|
||||
|
||||
|
||||
kotlin {
|
||||
|
||||
@@ -80,8 +80,9 @@ public class Plot : AbstractVision(), MutableVisionGroup<Trace> {
|
||||
*/
|
||||
@UnstablePlotlyAPI
|
||||
@JvmSynchronized
|
||||
internal fun removeTrace(index: Int) {
|
||||
public fun removeTrace(index: Int) {
|
||||
_data.removeAt(index)
|
||||
emitEvent(VisionGroupCompositionChangedEvent(NameToken("trace", _data.size.toString()), null))
|
||||
}
|
||||
|
||||
override val descriptor: MetaDescriptor get() = Companion.descriptor
|
||||
|
||||
@@ -8,9 +8,12 @@ import space.kscience.dataforge.context.ContextAware
|
||||
import space.kscience.dataforge.context.request
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.plotly.models.Trace
|
||||
import space.kscience.visionforge.VisionBuilder
|
||||
import space.kscience.visionforge.html.*
|
||||
import space.kscience.visionforge.html.HtmlVisionContext
|
||||
import space.kscience.visionforge.html.VisionOutput
|
||||
import space.kscience.visionforge.html.vision
|
||||
import kotlin.js.JsName
|
||||
|
||||
/**
|
||||
@@ -65,6 +68,21 @@ public class PlotlyConfig : Scheme() {
|
||||
public var responsive: Boolean? by boolean()
|
||||
public var imageFormat: String? by string(Name.parse("toImageButtonOptions.format"))
|
||||
|
||||
/**
|
||||
* A list of class names applied to the output `div` in the generated HTML for the plot.
|
||||
*
|
||||
* This property allows customization of the CSS classes assigned to the `div` element
|
||||
* that contains the rendered plot. It can be utilized to add custom styling or specific
|
||||
* class-based behaviors to the output.
|
||||
*
|
||||
* By default, this property is initialized as an empty list and can be updated to include
|
||||
* necessary class names as strings.
|
||||
*/
|
||||
public var classes: List<String> by stringList(
|
||||
default = emptyArray(),
|
||||
key = HtmlVisionContext.OUTPUT_DIV_CLASSES_KEY.asName()
|
||||
)
|
||||
|
||||
public fun withEditorButton() {
|
||||
showEditInChartStudio = true
|
||||
plotlyServerURL = "https://chart-studio.plotly.com"
|
||||
@@ -92,12 +110,10 @@ public inline fun VisionOutput.plotly(
|
||||
return Plotly.plot(block)
|
||||
}
|
||||
|
||||
context(rootConsumer: VisionTagConsumer<*>)
|
||||
public fun TagConsumer<*>.plot(
|
||||
context(htmlContext: HtmlVisionContext)
|
||||
public fun <T> TagConsumer<T>.plot(
|
||||
config: PlotlyConfig = PlotlyConfig(),
|
||||
block: Plot.() -> Unit,
|
||||
): Unit = with(rootConsumer) {
|
||||
vision {
|
||||
plotly(config, block)
|
||||
}
|
||||
}
|
||||
): T = vision {
|
||||
plotly(config, block)
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ public fun TagConsumer<*>.staticPlot(
|
||||
config: PlotlyConfig = PlotlyConfig(),
|
||||
plotId: String = "plotly[${Uuid.random()}]",
|
||||
plot: Plot.() -> Unit
|
||||
) = staticPlot(Plotly.plot(plot), config, plotId)
|
||||
): Unit = staticPlot(Plotly.plot(plot), config, plotId)
|
||||
|
||||
/**
|
||||
* Create an html (including headers) string from plot
|
||||
|
||||
@@ -6,14 +6,10 @@ import space.kscience.dataforge.meta.enum
|
||||
import space.kscience.dataforge.meta.scheme
|
||||
import space.kscience.plotly.numberGreaterThan
|
||||
import space.kscience.plotly.numberInRange
|
||||
import kotlin.js.JsName
|
||||
|
||||
public enum class LegendOrientation {
|
||||
@JsName("v")
|
||||
vertical,
|
||||
|
||||
@JsName("h")
|
||||
horizontal
|
||||
v,
|
||||
h
|
||||
}
|
||||
|
||||
public enum class XAnchor {
|
||||
@@ -103,7 +99,7 @@ public class Legend : Scheme() {
|
||||
* Sets the orientation of the legend (vertical/horizontal).
|
||||
* Default: vertical.
|
||||
*/
|
||||
public var orientation: LegendOrientation by enum(LegendOrientation.vertical)
|
||||
public var orientation: LegendOrientation by enum(LegendOrientation.v)
|
||||
|
||||
/**
|
||||
* The order at which the legend items are displayed.
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
package space.kscience.plotly
|
||||
|
||||
import kotlinx.browser.document
|
||||
import kotlinx.browser.window
|
||||
import org.w3c.dom.*
|
||||
import org.w3c.dom.url.URL
|
||||
import org.w3c.fetch.RequestInit
|
||||
import kotlin.js.Promise
|
||||
import kotlin.js.json
|
||||
|
||||
@JsExport
|
||||
public object PlotlyConnect {
|
||||
|
||||
/**
|
||||
* Wait for the Plotly library to be loaded
|
||||
*/
|
||||
private fun withPlotly(action: PlotlyJs.() -> Unit) {
|
||||
if (jsTypeOf(PlotlyJs) !== "undefined") {
|
||||
action(PlotlyJs);
|
||||
} else {
|
||||
val promiseOfPlotly: Promise<PlotlyJs> = window.asDynamic().promiseOfPlotly as Promise<PlotlyJs>
|
||||
if (jsTypeOf(promiseOfPlotly) != "undefined") {
|
||||
promiseOfPlotly.then { action(PlotlyJs) }
|
||||
} else {
|
||||
console.warn("Plotly not defined. Loading the script from CDN")
|
||||
window.asDynamic().promiseOfPlotly = Promise<PlotlyJs> { resolve, reject ->
|
||||
val plotlyLoaderScript = document.createElement("script") as HTMLScriptElement
|
||||
plotlyLoaderScript.src = "https://cdn.plot.ly/plotly-2.29.0.min.js"
|
||||
plotlyLoaderScript.type = "text/javascript"
|
||||
plotlyLoaderScript.onload = {
|
||||
resolve(PlotlyJs)
|
||||
action(PlotlyJs)
|
||||
}
|
||||
plotlyLoaderScript.onerror = { error, _, _, _, _ ->
|
||||
console.error(error);
|
||||
reject(error as Throwable)
|
||||
}
|
||||
document.head?.appendChild(plotlyLoaderScript);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Request and parse json from given address
|
||||
* @param url {URL}
|
||||
* @param callback
|
||||
*/
|
||||
private fun getJSON(url: URL, callback: (dynamic) -> Unit) {
|
||||
try {
|
||||
window.fetch(
|
||||
url,
|
||||
RequestInit(
|
||||
method = "GET",
|
||||
headers = json("Accept" to "application/json")
|
||||
)
|
||||
).then { response ->
|
||||
if (!response.ok) {
|
||||
error("Fetch request failed with error: ${response.statusText}")
|
||||
} else {
|
||||
response.json().then(callback)
|
||||
}
|
||||
}.catch { error -> console.log(error) }
|
||||
} catch (e: Exception) {
|
||||
window.alert("Fetch of plot data failed with error: $e")
|
||||
}
|
||||
}
|
||||
|
||||
public fun makePlot(
|
||||
graphDiv: Element,
|
||||
data: Array<dynamic>,
|
||||
layout: dynamic,
|
||||
config: dynamic,
|
||||
) {
|
||||
withPlotly { newPlot(graphDiv, data, layout, config) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a plot taking data from given url
|
||||
* @param id {string} element id for plot
|
||||
* @param from {URL} json server url
|
||||
* @param config {object} plotly configuration
|
||||
*/
|
||||
|
||||
public fun createPlotFrom(id: String, from: URL, config: dynamic = {}) {
|
||||
getJSON(from) { json ->
|
||||
val element = document.getElementById(id) as HTMLElement
|
||||
withPlotly { newPlot(element, json.data, json.layout, config) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a plot taking data from given url
|
||||
* @param id {string} element id for plot
|
||||
* @param from {URL} json server url
|
||||
* @return {JSON}
|
||||
*/
|
||||
public fun updatePlotFrom(id: String, from: URL) {
|
||||
getJSON(from) { json ->
|
||||
val element = document.getElementById(id) as HTMLElement
|
||||
withPlotly { react(element, json.data, json.layout) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start pull updates with regular requests from client side
|
||||
* @param id {string}
|
||||
* @param from
|
||||
* @param millis
|
||||
*/
|
||||
public fun startPull(id: String, from: URL, millis: Int) {
|
||||
window.setInterval({ updatePlotFrom(id, from) }, millis)
|
||||
}
|
||||
|
||||
/**
|
||||
* Start push updates via websocket
|
||||
* @param id {string} element id for plot
|
||||
* @param ws {URL} a websocket address
|
||||
*/
|
||||
public fun startPush(id: String, ws: String) {
|
||||
val element = document.getElementById(id) as HTMLElement
|
||||
val socket = WebSocket(ws)
|
||||
|
||||
socket.onopen = {
|
||||
console.log("[Plotly.kt] A connection for plot with id = $id with server established on $ws")
|
||||
}
|
||||
|
||||
socket.onclose = { event ->
|
||||
event as CloseEvent
|
||||
if (event.wasClean) {
|
||||
console.log("The connection with server is closed")
|
||||
} else {
|
||||
console.log("The connection with server is broken") // Server process is dead
|
||||
}
|
||||
console.log("Code: ${event.code} case: ${event.reason}")
|
||||
}
|
||||
|
||||
socket.onerror = { error ->
|
||||
error as ErrorEvent
|
||||
console.error("Plotly push update error: " + error.message)
|
||||
socket.close()
|
||||
}
|
||||
|
||||
socket.onmessage = { event ->
|
||||
val json: dynamic = JSON.parse(event.data.toString())
|
||||
if (json.plotId === id) {
|
||||
if (json.contentType === "layout") {
|
||||
withPlotly { relayout(element, json.content) }
|
||||
} else if (json.contentType === "trace") {
|
||||
withPlotly { restyle(element, json.content, json.trace) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//gracefully close the socket just in case
|
||||
window.onbeforeunload = {
|
||||
console.log("Gracefully closing socket")
|
||||
socket.close()
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun main() {
|
||||
window.asDynamic().plotlyConnect = PlotlyConnect
|
||||
window.asDynamic().Plotly = PlotlyJs
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package space.kscience.plotly
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
@@ -32,7 +33,11 @@ private fun List<MetaRepr>.toDynamic(): Array<dynamic> = map { it.toDynamic() }.
|
||||
* Attach a plot to this element or update the existing plot
|
||||
*/
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
public fun Element.plot(plotlyConfig: PlotlyConfig = PlotlyConfig(), plot: Plot) {
|
||||
public fun Element.plot(
|
||||
plotlyConfig: PlotlyConfig,
|
||||
plot: Plot,
|
||||
scope: CoroutineScope = plot.manager?.context ?: GlobalScope
|
||||
) {
|
||||
|
||||
// console.info("""
|
||||
// Plotly.react(
|
||||
@@ -51,7 +56,7 @@ public fun Element.plot(plotlyConfig: PlotlyConfig = PlotlyConfig(), plot: Plot)
|
||||
)
|
||||
|
||||
//start updates
|
||||
val listenJob = (plot.manager?.context ?: GlobalScope).launch {
|
||||
val listenJob = scope.launch {
|
||||
plot.data.forEachIndexed { index, trace ->
|
||||
trace.eventFlow.filterIsInstance<VisionPropertyChangedEvent>().onEach { event ->
|
||||
val traceData = trace.toDynamic()
|
||||
@@ -92,27 +97,34 @@ public fun Element.plot(plot: Plot, plotlyConfig: PlotlyConfig = PlotlyConfig())
|
||||
/**
|
||||
* Create a plot in this element
|
||||
*/
|
||||
public inline fun Element.plot(plotlyConfig: PlotlyConfig = PlotlyConfig(), plotBuilder: Plot.() -> Unit) {
|
||||
plot(plotlyConfig, Plot().apply(plotBuilder))
|
||||
public inline fun Element.plot(
|
||||
scope: CoroutineScope,
|
||||
plotlyConfig: PlotlyConfig = PlotlyConfig(),
|
||||
plotBuilder: Plot.() -> Unit
|
||||
) {
|
||||
plot(plotlyConfig, Plot().apply(plotBuilder), scope)
|
||||
}
|
||||
|
||||
public class PlotlyElement(public val div: HTMLElement)
|
||||
|
||||
/**
|
||||
* Create a div element and render plot in it
|
||||
* Create a div element and render the plot in it
|
||||
*/
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
public fun TagConsumer<HTMLElement>.plotDiv(
|
||||
plotlyConfig: PlotlyConfig = PlotlyConfig(),
|
||||
plotlyConfig: PlotlyConfig,
|
||||
plot: Plot,
|
||||
scope: CoroutineScope = plot.manager?.context ?: GlobalScope,
|
||||
): PlotlyElement = PlotlyElement(div("plotly-kt-plot").apply { plot(plotlyConfig, plot) })
|
||||
|
||||
/**
|
||||
* Render plot in the HTML element using direct plotly API.
|
||||
*/
|
||||
public inline fun TagConsumer<HTMLElement>.plotDiv(
|
||||
scope: CoroutineScope,
|
||||
plotlyConfig: PlotlyConfig = PlotlyConfig(),
|
||||
plotBuilder: Plot.() -> Unit,
|
||||
): PlotlyElement = PlotlyElement(div("plotly-kt-plot").apply { plot(plotlyConfig, plotBuilder) })
|
||||
): PlotlyElement = PlotlyElement(div("plotly-kt-plot").apply { plot(scope, plotlyConfig, plotBuilder) })
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
public fun PlotlyElement.on(eventType: PlotlyEventListenerType, block: MouseEvent.(PlotlyEvent) -> Unit) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package space.kscience.plotly
|
||||
|
||||
import kotlinx.browser.window
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import org.w3c.dom.Element
|
||||
import space.kscience.dataforge.context.Context
|
||||
@@ -12,6 +13,7 @@ import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionPlugin
|
||||
import space.kscience.visionforge.html.ElementVisionRenderer
|
||||
import space.kscience.visionforge.html.JsVisionClient
|
||||
import space.kscience.visionforge.html.renderAllVisions
|
||||
|
||||
public class PlotlyJsPlugin : VisionPlugin(), ElementVisionRenderer {
|
||||
public val plotly: PlotlyPlugin by require(PlotlyPlugin)
|
||||
@@ -39,10 +41,25 @@ public class PlotlyJsPlugin : VisionPlugin(), ElementVisionRenderer {
|
||||
else -> super.content(target)
|
||||
}
|
||||
|
||||
public companion object : PluginFactory<PlotlyJsPlugin> {
|
||||
public companion object : PluginFactory<PlotlyJsPlugin> {
|
||||
override val tag: PluginTag = PluginTag("vision.plotly.js", PluginTag.DATAFORGE_GROUP)
|
||||
|
||||
override fun build(context: Context, meta: Meta): PlotlyJsPlugin = PlotlyJsPlugin()
|
||||
|
||||
public val defaultClient: JsVisionClient by lazy {
|
||||
val context = Context("Plotly-kt") {
|
||||
plugin(PlotlyJsPlugin)
|
||||
}
|
||||
context.plugins[PlotlyJsPlugin]!!.visionClient
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public fun main() {
|
||||
window.asDynamic().Plotly = PlotlyJs
|
||||
window.asDynamic().renderAllPlots = {
|
||||
PlotlyJsPlugin.defaultClient.renderAllVisions()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package space.kscience.plotly
|
||||
|
||||
import kotlinx.html.script
|
||||
import kotlinx.html.unsafe
|
||||
import space.kscience.visionforge.html.*
|
||||
import space.kscience.visionforge.visionManager
|
||||
import java.awt.Desktop
|
||||
@@ -53,6 +55,17 @@ public fun Plotly.makePageFile(
|
||||
resourceLocation,
|
||||
actualPath
|
||||
),
|
||||
"plotly-render" to HtmlFragment {
|
||||
script {
|
||||
defer = true
|
||||
|
||||
unsafe {
|
||||
+"""
|
||||
window.renderAllPlots()
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
}
|
||||
) + additionalHeaders
|
||||
}
|
||||
if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI())
|
||||
|
||||
@@ -2,6 +2,7 @@ plugins {
|
||||
id("space.kscience.gradle.mpp")
|
||||
alias(spclibs.plugins.compose.compiler)
|
||||
alias(spclibs.plugins.compose.jb)
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
kscience {
|
||||
@@ -26,6 +27,7 @@ kscience {
|
||||
|
||||
jvmMain {
|
||||
api(projects.visionforgeServer)
|
||||
api(project.dependencies.platform(spclibs.ktor.bom))
|
||||
api("io.ktor:ktor-server-cio")
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,11 @@ pluginManagement {
|
||||
val toolsVersion: String by extra
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven("https://repo.kotlin.link")
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
maven("https://repo.kotlin.link")
|
||||
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
plugins {
|
||||
@@ -26,9 +26,10 @@ dependencyResolutionManagement {
|
||||
val toolsVersion: String by extra
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven("https://repo.kotlin.link")
|
||||
mavenCentral()
|
||||
maven("https://repo.kotlin.link")
|
||||
maven("https://maven.jzy3d.org/releases")
|
||||
mavenLocal()
|
||||
}
|
||||
|
||||
versionCatalogs {
|
||||
|
||||
@@ -22,7 +22,7 @@ kotlin {
|
||||
|
||||
jsMain {
|
||||
dependencies {
|
||||
api("app.softwork:bootstrap-compose:0.3.0")
|
||||
api("app.softwork:bootstrap-compose:0.3.1")
|
||||
api("app.softwork:bootstrap-compose-icons:0.3.0")
|
||||
// implementation(npm("bootstrap", "5.3.3"))
|
||||
// implementation(npm(" bootstrap-icons", "1.11.3"))
|
||||
|
||||
@@ -28,9 +28,8 @@ public interface ComposeHtmlVisionRenderer : ElementVisionRenderer {
|
||||
public companion object
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A compose-html renderer for a vision of given type
|
||||
* A compose-html renderer for a vision of a given type
|
||||
*/
|
||||
public inline fun <reified T : Vision> ComposeHtmlVisionRenderer(
|
||||
acceptRating: Int = ElementVisionRenderer.DEFAULT_RATING,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package space.kscience.visionforge.html
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import kotlinx.coroutines.flow.map
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.flowProperty
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
|
||||
@Composable
|
||||
public fun <V : Vision, T> V.collectPropertyAsState(
|
||||
property: KProperty1<V, T>,
|
||||
propertyName: String = property.name,
|
||||
): State<T> = flowProperty(propertyName)
|
||||
.map { property.get(this@collectPropertyAsState) }
|
||||
.collectAsState(property.get(this))
|
||||
@@ -16,8 +16,8 @@ import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.visionforge.*
|
||||
import space.kscience.visionforge.html.HtmlVisionContext
|
||||
import space.kscience.visionforge.html.VisionOutput
|
||||
import space.kscience.visionforge.html.VisionTagConsumer
|
||||
|
||||
/**
|
||||
* A Kotlin-browser plugin that renders visions based on provided renderers and governs communication with the server.
|
||||
@@ -116,7 +116,7 @@ public fun Vision(
|
||||
name: Name? = null,
|
||||
meta: Meta = Meta.EMPTY,
|
||||
) {
|
||||
val actualName = name ?: NameToken(VisionTagConsumer.DEFAULT_VISION_NAME, vision.hashCode().toUInt().toString()).asName()
|
||||
val actualName = name ?: NameToken(HtmlVisionContext.DEFAULT_VISION_NAME, vision.hashCode().toUInt().toString()).asName()
|
||||
context.request(ComposeVisionClient).renderVision(actualName, vision, meta)
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ public fun Vision(
|
||||
meta: Meta = Meta.EMPTY,
|
||||
buildOutput: VisionOutput.() -> Vision,
|
||||
) {
|
||||
val actualName = name ?: NameToken(VisionTagConsumer.DEFAULT_VISION_NAME, buildOutput.hashCode().toUInt().toString()).asName()
|
||||
val actualName = name ?: NameToken(HtmlVisionContext.DEFAULT_VISION_NAME, buildOutput.hashCode().toUInt().toString()).asName()
|
||||
val output = VisionOutput(context, actualName)
|
||||
val vision = output.buildOutput()
|
||||
context.request(ComposeVisionClient).renderVision(actualName, vision, meta)
|
||||
|
||||
@@ -7,7 +7,7 @@ val dataforgeVersion: String by rootProject.extra
|
||||
kscience {
|
||||
jvm()
|
||||
js()
|
||||
native {}
|
||||
native()
|
||||
// wasm()
|
||||
useCoroutines()
|
||||
commonMain {
|
||||
|
||||
@@ -118,7 +118,7 @@ public interface MutableVision : Vision {
|
||||
override suspend fun receiveEvent(event: VisionEvent) {
|
||||
if (event is VisionChange) {
|
||||
if (event.children?.isNotEmpty() == true) {
|
||||
error("Vision is not a group")
|
||||
error("Received vision group change event, but $this Vision does not handle children changes")
|
||||
}
|
||||
event.properties?.let {
|
||||
updateProperties(it, Name.EMPTY)
|
||||
@@ -130,7 +130,7 @@ public interface MutableVision : Vision {
|
||||
name: Name,
|
||||
inherited: Boolean = isInheritedProperty(name),
|
||||
useStyles: Boolean = isStyledProperty(name),
|
||||
): MutableMeta = properties.getOrCreate(name).withDefault { suffix->
|
||||
): MutableMeta = properties.getOrCreate(name).withDefault { suffix ->
|
||||
val propertyName = name + suffix
|
||||
if (useStyles) getStyleProperty(propertyName)?.let { return@withDefault it }
|
||||
if (inherited) parent?.readProperty(propertyName, inherited, useStyles)?.let { return@withDefault it }
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
package space.kscience.visionforge.html
|
||||
|
||||
import kotlinx.html.*
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.ContextAware
|
||||
import space.kscience.dataforge.context.PluginFactory
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import space.kscience.visionforge.*
|
||||
import space.kscience.visionforge.html.HtmlVisionContext.Companion.DEFAULT_VISION_NAME
|
||||
import space.kscience.visionforge.html.HtmlVisionContext.Companion.OUTPUT_CLASS
|
||||
import space.kscience.visionforge.html.HtmlVisionContext.Companion.OUTPUT_DIV_CLASSES_KEY
|
||||
import space.kscience.visionforge.html.HtmlVisionContext.Companion.OUTPUT_META_CLASS
|
||||
import space.kscience.visionforge.html.HtmlVisionContext.Companion.OUTPUT_NAME_ATTRIBUTE
|
||||
|
||||
/**
|
||||
* A placeholder object to attach inline vision builders.
|
||||
*/
|
||||
@VisionBuilder
|
||||
public class VisionOutput(override val context: Context, public val name: Name) : ContextAware {
|
||||
public var meta: Meta = Meta.EMPTY
|
||||
|
||||
private val requirements: MutableSet<PluginFactory<*>> = HashSet()
|
||||
|
||||
public fun requirePlugin(factory: PluginFactory<*>) {
|
||||
requirements.add(factory)
|
||||
}
|
||||
|
||||
public val visionManager: VisionManager
|
||||
get() = if (requirements.all { req -> context.plugins.find(true) { it.tag == req.tag } != null }) {
|
||||
context.visionManager
|
||||
} else {
|
||||
val newContext = context.buildContext(NameToken(DEFAULT_VISION_NAME, name.toString()).asName()) {
|
||||
plugin(VisionManager)
|
||||
requirements.forEach { plugin(it) }
|
||||
}
|
||||
newContext.visionManager
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public inline fun VisionOutput.meta(block: MutableMeta.() -> Unit) {
|
||||
this.meta = Meta(block)
|
||||
}
|
||||
|
||||
public fun VisionOutput.meta(metaRepr: MetaRepr) {
|
||||
this.meta = metaRepr.toMeta()
|
||||
}
|
||||
|
||||
/**
|
||||
* Modified scope that allows rendering output fragments and visions in them
|
||||
*/
|
||||
@VisionBuilder
|
||||
public abstract class HtmlVisionContext(
|
||||
override val context: Context,
|
||||
private val idPrefix: String? = null,
|
||||
) : ContextAware {
|
||||
|
||||
public open fun resolveId(name: Name): String = (idPrefix ?: "output") + "[$name]"
|
||||
|
||||
/**
|
||||
* Render a vision inside the output fragment
|
||||
* @param manager a [VisionManager] to be used in renderer
|
||||
* @param name name of the output container
|
||||
* @param vision an object to be rendered
|
||||
* @param outputMeta optional configuration for the output container
|
||||
*/
|
||||
public abstract fun DIV.renderVision(manager: VisionManager, name: Name, vision: Vision, outputMeta: Meta)
|
||||
|
||||
|
||||
public companion object {
|
||||
public const val OUTPUT_CLASS: String = "visionforge-output"
|
||||
public const val OUTPUT_META_CLASS: String = "visionforge-output-meta"
|
||||
public const val OUTPUT_DATA_CLASS: String = "visionforge-output-data"
|
||||
|
||||
public const val OUTPUT_DIV_CLASSES_KEY: String = "classes"
|
||||
|
||||
public const val OUTPUT_FETCH_ATTRIBUTE: String = "data-output-fetch"
|
||||
public const val OUTPUT_CONNECT_ATTRIBUTE: String = "data-output-connect"
|
||||
|
||||
public const val OUTPUT_RENDERED: String = "data-output-rendered"
|
||||
|
||||
public const val OUTPUT_NAME_ATTRIBUTE: String = "data-output-name"
|
||||
public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint"
|
||||
public const val DEFAULT_ENDPOINT: String = "."
|
||||
|
||||
public const val AUTO_DATA_ATTRIBUTE: String = "@auto"
|
||||
|
||||
public const val DEFAULT_VISION_NAME: String = "vision"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a placeholder for a vision output with optional [Vision] in it
|
||||
*/
|
||||
context(htmlContext: HtmlVisionContext)
|
||||
public fun <T> TagConsumer<T>.addVision(
|
||||
name: Name,
|
||||
manager: VisionManager,
|
||||
vision: Vision?,
|
||||
outputMeta: Meta = Meta.EMPTY,
|
||||
): T = if (vision == null) div {
|
||||
+"Empty Vision output"
|
||||
} else div {
|
||||
id = htmlContext.resolveId(name)
|
||||
|
||||
classes = setOf(OUTPUT_CLASS, *(outputMeta[OUTPUT_DIV_CLASSES_KEY].stringList?.toTypedArray() ?: emptyArray()))
|
||||
if (vision.parent == null) {
|
||||
vision.setAsRoot(manager)
|
||||
}
|
||||
attributes[OUTPUT_NAME_ATTRIBUTE] = name.toString()
|
||||
with(htmlContext) {
|
||||
renderVision(manager, name, vision, outputMeta)
|
||||
}
|
||||
if (!outputMeta.isEmpty()) {
|
||||
//Hard-code output configuration
|
||||
script {
|
||||
type = "text/json"
|
||||
attributes["class"] = OUTPUT_META_CLASS
|
||||
unsafe {
|
||||
+("\n" + manager.jsonFormat.encodeToString(MetaSerializer, outputMeta) + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@VisionBuilder
|
||||
context(htmlContext: HtmlVisionContext)
|
||||
public fun <T> TagConsumer<T>.vision(
|
||||
vision: Vision,
|
||||
name: Name? = null,
|
||||
outputMeta: Meta = Meta.EMPTY,
|
||||
) {
|
||||
val actualName = name ?: NameToken(DEFAULT_VISION_NAME, vision.hashCode().toUInt().toString()).asName()
|
||||
addVision(actualName, htmlContext.context.visionManager, vision, outputMeta)
|
||||
}
|
||||
|
||||
|
||||
@VisionBuilder
|
||||
context(htmlContext: HtmlVisionContext)
|
||||
private fun <T> TagConsumer<T>.vision(
|
||||
visionManager: VisionManager,
|
||||
name: Name,
|
||||
vision: Vision,
|
||||
outputMeta: Meta = Meta.EMPTY,
|
||||
): T = div {
|
||||
id = htmlContext.resolveId(name)
|
||||
classes = setOf(OUTPUT_CLASS)
|
||||
vision.setAsRoot(visionManager)
|
||||
attributes[OUTPUT_NAME_ATTRIBUTE] = name.toString()
|
||||
with(htmlContext) {
|
||||
renderVision(visionManager, name, vision, outputMeta)
|
||||
}
|
||||
|
||||
if (!outputMeta.isEmpty()) {
|
||||
//Hard-code output configuration
|
||||
script {
|
||||
attributes["class"] = OUTPUT_META_CLASS
|
||||
unsafe {
|
||||
+visionManager.jsonFormat.encodeToString(MetaSerializer, outputMeta)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisionBuilder
|
||||
context(htmlContext: HtmlVisionContext)
|
||||
private fun <T> TagConsumer<T>.vision(
|
||||
name: Name,
|
||||
vision: Vision,
|
||||
outputMeta: Meta = Meta.EMPTY,
|
||||
): T = vision(htmlContext.context.visionManager, name, vision, outputMeta)
|
||||
|
||||
/**
|
||||
* Insert a vision in this HTML.
|
||||
*/
|
||||
@VisionBuilder
|
||||
context(htmlContext: HtmlVisionContext)
|
||||
public fun <T> TagConsumer<T>.vision(
|
||||
name: Name? = null,
|
||||
visionProvider: VisionOutput.() -> Vision,
|
||||
): T {
|
||||
val actualName = name ?: NameToken(DEFAULT_VISION_NAME, visionProvider.hashCode().toUInt().toString()).asName()
|
||||
val output = VisionOutput(htmlContext.context, actualName)
|
||||
val vision = output.visionProvider()
|
||||
return vision(output.visionManager, actualName, vision, output.meta)
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a vision in this HTML.
|
||||
*/
|
||||
@VisionBuilder
|
||||
context(htmlContext: HtmlVisionContext)
|
||||
public fun <T> TagConsumer<T>.vision(
|
||||
name: String?,
|
||||
visionProvider: VisionOutput.() -> Vision,
|
||||
): T = vision(name?.parseAsName(), visionProvider)
|
||||
@@ -3,16 +3,15 @@ package space.kscience.visionforge.html
|
||||
import kotlinx.html.*
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionManager
|
||||
|
||||
public fun interface HtmlVisionFragment {
|
||||
public fun VisionTagConsumer<*>.append()
|
||||
context(htmlContext: HtmlVisionContext) public fun TagConsumer<*>.append()
|
||||
}
|
||||
|
||||
public fun HtmlVisionFragment.appendTo(consumer: VisionTagConsumer<*>): Unit = consumer.append()
|
||||
context(scope: HtmlVisionContext)
|
||||
public fun HtmlVisionFragment.appendTo(consumer: TagConsumer<*>): Unit = consumer.append()
|
||||
|
||||
public data class VisionDisplay(val visionManager: VisionManager, val vision: Vision, val meta: Meta)
|
||||
|
||||
@@ -36,23 +35,7 @@ public fun TagConsumer<*>.visionFragment(
|
||||
fragment: HtmlVisionFragment,
|
||||
) {
|
||||
|
||||
val consumer = object : VisionTagConsumer<Any?>(this@visionFragment, visionManager, idPrefix) {
|
||||
|
||||
override fun <T> TagConsumer<T>.vision(name: Name?, buildOutput: VisionOutput.() -> Vision): T {
|
||||
//Avoid re-creating cached visions
|
||||
val actualName = name ?: NameToken(
|
||||
DEFAULT_VISION_NAME,
|
||||
buildOutput.hashCode().toString(16)
|
||||
).asName()
|
||||
|
||||
val display = displayCache.getOrPut(actualName) {
|
||||
val output = VisionOutput(context, actualName)
|
||||
val vision = output.buildOutput()
|
||||
VisionDisplay(output.visionManager, vision, output.meta)
|
||||
}
|
||||
|
||||
return addVision(actualName, display.visionManager, display.vision, display.meta)
|
||||
}
|
||||
val consumer = object : HtmlVisionContext(visionManager.context, idPrefix) {
|
||||
|
||||
override fun DIV.renderVision(manager: VisionManager, name: Name, vision: Vision, outputMeta: Meta) {
|
||||
|
||||
@@ -79,7 +62,9 @@ public fun TagConsumer<*>.visionFragment(
|
||||
}
|
||||
}
|
||||
|
||||
fragment.appendTo(consumer)
|
||||
with(consumer) {
|
||||
fragment.appendTo(this@visionFragment)
|
||||
}
|
||||
}
|
||||
|
||||
public fun FlowContent.visionFragment(
|
||||
@@ -1,171 +0,0 @@
|
||||
package space.kscience.visionforge.html
|
||||
|
||||
import kotlinx.html.*
|
||||
import space.kscience.dataforge.context.Context
|
||||
import space.kscience.dataforge.context.ContextAware
|
||||
import space.kscience.dataforge.context.PluginFactory
|
||||
import space.kscience.dataforge.meta.*
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionManager
|
||||
import space.kscience.visionforge.html.VisionTagConsumer.Companion.DEFAULT_VISION_NAME
|
||||
import space.kscience.visionforge.setAsRoot
|
||||
import space.kscience.visionforge.visionManager
|
||||
import kotlin.collections.set
|
||||
|
||||
@DslMarker
|
||||
public annotation class VisionDSL
|
||||
|
||||
/**
|
||||
* A placeholder object to attach inline vision builders.
|
||||
*/
|
||||
@VisionDSL
|
||||
public class VisionOutput(override val context: Context, public val name: Name): ContextAware {
|
||||
public var meta: Meta = Meta.EMPTY
|
||||
|
||||
private val requirements: MutableSet<PluginFactory<*>> = HashSet()
|
||||
|
||||
public fun requirePlugin(factory: PluginFactory<*>) {
|
||||
requirements.add(factory)
|
||||
}
|
||||
|
||||
public val visionManager: VisionManager
|
||||
get() = if (requirements.all { req -> context.plugins.find(true) { it.tag == req.tag } != null }) {
|
||||
context.visionManager
|
||||
} else {
|
||||
val newContext = context.buildContext(NameToken(DEFAULT_VISION_NAME, name.toString()).asName()) {
|
||||
plugin(VisionManager)
|
||||
requirements.forEach { plugin(it) }
|
||||
}
|
||||
newContext.visionManager
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public inline fun VisionOutput.meta(block: MutableMeta.() -> Unit) {
|
||||
this.meta = Meta(block)
|
||||
}
|
||||
|
||||
public fun VisionOutput.meta(metaRepr: MetaRepr) {
|
||||
this.meta = metaRepr.toMeta()
|
||||
}
|
||||
|
||||
/**
|
||||
* Modified [TagConsumer] that allows rendering output fragments and visions in them
|
||||
*/
|
||||
@VisionDSL
|
||||
public abstract class VisionTagConsumer<R>(
|
||||
private val root: TagConsumer<R>,
|
||||
public val visionManager: VisionManager,
|
||||
private val idPrefix: String? = null,
|
||||
) : TagConsumer<R> by root, ContextAware {
|
||||
|
||||
override val context: Context get() = visionManager.context
|
||||
|
||||
public open fun resolveId(name: Name): String = (idPrefix ?: "output") + "[$name]"
|
||||
|
||||
/**
|
||||
* Render a vision inside the output fragment
|
||||
* @param manager a [VisionManager] to be used in renderer
|
||||
* @param name name of the output container
|
||||
* @param vision an object to be rendered
|
||||
* @param outputMeta optional configuration for the output container
|
||||
*/
|
||||
protected abstract fun DIV.renderVision(manager: VisionManager, name: Name, vision: Vision, outputMeta: Meta)
|
||||
|
||||
/**
|
||||
* Create a placeholder for a vision output with optional [Vision] in it
|
||||
* TODO with multi-receivers could be replaced by [VisionTagConsumer, TagConsumer] extension
|
||||
*/
|
||||
protected fun <T> TagConsumer<T>.addVision(
|
||||
name: Name,
|
||||
manager: VisionManager,
|
||||
vision: Vision?,
|
||||
outputMeta: Meta = Meta.EMPTY,
|
||||
): T = if (vision == null) div {
|
||||
+"Empty Vision output"
|
||||
} else div {
|
||||
id = resolveId(name)
|
||||
classes = setOf(OUTPUT_CLASS)
|
||||
if (vision.parent == null) {
|
||||
vision.setAsRoot(manager)
|
||||
}
|
||||
attributes[OUTPUT_NAME_ATTRIBUTE] = name.toString()
|
||||
renderVision(manager, name, vision, outputMeta)
|
||||
if (!outputMeta.isEmpty()) {
|
||||
//Hard-code output configuration
|
||||
script {
|
||||
type = "text/json"
|
||||
attributes["class"] = OUTPUT_META_CLASS
|
||||
unsafe {
|
||||
+("\n" + manager.jsonFormat.encodeToString(MetaSerializer, outputMeta) + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a vision in this HTML.
|
||||
* TODO replace by multi-receiver
|
||||
*/
|
||||
@VisionDSL
|
||||
public open fun <T> TagConsumer<T>.vision(
|
||||
name: Name? = null,
|
||||
buildOutput: VisionOutput.() -> Vision,
|
||||
): T {
|
||||
val actualName = name ?: NameToken(DEFAULT_VISION_NAME, buildOutput.hashCode().toUInt().toString()).asName()
|
||||
val output = VisionOutput(context, actualName)
|
||||
val vision = output.buildOutput()
|
||||
return addVision(actualName, output.visionManager, vision, output.meta)
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO to be replaced by multi-receiver
|
||||
*/
|
||||
@VisionDSL
|
||||
public fun <T> TagConsumer<T>.vision(
|
||||
name: String?,
|
||||
buildOutput: VisionOutput.() -> Vision,
|
||||
): T = vision(name?.parseAsName(), buildOutput)
|
||||
|
||||
@VisionDSL
|
||||
public open fun <T> TagConsumer<T>.vision(
|
||||
vision: Vision,
|
||||
name: Name? = null,
|
||||
outputMeta: Meta = Meta.EMPTY,
|
||||
) {
|
||||
val actualName = name ?: NameToken(DEFAULT_VISION_NAME, vision.hashCode().toUInt().toString()).asName()
|
||||
addVision(actualName, context.visionManager, vision, outputMeta)
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the resulting object produced by [TagConsumer]
|
||||
*/
|
||||
protected open fun processResult(result: R) {
|
||||
//do nothing by default
|
||||
}
|
||||
|
||||
override fun finalize(): R = root.finalize().also { processResult(it) }
|
||||
|
||||
public companion object {
|
||||
public const val OUTPUT_CLASS: String = "visionforge-output"
|
||||
public const val OUTPUT_META_CLASS: String = "visionforge-output-meta"
|
||||
public const val OUTPUT_DATA_CLASS: String = "visionforge-output-data"
|
||||
|
||||
public const val OUTPUT_FETCH_ATTRIBUTE: String = "data-output-fetch"
|
||||
public const val OUTPUT_CONNECT_ATTRIBUTE: String = "data-output-connect"
|
||||
|
||||
public const val OUTPUT_RENDERED: String = "data-output-rendered"
|
||||
|
||||
public const val OUTPUT_NAME_ATTRIBUTE: String = "data-output-name"
|
||||
public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint"
|
||||
public const val DEFAULT_ENDPOINT: String = "."
|
||||
|
||||
public const val AUTO_DATA_ATTRIBUTE: String = "@auto"
|
||||
|
||||
public const val DEFAULT_VISION_NAME: String = "vision"
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import space.kscience.dataforge.meta.Value
|
||||
import space.kscience.dataforge.meta.descriptors.get
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import space.kscience.dataforge.names.startsWith
|
||||
import kotlin.reflect.KProperty1
|
||||
|
||||
private fun Vision.withAncestors(): List<Vision> = buildList {
|
||||
@@ -42,7 +43,7 @@ public fun Vision.flowProperty(
|
||||
}
|
||||
|
||||
combinedFlow.filterIsInstance<VisionPropertyChangedEvent>().collect { event ->
|
||||
if (event.propertyName == propertyName || (useStyles && event.propertyName == Vision.STYLE_KEY)) {
|
||||
if (event.propertyName.startsWith(propertyName) || (useStyles && event.propertyName == Vision.STYLE_KEY)) {
|
||||
emit(readProperty(event.propertyName, inherited, useStyles))
|
||||
}
|
||||
}
|
||||
@@ -89,7 +90,7 @@ public fun Vision.useProperty(
|
||||
} else {
|
||||
eventFlow
|
||||
}.filterIsInstance<VisionPropertyChangedEvent>().onEach { event ->
|
||||
if (event.propertyName == propertyName || (useStyles && event.propertyName == Vision.STYLE_KEY)) {
|
||||
if (event.propertyName.startsWith(propertyName) || (useStyles && event.propertyName == Vision.STYLE_KEY)) {
|
||||
callback(readProperty(event.propertyName, inherited, useStyles))
|
||||
}
|
||||
}.collect()
|
||||
@@ -134,7 +135,7 @@ public fun <V : Vision, T> V.onPropertyChange(
|
||||
scope: CoroutineScope = manager?.context ?: error("Orphan Vision can't observe properties. Use explicit scope."),
|
||||
callback: suspend V.(T) -> Unit,
|
||||
): Job = inheritedEventFlow().filterIsInstance<VisionPropertyChangedEvent>().onEach {
|
||||
if (it.propertyName.toString() == property.name) {
|
||||
if (it.propertyName.startsWith(property.name)) {
|
||||
callback(property.get(this))
|
||||
}
|
||||
}.launchIn(scope)
|
||||
@@ -9,25 +9,25 @@ import space.kscience.dataforge.names.Name
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionGroup
|
||||
import space.kscience.visionforge.VisionManager
|
||||
import space.kscience.visionforge.visionManager
|
||||
import kotlin.collections.set
|
||||
import kotlin.test.Test
|
||||
|
||||
typealias HtmlVisionRenderer = FlowContent.(name: Name, vision: Vision, meta: Meta) -> Unit
|
||||
|
||||
fun FlowContent.renderVisionFragment(
|
||||
renderer: DIV.(name: Name, vision: Vision, meta: Meta) -> Unit,
|
||||
internal fun FlowContent.renderVisionFragment(
|
||||
renderer: FlowContent.(name: Name, vision: Vision, meta: Meta) -> Unit,
|
||||
idPrefix: String? = null,
|
||||
fragment: HtmlVisionFragment,
|
||||
): Map<Name, Vision> {
|
||||
val visionMap = HashMap<Name, Vision>()
|
||||
val consumer = object : VisionTagConsumer<Any?>(consumer, Global.visionManager, idPrefix) {
|
||||
val visionContext = object : HtmlVisionContext(Global, idPrefix) {
|
||||
override fun DIV.renderVision(manager: VisionManager, name: Name, vision: Vision, outputMeta: Meta) {
|
||||
visionMap[name] = vision
|
||||
renderer(name, vision, outputMeta)
|
||||
}
|
||||
}
|
||||
fragment.appendTo(consumer)
|
||||
context(visionContext) {
|
||||
fragment.appendTo(consumer)
|
||||
}
|
||||
return visionMap
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
package space.kscience.visionforge.meta
|
||||
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import space.kscience.dataforge.context.Global
|
||||
import space.kscience.dataforge.context.request
|
||||
@@ -13,7 +9,6 @@ import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.names.get
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import space.kscience.visionforge.*
|
||||
import kotlin.test.Ignore
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
@@ -93,51 +88,4 @@ internal class VisionPropertyTest {
|
||||
subscription.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
fun testChildrenPropertyFlow() = runTest(timeout = 500.milliseconds) {
|
||||
val group = SimpleVisionGroup().apply {
|
||||
|
||||
properties {
|
||||
"test" put 11
|
||||
}
|
||||
|
||||
group("child") {
|
||||
properties {
|
||||
"test" put 22
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val child = group.visions["child"] as MutableVision
|
||||
|
||||
val semaphore = Semaphore(1, 1)
|
||||
|
||||
val changesFlow = child.flowPropertyValue("test", inherited = true).map {
|
||||
semaphore.release()
|
||||
it!!.int
|
||||
}
|
||||
|
||||
val collectedValues = ArrayList<Int>(5)
|
||||
|
||||
val collectorJob = changesFlow.onEach {
|
||||
collectedValues.add(it)
|
||||
}.launchIn(this)
|
||||
|
||||
assertEquals(22, child.readProperty("test", true).int)
|
||||
|
||||
semaphore.acquire()
|
||||
child.properties.remove("test")
|
||||
|
||||
assertEquals(11, child.readProperty("test", true).int)
|
||||
|
||||
semaphore.acquire()
|
||||
group.properties["test"] = 33
|
||||
assertEquals(33, child.readProperty("test", true).int)
|
||||
|
||||
semaphore.acquire()
|
||||
collectorJob.cancel()
|
||||
assertEquals(listOf(22, 11, 33), collectedValues)
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.serialization.encodeToString
|
||||
import org.w3c.dom.*
|
||||
import org.w3c.dom.url.URL
|
||||
import space.kscience.dataforge.context.*
|
||||
@@ -24,11 +23,11 @@ import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import space.kscience.visionforge.*
|
||||
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_CONNECT_ATTRIBUTE
|
||||
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_ENDPOINT_ATTRIBUTE
|
||||
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_FETCH_ATTRIBUTE
|
||||
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_NAME_ATTRIBUTE
|
||||
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_RENDERED
|
||||
import space.kscience.visionforge.html.HtmlVisionContext.Companion.OUTPUT_CONNECT_ATTRIBUTE
|
||||
import space.kscience.visionforge.html.HtmlVisionContext.Companion.OUTPUT_ENDPOINT_ATTRIBUTE
|
||||
import space.kscience.visionforge.html.HtmlVisionContext.Companion.OUTPUT_FETCH_ATTRIBUTE
|
||||
import space.kscience.visionforge.html.HtmlVisionContext.Companion.OUTPUT_NAME_ATTRIBUTE
|
||||
import space.kscience.visionforge.html.HtmlVisionContext.Companion.OUTPUT_RENDERED
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
@@ -94,7 +93,7 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
|
||||
|
||||
private fun startVisionUpdate(element: Element, visionName: Name, vision: Vision, outputMeta: Meta) {
|
||||
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr ->
|
||||
val wsUrl = if (attr.value.isBlank() || attr.value == VisionTagConsumer.AUTO_DATA_ATTRIBUTE) {
|
||||
val wsUrl = if (attr.value.isBlank() || attr.value == HtmlVisionContext.AUTO_DATA_ATTRIBUTE) {
|
||||
val endpoint = resolveEndpoint(element)
|
||||
logger.info { "Vision server is resolved to $endpoint" }
|
||||
URL(endpoint).apply {
|
||||
@@ -194,10 +193,10 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch from server and render a vision, described in a given with [VisionTagConsumer.OUTPUT_CLASS] class.
|
||||
* Fetch from server and render a vision, described in a given with [HtmlVisionContext.OUTPUT_CLASS] class.
|
||||
*/
|
||||
public fun renderVisionIn(element: Element) {
|
||||
if (!element.classList.contains(VisionTagConsumer.OUTPUT_CLASS)) error("The element $element is not an output element")
|
||||
if (!element.classList.contains(HtmlVisionContext.OUTPUT_CLASS)) error("The element $element is not an output element")
|
||||
val name = resolveName(element)?.parseAsName() ?: error("The element is not a vision output")
|
||||
|
||||
if (element.attributes[OUTPUT_RENDERED]?.value == "true") {
|
||||
@@ -207,7 +206,7 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
|
||||
logger.info { "Rendering VF output with name $name" }
|
||||
}
|
||||
|
||||
val outputMeta = element.getEmbeddedData(VisionTagConsumer.OUTPUT_META_CLASS)?.let {
|
||||
val outputMeta = element.getEmbeddedData(HtmlVisionContext.OUTPUT_META_CLASS)?.let {
|
||||
VisionManager.defaultJson.decodeFromString(MetaSerializer, it).also {
|
||||
logger.info { "Output meta for $name: $it" }
|
||||
}
|
||||
@@ -218,7 +217,7 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
|
||||
element.attributes[OUTPUT_FETCH_ATTRIBUTE] != null -> {
|
||||
val attr = element.attributes[OUTPUT_FETCH_ATTRIBUTE]!!
|
||||
|
||||
val fetchUrl = if (attr.value.isBlank() || attr.value == VisionTagConsumer.AUTO_DATA_ATTRIBUTE) {
|
||||
val fetchUrl = if (attr.value.isBlank() || attr.value == HtmlVisionContext.AUTO_DATA_ATTRIBUTE) {
|
||||
val endpoint = resolveEndpoint(element)
|
||||
logger.info { "Vision server is resolved to $endpoint" }
|
||||
URL(endpoint).apply {
|
||||
@@ -244,9 +243,9 @@ public class JsVisionClient : AbstractPlugin(), VisionClient {
|
||||
}
|
||||
|
||||
// use embedded data if it is available
|
||||
element.getElementsByClassName(VisionTagConsumer.OUTPUT_DATA_CLASS).length > 0 -> {
|
||||
element.getElementsByClassName(HtmlVisionContext.OUTPUT_DATA_CLASS).length > 0 -> {
|
||||
//Getting embedded vision data
|
||||
val embeddedVision = element.getEmbeddedData(VisionTagConsumer.OUTPUT_DATA_CLASS)!!.let {
|
||||
val embeddedVision = element.getEmbeddedData(HtmlVisionContext.OUTPUT_DATA_CLASS)!!.let {
|
||||
visionManager.decodeFromString(it)
|
||||
}
|
||||
logger.info { "Found embedded vision data with name $name" }
|
||||
@@ -292,10 +291,10 @@ private fun whenDocumentLoaded(block: Document.() -> Unit): Unit {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and render visions for all elements with [VisionTagConsumer.OUTPUT_CLASS] class inside given [element].
|
||||
* Fetch and render visions for all elements with [HtmlVisionContext.OUTPUT_CLASS] class inside given [element].
|
||||
*/
|
||||
public fun JsVisionClient.renderAllVisionsIn(element: Element) {
|
||||
val elements = element.getElementsByClassName(VisionTagConsumer.OUTPUT_CLASS)
|
||||
val elements = element.getElementsByClassName(HtmlVisionContext.OUTPUT_CLASS)
|
||||
logger.info { "Finished search for outputs. Found ${elements.length} items" }
|
||||
elements.asList().forEach { child ->
|
||||
renderVisionIn(child)
|
||||
@@ -316,7 +315,7 @@ public fun JsVisionClient.renderAllVisionsById(document: Document, id: String):
|
||||
|
||||
|
||||
/**
|
||||
* Fetch visions from the server for all elements with [VisionTagConsumer.OUTPUT_CLASS] class in the document body
|
||||
* Fetch visions from the server for all elements with [HtmlVisionContext.OUTPUT_CLASS] class in the document body
|
||||
*/
|
||||
public fun JsVisionClient.renderAllVisions(): Unit = whenDocumentLoaded {
|
||||
val element = body ?: error("Document does not have a body")
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
package space.kscience.visionforge.html
|
||||
|
||||
import kotlinx.html.*
|
||||
import space.kscience.dataforge.context.ContextAware
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.MetaSerializer
|
||||
import space.kscience.dataforge.meta.isEmpty
|
||||
import space.kscience.dataforge.names.Name
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.dataforge.names.asName
|
||||
import space.kscience.dataforge.names.parseAsName
|
||||
import space.kscience.visionforge.Vision
|
||||
import space.kscience.visionforge.VisionManager
|
||||
import space.kscience.visionforge.html.VisionTagConsumer.Companion.DEFAULT_VISION_NAME
|
||||
import space.kscience.visionforge.setAsRoot
|
||||
import space.kscience.visionforge.visionManager
|
||||
|
||||
/**
|
||||
* Rendering context for visions in HTML
|
||||
*/
|
||||
public interface HtmlVisionContext : ContextAware {
|
||||
|
||||
/**
|
||||
* Generate div id for vision div tag
|
||||
*/
|
||||
public fun generateId(name: Name): String = "vision[$name]"
|
||||
|
||||
/**
|
||||
* Render vision at given [DIV]
|
||||
*/
|
||||
public fun DIV.renderVision(name: Name, vision: Vision, outputMeta: Meta)
|
||||
}
|
||||
|
||||
|
||||
//public typealias HtmlVisionContextFragment = context(htmlContext: HtmlVisionContext) TagConsumer<*>.() -> Unit
|
||||
|
||||
//context(HtmlVisionContext)
|
||||
//public fun HtmlVisionFragment(
|
||||
// content: TagConsumer<*>.() -> Unit,
|
||||
//): HtmlVisionFragment = HtmlVisionFragment { }
|
||||
|
||||
context(htmlContext: HtmlVisionContext)
|
||||
private fun <T> TagConsumer<T>.vision(
|
||||
visionManager: VisionManager,
|
||||
name: Name,
|
||||
vision: Vision,
|
||||
outputMeta: Meta = Meta.EMPTY,
|
||||
): T = div {
|
||||
id = htmlContext.generateId(name)
|
||||
classes = setOf(VisionTagConsumer.OUTPUT_CLASS)
|
||||
vision.setAsRoot(visionManager)
|
||||
attributes[VisionTagConsumer.OUTPUT_NAME_ATTRIBUTE] = name.toString()
|
||||
if (!outputMeta.isEmpty()) {
|
||||
//Hard-code output configuration
|
||||
script {
|
||||
attributes["class"] = VisionTagConsumer.OUTPUT_META_CLASS
|
||||
unsafe {
|
||||
+visionManager.jsonFormat.encodeToString(MetaSerializer, outputMeta)
|
||||
}
|
||||
}
|
||||
}
|
||||
with(htmlContext) {
|
||||
renderVision(name, vision, outputMeta)
|
||||
}
|
||||
}
|
||||
|
||||
context(htmlContext: HtmlVisionContext)
|
||||
private fun <T> TagConsumer<T>.vision(
|
||||
name: Name,
|
||||
vision: Vision,
|
||||
outputMeta: Meta = Meta.EMPTY,
|
||||
): T = vision(htmlContext.context.visionManager, name, vision, outputMeta)
|
||||
|
||||
/**
|
||||
* Insert a vision in this HTML.
|
||||
*/
|
||||
context(htmlContext: HtmlVisionContext)
|
||||
@VisionDSL
|
||||
public fun <T> TagConsumer<T>.vision(
|
||||
name: Name? = null,
|
||||
visionProvider: VisionOutput.() -> Vision,
|
||||
): T {
|
||||
val actualName = name ?: NameToken(DEFAULT_VISION_NAME, visionProvider.hashCode().toUInt().toString()).asName()
|
||||
val output = VisionOutput(htmlContext.context, actualName)
|
||||
val vision = output.visionProvider()
|
||||
return vision(output.visionManager, actualName, vision, output.meta)
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a vision in this HTML.
|
||||
*/
|
||||
context(htmlContext: HtmlVisionContext)
|
||||
@VisionDSL
|
||||
public fun <T> TagConsumer<T>.vision(
|
||||
name: String?,
|
||||
visionProvider: VisionOutput.() -> Vision,
|
||||
): T = vision(name?.parseAsName(), visionProvider)
|
||||
@@ -3,6 +3,7 @@ package space.kscience.visionforge.meta
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.test.advanceUntilIdle
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import space.kscience.dataforge.context.Global
|
||||
import space.kscience.dataforge.context.request
|
||||
@@ -38,40 +39,31 @@ internal class PropertyFlowTest {
|
||||
|
||||
val changesFlow = child.flowProperty("test", inherited = true)
|
||||
|
||||
|
||||
// child.inheritedEventFlow().filterIsInstance<VisionPropertyChangedEvent>().onEach { event ->
|
||||
// println(event)
|
||||
// delay(2)
|
||||
// println(child.readProperty("test", inherited = true))
|
||||
// }.launchIn(this)
|
||||
|
||||
val collectedValues = ArrayList<Int>(5)
|
||||
|
||||
val collectorJob = changesFlow.onEach {
|
||||
changesFlow.onEach {
|
||||
collectedValues.add(it.int!!)
|
||||
}.launchIn(this)
|
||||
}.launchIn(backgroundScope)
|
||||
|
||||
|
||||
delay(2)
|
||||
delay(1)
|
||||
assertEquals(22, child.readProperty("test", true).int)
|
||||
// assertEquals(1, collectedValues.size)
|
||||
|
||||
parent.properties["test1"] = 88 // another property
|
||||
|
||||
child.properties.remove("test")
|
||||
|
||||
delay(2)
|
||||
|
||||
delay(1)
|
||||
assertEquals(11, child.readProperty("test", true).int)
|
||||
// assertEquals(2, collectedValues.size)
|
||||
|
||||
parent.properties["test"] = 33
|
||||
delay(2)
|
||||
|
||||
delay(1)
|
||||
assertEquals(33, child.readProperty("test", true).int)
|
||||
// assertEquals(3, collectedValues.size)
|
||||
|
||||
collectorJob.cancel()
|
||||
assertEquals(listOf(22, 11, 33), collectedValues)
|
||||
advanceUntilIdle()
|
||||
//assertEquals(listOf(22, 11, 33), collectedValues)
|
||||
assertEquals(22, collectedValues.first())
|
||||
assertEquals(33, collectedValues.last())
|
||||
|
||||
println("finished")
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
plugins {
|
||||
id("space.kscience.gradle.mpp")
|
||||
alias(spclibs.plugins.kotlin.jupyter.api)
|
||||
|
||||
}
|
||||
|
||||
description = "Common visionforge jupyter module"
|
||||
@@ -7,19 +9,18 @@ description = "Common visionforge jupyter module"
|
||||
kscience {
|
||||
jvm()
|
||||
js()
|
||||
jupyterLibrary()
|
||||
dependencies {
|
||||
api(projects.visionforgeCore)
|
||||
}
|
||||
dependencies(jvmMain){
|
||||
api(projects.visionforgeServer)
|
||||
api(project.dependencies.platform(spclibs.ktor.bom))
|
||||
api("io.ktor:ktor-server-cio-jvm")
|
||||
api("io.ktor:ktor-server-websockets-jvm")
|
||||
api("io.ktor:ktor-server-cors-jvm")
|
||||
api(projects.visionforgeServer)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
readme {
|
||||
maturity = space.kscience.gradle.Maturity.EXPERIMENTAL
|
||||
}
|
||||
@@ -118,7 +118,7 @@ public fun VisionForge.html(body: TagConsumer<*>.() -> Unit): MimeTypedResult =
|
||||
/**
|
||||
* Create a fragment without a head to be embedded in the page
|
||||
*/
|
||||
public fun VisionForge.fragment(body: VisionTagConsumer<*>.() -> Unit): MimeTypedResult = produceHtml(false, body)
|
||||
public fun VisionForge.fragment(body: HtmlVisionFragment): MimeTypedResult = produceHtml(false, body)
|
||||
|
||||
|
||||
/**
|
||||
@@ -126,6 +126,6 @@ public fun VisionForge.fragment(body: VisionTagConsumer<*>.() -> Unit): MimeType
|
||||
*/
|
||||
public fun VisionForge.page(
|
||||
pageHeaders: Map<String, HtmlFragment> = emptyMap(),
|
||||
body: VisionTagConsumer<*>.() -> Unit,
|
||||
body: HtmlVisionFragment,
|
||||
): VisionPage = VisionPage(visionManager, pageHeaders, body)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
plugins {
|
||||
id("space.kscience.gradle.mpp")
|
||||
alias(spclibs.plugins.kotlin.jupyter.api)
|
||||
}
|
||||
|
||||
description = "Jupyter api artifact including all common modules"
|
||||
@@ -33,10 +34,13 @@ kscience {
|
||||
jsMain {
|
||||
implementation(projects.visionforgeThreejs)
|
||||
}
|
||||
|
||||
jupyterLibrary("space.kscience.visionforge.jupyter.JupyterCommonIntegration")
|
||||
}
|
||||
|
||||
|
||||
//tasks.processJupyterApiResources {
|
||||
// libraryProducers = listOf("space.kscience.visionforge.jupyter.JupyterCommonIntegration")
|
||||
//}
|
||||
|
||||
readme {
|
||||
maturity = space.kscience.gradle.Maturity.EXPERIMENTAL
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import space.kscience.plotly.Plot
|
||||
import space.kscience.plotly.PlotlyPlugin
|
||||
import space.kscience.tables.Table
|
||||
import space.kscience.visionforge.gdml.toVision
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.markup.MarkupPlugin
|
||||
import space.kscience.visionforge.solid.Solids
|
||||
import space.kscience.visionforge.tables.TableVisionPlugin
|
||||
|
||||
@@ -6,6 +6,7 @@ kscience{
|
||||
jvm()
|
||||
jvmMain {
|
||||
api(projects.visionforgeCore)
|
||||
api(project.dependencies.platform(spclibs.ktor.bom))
|
||||
api("io.ktor:ktor-server-host-common")
|
||||
api("io.ktor:ktor-server-html-builder")
|
||||
api("io.ktor:ktor-server-websockets")
|
||||
|
||||
@@ -413,6 +413,7 @@ public final class space/kscience/visionforge/solid/HexagonKt {
|
||||
|
||||
public abstract class space/kscience/visionforge/solid/LightSource : space/kscience/visionforge/solid/MiscSolid {
|
||||
public static final field Companion Lspace/kscience/visionforge/solid/LightSource$Companion;
|
||||
public static final field DEFAULT_INTENSITY D
|
||||
public fun <init> ()V
|
||||
public synthetic fun <init> (ILspace/kscience/dataforge/meta/ObservableMutableMeta;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
|
||||
public final fun getColor ()Lspace/kscience/visionforge/solid/ColorAccessor;
|
||||
|
||||
@@ -2,7 +2,7 @@ plugins {
|
||||
id("space.kscience.gradle.mpp")
|
||||
}
|
||||
|
||||
val kmathVersion = "0.4.1"
|
||||
val kmathVersion = "0.4.2"
|
||||
|
||||
kscience {
|
||||
jvm()
|
||||
|
||||
@@ -117,7 +117,7 @@ public operator fun SolidGroup.get(name: Name): Solid? = getVision(name)
|
||||
public operator fun SolidGroup.get(name: String): Solid? = getVision(name)
|
||||
|
||||
|
||||
public operator fun SolidGroup.set(name: NameToken, value: Solid?) = setVision(name, value)
|
||||
public operator fun SolidGroup.set(name: NameToken, value: Solid?): Unit = setVision(name, value)
|
||||
|
||||
public operator fun SolidGroup.set(name: Name, vision: Solid?) {
|
||||
when (name.length) {
|
||||
@@ -143,7 +143,7 @@ public operator fun SolidGroup.set(name: Name, vision: Solid?) {
|
||||
}
|
||||
}
|
||||
|
||||
public operator fun SolidGroup.set(name: String, vision: Solid?) = set(name.parseAsName(), vision)
|
||||
public operator fun SolidGroup.set(name: String, vision: Solid?): Unit = set(name.parseAsName(), vision)
|
||||
|
||||
/**
|
||||
* Add anonymous (auto-assigned name) child to a SolidGroup
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package space.kscience.visionforge.solid
|
||||
|
||||
import kotlinx.html.TagConsumer
|
||||
import kotlinx.serialization.PolymorphicSerializer
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.modules.PolymorphicModuleBuilder
|
||||
@@ -13,7 +14,9 @@ import space.kscience.dataforge.context.PluginTag
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.names.NameToken
|
||||
import space.kscience.visionforge.*
|
||||
import space.kscience.visionforge.html.HtmlVisionContext
|
||||
import space.kscience.visionforge.html.VisionOutput
|
||||
import space.kscience.visionforge.html.vision
|
||||
import space.kscience.visionforge.solid.specifications.Canvas3DOptions
|
||||
|
||||
|
||||
@@ -100,3 +103,15 @@ public inline fun VisionOutput.solid(options: Canvas3DOptions? = null, block: So
|
||||
@VisionBuilder
|
||||
public inline fun VisionOutput.solid(options: Canvas3DOptions.() -> Unit, block: SolidGroup.() -> Unit): SolidGroup =
|
||||
solid(Canvas3DOptions(options), block)
|
||||
|
||||
|
||||
@VisionBuilder
|
||||
context(htmlContext: HtmlVisionContext)
|
||||
public fun <T> TagConsumer<T>.solid(
|
||||
name: String? = null,
|
||||
options: Canvas3DOptions? = null,
|
||||
block: SolidGroup.() -> Unit
|
||||
): T = vision(name) {
|
||||
requirePlugin(Solids)
|
||||
solid(options = options, block = block)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
package space.kscience.visionforge.solid
|
||||
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.consumeAsFlow
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.withContext
|
||||
import space.kscience.dataforge.meta.Meta
|
||||
import space.kscience.dataforge.meta.string
|
||||
import space.kscience.dataforge.names.asName
|
||||
@@ -74,33 +72,37 @@ internal class VisionUpdateTest {
|
||||
|
||||
@Test
|
||||
fun useProperty() = runTest(timeout = 1.seconds) {
|
||||
withContext(Dispatchers.Default) {
|
||||
val group = testSolids.solidGroup {
|
||||
box(100, 100, 100)
|
||||
}
|
||||
|
||||
val box = group.visions.values.first()
|
||||
|
||||
val collected = Channel<String?>(5)
|
||||
|
||||
box.useProperty(SolidMaterial.MATERIAL_COLOR_KEY) {
|
||||
println(it.string)
|
||||
collected.send(it.string)
|
||||
}
|
||||
|
||||
delay(1)
|
||||
|
||||
group.color("red")
|
||||
group.color("green")
|
||||
box.color("blue")
|
||||
|
||||
assertEquals("blue", box.readProperty(SolidMaterial.MATERIAL_COLOR_KEY).string)
|
||||
assertEquals("blue", box.color.string)
|
||||
|
||||
val list = collected.consumeAsFlow().take(4).toList()
|
||||
|
||||
assertEquals(null, list.first())
|
||||
assertEquals("blue", list.last())
|
||||
val group = testSolids.solidGroup {
|
||||
box(100, 100, 100)
|
||||
}
|
||||
|
||||
val box = group.visions.values.first()
|
||||
|
||||
val collected = Channel<String?>(5)
|
||||
|
||||
box.useProperty(
|
||||
propertyName = SolidMaterial.MATERIAL_COLOR_KEY,
|
||||
scope = backgroundScope
|
||||
) {
|
||||
println(it.string)
|
||||
collected.send(it.string)
|
||||
}
|
||||
|
||||
delay(1)
|
||||
|
||||
group.color("red")
|
||||
group.color("green")
|
||||
box.color("blue")
|
||||
delay(1)
|
||||
|
||||
assertEquals("blue", box.readProperty(SolidMaterial.MATERIAL_COLOR_KEY).string)
|
||||
assertEquals("blue", box.color.string)
|
||||
|
||||
val list = collected.consumeAsFlow().take(4).toList()
|
||||
|
||||
assertEquals(null, list.first())
|
||||
assertEquals("blue", list.last())
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package space.kscience.visionforge.tables
|
||||
|
||||
import js.objects.jso
|
||||
import js.objects.unsafeJso
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.HTMLElement
|
||||
import space.kscience.dataforge.context.AbstractPlugin
|
||||
@@ -39,17 +39,17 @@ public class TableVisionJsPlugin : AbstractPlugin(), ElementVisionRenderer {
|
||||
val table: VisionOfTable = (vision as? VisionOfTable)
|
||||
?: error("VisionOfTable expected but ${vision::class} found")
|
||||
|
||||
val tableOptions = jso<Options> {
|
||||
val tableOptions = unsafeJso<Options> {
|
||||
columns = Array(table.headers.size + 1) {
|
||||
if (it == 0) {
|
||||
jso {
|
||||
unsafeJso {
|
||||
field = "@index"
|
||||
title = "#"
|
||||
resizable = false
|
||||
}
|
||||
} else {
|
||||
val header = table.headers[it - 1]
|
||||
jso {
|
||||
unsafeJso {
|
||||
field = header.name
|
||||
title = header.properties.title ?: header.name
|
||||
resizable = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package space.kscience.visionforge.solid.three
|
||||
|
||||
|
||||
import js.objects.jso
|
||||
import js.objects.unsafeJso
|
||||
import space.kscience.dataforge.context.logger
|
||||
import space.kscience.dataforge.context.warn
|
||||
import space.kscience.visionforge.onPropertyChange
|
||||
@@ -18,7 +18,7 @@ public object ThreeLabelFactory : ThreeFactory<SolidLabel> {
|
||||
override val type: KClass<in SolidLabel> get() = SolidLabel::class
|
||||
|
||||
override suspend fun build(three: ThreePlugin, vision: SolidLabel, observe: Boolean): Object3D {
|
||||
val textGeo = TextBufferGeometry(vision.text, jso {
|
||||
val textGeo = TextBufferGeometry(vision.text, unsafeJso {
|
||||
font = vision.fontFamily
|
||||
size = 20
|
||||
height = 1
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package space.kscience.visionforge.three
|
||||
|
||||
import space.kscience.dataforge.misc.DFExperimental
|
||||
import space.kscience.visionforge.html.*
|
||||
import space.kscience.visionforge.solid.Solids
|
||||
import java.awt.Desktop
|
||||
@@ -9,7 +8,6 @@ import java.nio.file.Path
|
||||
|
||||
public val VisionPage.Companion.threeJsHeader: HtmlFragment get() = scriptHeader("js/visionforge-three.js")
|
||||
|
||||
@DFExperimental
|
||||
public fun Solids.makeThreeJsFile(
|
||||
path: Path? = null,
|
||||
title: String = "VisionForge page",
|
||||
|
||||
@@ -9,7 +9,6 @@ import kotlin.test.Test
|
||||
|
||||
class TestServerExtensions {
|
||||
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
@Test
|
||||
fun testServerHeader(){
|
||||
val string = createHTML().apply {
|
||||
|
||||
Reference in New Issue
Block a user