3 Commits

36 changed files with 540 additions and 579 deletions

View File

@@ -5,6 +5,7 @@
### Added
### Changed
- Replaced `VisionTagConsumer` with a proper `VisionHtmlContext` as a context parameter.
### Deprecated

View File

@@ -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
@@ -17,17 +17,19 @@ allprojects {
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")
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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()
}
}
}
}
}
}

View File

@@ -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

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -15,7 +15,10 @@ dependencies {
}
kotlin{
jvmToolchain(17)
jvmToolchain(21)
compilerOptions {
freeCompilerArgs.add("-Xcontext-parameters")
}
}
// A workaround for https://youtrack.jetbrains.com/issue/KT-44101

View File

@@ -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) }

View File

@@ -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

View File

@@ -11,8 +11,9 @@ 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.HtmlVisionContext
import space.kscience.visionforge.html.VisionOutput
import space.kscience.visionforge.html.VisionTagConsumer
import space.kscience.visionforge.html.vision
import kotlin.js.JsName
/**
@@ -77,7 +78,10 @@ public class PlotlyConfig : Scheme() {
* 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 = VisionTagConsumer.OUTPUT_DIV_CLASSES_KEY.asName())
public var classes: List<String> by stringList(
default = emptyArray(),
key = HtmlVisionContext.OUTPUT_DIV_CLASSES_KEY.asName()
)
public fun withEditorButton() {
showEditInChartStudio = true
@@ -106,14 +110,10 @@ public inline fun VisionOutput.plotly(
return Plotly.plot(block)
}
//FIXME rework VisionTagConsumer toa a context
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) {
this@plot.vision {
plotly(config, block)
}
}
): T = vision {
plotly(config, block)
}

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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(

View File

@@ -1,173 +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
@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, *(outputMeta[OUTPUT_DIV_CLASSES_KEY].stringList?.toTypedArray() ?: emptyArray()))
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_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"
}
}

View File

@@ -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
}

View File

@@ -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")

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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)
}