0.2.0 #71

Merged
altavir merged 139 commits from dev into master 2022-01-24 09:44:18 +03:00
38 changed files with 3208 additions and 267 deletions
Showing only changes of commit 6d5c00f88b - Show all commits

View File

@ -14,30 +14,22 @@
* [Features](#features) * [Features](#features)
* [About DataForge](#about-dataforge) * [About DataForge](#about-dataforge)
* [Modules contained in this repository](#modules-contained-in-this-repository) * [Modules contained in this repository](#modules-contained-in-this-repository)
* [visionforge-core](#visionforge-core) * [Visualization for External Systems](#visualization-for-external-systems)
* [visionforge-fx](#visionforge-fx)
* [visionforge-gdml](#visionforge-gdml)
* [visionforge-markdown](#visionforge-markdown)
* [visionforge-plotly](#visionforge-plotly)
* [visionforge-server](#visionforge-server)
* [visionforge-solid](#visionforge-solid)
* [visionforge-threejs](#visionforge-threejs)
* [Visualization for External Systems](#visualization-for-external-systems)
* [Demonstrations](#demonstrations) * [Demonstrations](#demonstrations)
* [Simple Example - Solid Showcase](#simple-example---solid-showcase) * [Simple Example - Solid Showcase](#simple-example---solid-showcase)
* [Full-Stack Application Example - Muon Monitor](#full-stack-application-example---muon-monitor-visualization) * [Full-Stack Application Example - Muon Monitor](#full-stack-application-example---muon-monitor-visualization)
* [GDML Example](#gdml-example) * [GDML Example](#gdml-example)
## Introduction ## Introduction
This repository contains a [DataForge](#about-dataforge)\-based framework This repository contains a [DataForge](#about-dataforge)\-based framework
used for visualization in various scientific applications. used for visualization in various scientific applications.
The main framework's use case for now is 3D visualization for particle physics experiments. The main framework's use case for now is 3D visualization for particle physics experiments.
Other applications including 2D plots are planned for the future. Other applications including 2D plots are planned for the future.
The project is developed as a [Kotlin multiplatform](https://kotlinlang.org/docs/reference/multiplatform.html) The project is developed as a [Kotlin multiplatform](https://kotlinlang.org/docs/reference/multiplatform.html)
application, currently targeting browser JavaScript and JVM. application, currently targeting browser JavaScript and JVM.
## Requirements ## Requirements
@ -62,39 +54,14 @@ Platform uses some of the concepts and modules of DataForge, including: `Meta`,
`Provider`, and some others. `Provider`, and some others.
To learn more about DataForge, please consult the following URLs: To learn more about DataForge, please consult the following URLs:
* [Kotlin multiplatform implementation of DataForge](https://github.com/mipt-npm/dataforge-core) * [Kotlin multiplatform implementation of DataForge](https://github.com/mipt-npm/dataforge-core)
* [DataForge documentation](http://npm.mipt.ru/dataforge/) * [DataForge documentation](http://npm.mipt.ru/dataforge/)
* [Original implementation of DataForge](https://bitbucket.org/Altavir/dataforge/src/default/) * [Original implementation of DataForge](https://bitbucket.org/Altavir/dataforge/src/default/)
## Modules contained in this repository ## Modules contained in this repository
### visionforge-core $modules
Contains a general hierarchy of classes and interfaces useful for visualization.
This module is not specific to 3D-visualization.
The `visionforge-core` module also includes configuration editors for JS (in `jsMain`) and JVM (in `jvmMain`).
**Class diagram:**
![](docs/images/class-diag-core.png)
### visionforge-fx
### visionforge-gdml
GDML bindings for 3D visualization (to be moved to gdml project).
### visionforge-markdown
### visionforge-plotly
### visionforge-server
### visionforge-solid
Includes common classes and serializers for 3D visualization, as well as Three.js and JavaFX implementations.
**Class diagram:** **Class diagram:**
@ -103,26 +70,26 @@ Includes common classes and serializers for 3D visualization, as well as Three.j
##### Prototypes ##### Prototypes
One of the important features of the framework is support for 3D object prototypes (sometimes One of the important features of the framework is support for 3D object prototypes (sometimes
also referred to as templates). The idea is that prototype geometry can be rendered once and reused also referred to as templates). The idea is that prototype geometry can be rendered once and reused
for multiple objects. This helps to significantly decrease memory usage. for multiple objects. This helps to significantly decrease memory usage.
The `prototypes` property tree is defined in `SolidGroup` class via `PrototypeHolder` interface, and The `prototypes` property tree is defined in `SolidGroup` class via `PrototypeHolder` interface, and
`SolidReference` class helps to reuse a template object. `SolidReference` class helps to reuse a template object.
##### Styles ##### Styles
`VisionGroup` has a `styleSheet` property that can optionally define styles at the Group's `VisionGroup` has a `styleSheet` property that can optionally define styles at the Group's
level. Styles are applied to child (descendant) objects using `Vision.styles: List<String>` property. level. Styles are applied to child (descendant) objects using `Vision.styles: List<String>` property.
### visionforge-threejs ### visionforge-threejs
## Visualization for External Systems ## Visualization for External Systems
The `visionforge` framework can be used to visualize geometry and events from external, The `visionforge` framework can be used to visualize geometry and events from external,
non-Kotlin based systems, such as ROOT. This will require a plugin to convert data model non-Kotlin based systems, such as ROOT. This will require a plugin to convert data model
of the external system to that of `visionforge`. Performing such integration is a work of the external system to that of `visionforge`. Performing such integration is a work
currently in progress. currently in progress.
## Demonstrations ## Demonstrations
@ -144,7 +111,7 @@ Some shapes will also periodically change their color and visibility.
### Full-Stack Application Example - Muon Monitor Visualization ### Full-Stack Application Example - Muon Monitor Visualization
A full-stack application example, showing the A full-stack application example, showing the
[Muon Monitor](http://npm.mipt.ru/en/projects/physics#mounMonitor) experiment set-up. [Muon Monitor](http://npm.mipt.ru/en/projects/physics#mounMonitor) experiment set-up.
[More details](demo/muon-monitor/README.md) [More details](demo/muon-monitor/README.md)
@ -156,7 +123,7 @@ A full-stack application example, showing the
### GDML Example ### GDML Example
Visualization example for geometry defined as GDML file. Visualization example for geometry defined as GDML file.
[More details](demo/gdml/README.md) [More details](demo/gdml/README.md)

View File

@ -1,26 +1,23 @@
plugins { plugins {
id("ru.mipt.npm.gradle.project") id("ru.mipt.npm.gradle.project")
id("org.jetbrains.kotlinx.kover") version "0.5.0-RC"
} }
val dataforgeVersion by extra("0.5.2") val dataforgeVersion by extra("0.5.2")
val fxVersion by extra("11") val fxVersion by extra("11")
allprojects { subprojects {
if (name.startsWith("visionforge")) apply<MavenPublishPlugin>()
repositories { repositories {
mavenLocal()
mavenCentral()
maven("https://repo.kotlin.link") maven("https://repo.kotlin.link")
mavenCentral()
maven("https://maven.jzy3d.org/releases") maven("https://maven.jzy3d.org/releases")
} }
group = "space.kscience" group = "space.kscience"
version = "0.2.0" version = "0.2.0-dev-99"
}
subprojects {
if (name.startsWith("visionforge")) {
plugins.apply("maven-publish")
}
} }
ksciencePublish { ksciencePublish {
@ -30,10 +27,10 @@ ksciencePublish {
} }
apiValidation { apiValidation {
validationDisabled = true
ignoredPackages.add("info.laht.threekt") ignoredPackages.add("info.laht.threekt")
} }
readme.readmeTemplate = file("docs/templates/README-TEMPLATE.md")
//workaround for https://youtrack.jetbrains.com/issue/KT-48273 //workaround for https://youtrack.jetbrains.com/issue/KT-48273
rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin::class.java) { rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin::class.java) {

View File

@ -16,6 +16,7 @@ kotlin {
jvm { jvm {
withJava() withJava()
} }
js { js {
useCommonJs() useCommonJs()
browser { browser {
@ -24,6 +25,7 @@ kotlin {
} }
} }
} }
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {

View File

@ -47,8 +47,9 @@ kotlin {
implementation(projects.visionforgeGdml) implementation(projects.visionforgeGdml)
implementation(projects.visionforgePlotly) implementation(projects.visionforgePlotly)
implementation(projects.visionforgeMarkdown) implementation(projects.visionforgeMarkdown)
implementation(projects.visionforgeTables)
implementation(projects.cernRootLoader) implementation(projects.cernRootLoader)
implementation(projects.jupyter.jupyterBase) implementation(projects.jupyter)
} }
} }
@ -56,6 +57,7 @@ kotlin {
dependencies { dependencies {
implementation(projects.ui.ring) implementation(projects.ui.ring)
implementation(projects.visionforgeThreejs) implementation(projects.visionforgeThreejs)
compileOnly(npm("webpack-bundle-analyzer","4.5.0"))
} }
} }
@ -66,6 +68,9 @@ kotlin {
implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6") implementation("com.github.Ricky12Awesome:json-schema-serialization:0.6.6")
} }
} }
all {
languageSettings.optIn("space.kscience.dataforge.misc.DFExperimental")
}
} }
} }

View File

@ -3,10 +3,12 @@ import space.kscience.visionforge.markup.MarkupPlugin
import space.kscience.visionforge.plotly.PlotlyPlugin import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.ring.ThreeWithControlsPlugin import space.kscience.visionforge.ring.ThreeWithControlsPlugin
import space.kscience.visionforge.runVisionClient import space.kscience.visionforge.runVisionClient
import space.kscience.visionforge.tables.TableVisionJsPlugin
@DFExperimental @DFExperimental
fun main() = runVisionClient { fun main() = runVisionClient {
plugin(ThreeWithControlsPlugin) plugin(ThreeWithControlsPlugin)
plugin(PlotlyPlugin) plugin(PlotlyPlugin)
plugin(MarkupPlugin) plugin(MarkupPlugin)
plugin(TableVisionJsPlugin)
} }

View File

@ -1,40 +0,0 @@
/* Remove default bullets */
ul, .tree {
list-style-type: none;
}
/* Style the caret/arrow */
.tree-caret {
cursor: pointer;
user-select: none; /* Prevent text selection */
}
/* Create the caret/arrow with a unicode, and style it */
.tree-caret::before {
content: "\25B6";
color: black;
display: inline-block;
margin-right: 6px;
}
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
.tree-caret-down::before {
transform: rotate(90deg);
}
ul, .tree {
list-style-type: none;
}
i, .tree-caret{
display: inline-block;
margin-right: 6px;
}
.rotate {
transform: rotate(90deg);
}
.tree-label-inactive {
color: gray;
}

View File

@ -1,16 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Playground</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
<link rel="stylesheet" href="css/common.css">
<script type="text/javascript" src="playground.js"></script>
</head>
<body class="application">
<div class="container">
<h1>Playground</h1>
</div>
<div id="app"></div>
</body>
</html>

View File

@ -0,0 +1,181 @@
package space.kscience.visionforge.examples
import kotlinx.html.h1
import kotlinx.html.h2
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.values.ValueType
import space.kscience.plotly.layout
import space.kscience.plotly.models.ScatterMode
import space.kscience.plotly.models.TextPosition
import space.kscience.plotly.scatter
import space.kscience.tables.ColumnHeader
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.markup.markdown
import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.plotly.plotly
import space.kscience.visionforge.solid.*
import space.kscience.visionforge.tables.TableVisionPlugin
import space.kscience.visionforge.tables.columnTable
import java.nio.file.Paths
fun main() {
val context = Context {
plugin(Solids)
plugin(PlotlyPlugin)
plugin(TableVisionPlugin)
}
context.makeVisionFile(
Paths.get("VisionForgeDemo.html"),
resourceLocation = ResourceLocation.EMBED
) {
markdown {
//language=markdown
"""
# VisionForge
This is a demo for current VisionForge features. This text is written in [MarkDown](https://github.com/JetBrains/markdown)
""".trimIndent()
}
h2 { +"3D visualization with Three-js" }
vision("3D") {
solid {
box(100, 100, 100, name = "aBox")
}
}
h2 { +"Interactive plots with Plotly" }
vision("plot") {
plotly {
scatter {
x(1, 2, 3, 4)
y(10, 15, 13, 17)
mode = ScatterMode.markers
name = "Team A"
text("A-1", "A-2", "A-3", "A-4", "A-5")
textposition = TextPosition.`top center`
textfont {
family = "Raleway, sans-serif"
}
marker { size = 12 }
}
scatter {
x(2, 3, 4, 5)
y(10, 15, 13, 17)
mode = ScatterMode.lines
name = "Team B"
text("B-a", "B-b", "B-c", "B-d", "B-e")
textposition = TextPosition.`bottom center`
textfont {
family = "Times New Roman"
}
marker { size = 12 }
}
layout {
title = "Data Labels Hover"
xaxis {
range(0.75..5.25)
}
legend {
y = 0.5
font {
family = "Arial, sans-serif"
size = 20
color("grey")
}
}
}
}
}
h2 { +"Interactive tables with Tabulator" }
vision("table") {
val x by ColumnHeader.value(ValueType.NUMBER)
val y by ColumnHeader.value(ValueType.NUMBER)
columnTable(
x to listOf(2, 3, 4, 5),
y to listOf(10, 15, 13, 17)
)
}
markdown {
//language=markdown
"""
## The code for everything above
```kotlin
markdown {
//language=markdown
""${'"'}
# VisionForge
This is a demo for current VisionForge features. This text is written in [MarkDown](https://github.com/JetBrains/markdown)
""${'"'}.trimIndent()
}
h2 { +"3D visualization with Three-js" }
vision("3D") {
solid {
box(100, 100, 100, name = "aBox")
}
}
h2 { +"Interactive plots with Plotly" }
vision("plot") {
plotly {
scatter {
x(1, 2, 3, 4)
y(10, 15, 13, 17)
mode = ScatterMode.markers
name = "Team A"
text("A-1", "A-2", "A-3", "A-4", "A-5")
textposition = TextPosition.`top center`
textfont {
family = "Raleway, sans-serif"
}
marker { size = 12 }
}
scatter {
x(2, 3, 4, 5)
y(10, 15, 13, 17)
mode = ScatterMode.lines
name = "Team B"
text("B-a", "B-b", "B-c", "B-d", "B-e")
textposition = TextPosition.`bottom center`
textfont {
family = "Times New Roman"
}
marker { size = 12 }
}
layout {
title = "Data Labels Hover"
xaxis {
range(0.75..5.25)
}
legend {
y = 0.5
font {
family = "Arial, sans-serif"
size = 20
color("grey")
}
}
}
}
}
h2 { +"Interactive tables with Tabulator" }
vision("table") {
val x by ColumnHeader.value(ValueType.NUMBER)
val y by ColumnHeader.value(ValueType.NUMBER)
columnTable(
x to listOf(2, 3, 4, 5),
y to listOf(10, 15, 13, 17)
)
}
```
""".trimIndent()
}
}
}

View File

@ -1,91 +0,0 @@
package space.kscience.visionforge.examples
import kotlinx.html.div
import kotlinx.html.h1
import space.kscience.dataforge.context.Context
import space.kscience.plotly.layout
import space.kscience.plotly.models.ScatterMode
import space.kscience.plotly.models.TextPosition
import space.kscience.plotly.scatter
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.markup.markdown
import space.kscience.visionforge.plotly.PlotlyPlugin
import space.kscience.visionforge.plotly.plotly
import space.kscience.visionforge.solid.*
fun main() {
val context = Context {
plugin(Solids)
plugin(PlotlyPlugin)
}
context.makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
markdown {
//language=markdown
"""
# Section
**TBD**
## Subsection
""".trimIndent()
}
div {
h1 { +"Canvas" }
vision("canvas") {
solid {
box(100, 100, 100)
material {
emissiveColor("red")
}
}
}
}
vision("plot") {
plotly {
scatter {
x(1, 2, 3, 4)
y(10, 15, 13, 17)
mode = ScatterMode.markers
name = "Team A"
text("A-1", "A-2", "A-3", "A-4", "A-5")
textposition = TextPosition.`top center`
textfont {
family = "Raleway, sans-serif"
}
marker { size = 12 }
}
scatter {
x(2, 3, 4, 5)
y(10, 15, 13, 17)
mode = ScatterMode.lines
name = "Team B"
text("B-a", "B-b", "B-c", "B-d", "B-e")
textposition = TextPosition.`bottom center`
textfont {
family = "Times New Roman"
}
marker { size = 12 }
}
layout {
title = "Data Labels Hover"
xaxis {
range(0.75..5.25)
}
legend {
y = 0.5
font {
family = "Arial, sans-serif"
size = 20
color("grey")
}
}
}
}
}
}
}

View File

@ -24,10 +24,13 @@ public fun Context.makeVisionFile(
title: String = "VisionForge page", title: String = "VisionForge page",
resourceLocation: ResourceLocation = ResourceLocation.SYSTEM, resourceLocation: ResourceLocation = ResourceLocation.SYSTEM,
show: Boolean = true, show: Boolean = true,
content: VisionTagConsumer<*>.() -> Unit content: VisionTagConsumer<*>.() -> Unit,
): Unit { ): Unit {
val actualPath = visionManager.page(title, content = content).makeFile(path) { actualPath -> val actualPath = visionManager.page(title, content = content).makeFile(path) { actualPath ->
mapOf("playground" to scriptHeader("js/visionforge-playground.js", resourceLocation, actualPath)) mapOf(
"playground" to scriptHeader("js/visionforge-playground.js", resourceLocation, actualPath),
//"tables" to tabulatorCssHader
)
} }
if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI()) if (show) Desktop.getDesktop().browse(actualPath.toFile().toURI())
} }

View File

@ -0,0 +1,28 @@
package space.kscience.visionforge.examples
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.values.ValueType
import space.kscience.tables.ColumnHeader
import space.kscience.tables.valueRow
import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.tables.TableVisionPlugin
import space.kscience.visionforge.tables.table
import kotlin.math.pow
fun main() {
val context = Context {
plugin(TableVisionPlugin)
}
val x by ColumnHeader.value(ValueType.NUMBER)
val y by ColumnHeader.value(ValueType.NUMBER)
context.makeVisionFile(resourceLocation = ResourceLocation.SYSTEM) {
vision {
table(x, y) {
repeat(100) {
valueRow(x to it, y to it.toDouble().pow(2))
}
}
}
}
}

View File

@ -1,3 +1,22 @@
const ringConfig = require('@jetbrains/ring-ui/webpack.config').config; const ringConfig = require('@jetbrains/ring-ui/webpack.config').config;
config.module.rules.push(...ringConfig.module.rules) config.module.rules.push(...ringConfig.module.rules)
config.module.rules.push(
{
test: /\.css$/,
exclude: [
'D:\\Work\\Projects\\visionforge\\build\\js\\node_modules\\@jetbrains\\ring-ui'
],
use: [
{
loader: 'style-loader',
options: {}
},
{
loader: 'css-loader',
options: {}
}
]
}
)

View File

@ -0,0 +1,10 @@
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
module.exports = {
plugins: [
new BundleAnalyzerPlugin({
analyzerMode: "static",
reportFilename: "bundle-report.html"
})
]
}

26
docs/templates/ARTIFACT-TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,26 @@
## Artifact:
The Maven coordinates of this project are `${group}:${name}:${version}`.
**Gradle Groovy:**
```gradle
repositories {
maven { url 'https://repo.kotlin.link' }
mavenCentral()
}
dependencies {
implementation '${group}:${name}:${version}'
}
```
**Gradle Kotlin DSL:**
```kotlin
repositories {
maven("https://repo.kotlin.link")
mavenCentral()
}
dependencies {
implementation("${group}:${name}:${version}")
}
```

138
docs/templates/README-TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,138 @@
[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
[![DOI](https://zenodo.org/badge/174502624.svg)](https://zenodo.org/badge/latestdoi/174502624)
![Gradle build](https://github.com/mipt-npm/visionforge/workflows/Gradle%20build/badge.svg)
[![Slack](https://img.shields.io/badge/slack-channel-green?logo=slack)](https://kotlinlang.slack.com/archives/CEXV2QWNM)
# DataForge Visualization Platform
## Table of Contents
* [Introduction](#introduction)
* [Requirements](#requirements)
* [Features](#features)
* [About DataForge](#about-dataforge)
* [Modules contained in this repository](#modules-contained-in-this-repository)
* [Visualization for External Systems](#visualization-for-external-systems)
* [Demonstrations](#demonstrations)
* [Simple Example - Solid Showcase](#simple-example---solid-showcase)
* [Full-Stack Application Example - Muon Monitor](#full-stack-application-example---muon-monitor-visualization)
* [GDML Example](#gdml-example)
## Introduction
This repository contains a [DataForge](#about-dataforge)\-based framework
used for visualization in various scientific applications.
The main framework's use case for now is 3D visualization for particle physics experiments.
Other applications including 2D plots are planned for the future.
The project is developed as a [Kotlin multiplatform](https://kotlinlang.org/docs/reference/multiplatform.html)
application, currently targeting browser JavaScript and JVM.
## Requirements
JVM backend requires JDK 11 or later
## Features
The main framework's features for now include:
- 3D visualization of complex experimental set-ups
- Event display such as particle tracks, etc.
- Scales up to few hundred thousands of elements
- Camera move, rotate, zoom-in and zoom-out
- Scene graph as an object tree with property editor
- Settings export and import
- Multiple platform support
## About DataForge
DataForge is a software framework for automated scientific data processing. DataForge Visualization
Platform uses some of the concepts and modules of DataForge, including: `Meta`, `Configuration`, `Context`,
`Provider`, and some others.
To learn more about DataForge, please consult the following URLs:
* [Kotlin multiplatform implementation of DataForge](https://github.com/mipt-npm/dataforge-core)
* [DataForge documentation](http://npm.mipt.ru/dataforge/)
* [Original implementation of DataForge](https://bitbucket.org/Altavir/dataforge/src/default/)
## Modules contained in this repository
$modules
**Class diagram:**
![](docs/images/class-diag-solid.png)
##### Prototypes
One of the important features of the framework is support for 3D object prototypes (sometimes
also referred to as templates). The idea is that prototype geometry can be rendered once and reused
for multiple objects. This helps to significantly decrease memory usage.
The `prototypes` property tree is defined in `SolidGroup` class via `PrototypeHolder` interface, and
`SolidReference` class helps to reuse a template object.
##### Styles
`VisionGroup` has a `styleSheet` property that can optionally define styles at the Group's
level. Styles are applied to child (descendant) objects using `Vision.styles: List<String>` property.
### visionforge-threejs
## Visualization for External Systems
The `visionforge` framework can be used to visualize geometry and events from external,
non-Kotlin based systems, such as ROOT. This will require a plugin to convert data model
of the external system to that of `visionforge`. Performing such integration is a work
currently in progress.
## Demonstrations
The `demo` module contains several example projects (demonstrations) of using the `visionforge` framework.
They are briefly described in this section, for more details please consult the corresponding per-project
README file.
### Simple Example - Solid Showcase
Contains a simple demonstration with a grid including a few shapes that you can rotate, move camera, and so on.
Some shapes will also periodically change their color and visibility.
[More details](demo/solid-showcase/README.md)
**Example view:**
![](docs/images/solid-showcase.png)
### Full-Stack Application Example - Muon Monitor Visualization
A full-stack application example, showing the
[Muon Monitor](http://npm.mipt.ru/en/projects/physics#mounMonitor) experiment set-up.
[More details](demo/muon-monitor/README.md)
**Example view:**
![](docs/images/muon-monitor.png)
### GDML Example
Visualization example for geometry defined as GDML file.
[More details](demo/gdml/README.md)
##### Example view:
![](docs/images/gdml-demo.png)
## Thanks and references
The original three.js bindings were made by [Lars Ivar Hatledal](https://github.com/markaren), but the project is discontinued right now.
All other libraries are explicitly shown as dependencies. We would like to express specific thanks to JetBrains Kotlin-JS team for consulting us during the work.

View File

@ -33,7 +33,7 @@ kotlin {
commonMain { commonMain {
dependencies { dependencies {
implementation(projects.visionforgeSolid) implementation(projects.visionforgeSolid)
implementation(projects.jupyter.jupyterBase) implementation(projects.jupyter)
} }
} }
jvmMain { jvmMain {

View File

@ -1,9 +1,14 @@
rootProject.name = "visionforge"
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
enableFeaturePreview("VERSION_CATALOGS")
pluginManagement { pluginManagement {
val toolsVersion: String by extra val toolsVersion: String by extra
repositories { repositories {
//mavenLocal() mavenLocal()
maven("https://repo.kotlin.link") maven("https://repo.kotlin.link")
mavenCentral() mavenCentral()
gradlePluginPortal() gradlePluginPortal()
@ -17,16 +22,12 @@ pluginManagement {
} }
} }
rootProject.name = "visionforge"
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
enableFeaturePreview("VERSION_CATALOGS")
dependencyResolutionManagement { dependencyResolutionManagement {
val toolsVersion: String by extra val toolsVersion: String by extra
repositories { repositories {
mavenLocal()
maven("https://repo.kotlin.link") maven("https://repo.kotlin.link")
mavenCentral() mavenCentral()
} }
@ -53,6 +54,7 @@ include(
":cern-root-loader", ":cern-root-loader",
":visionforge-server", ":visionforge-server",
":visionforge-plotly", ":visionforge-plotly",
":visionforge-tables",
":visionforge-markdown", ":visionforge-markdown",
":demo:solid-showcase", ":demo:solid-showcase",
":demo:gdml", ":demo:gdml",
@ -61,6 +63,6 @@ include(
":demo:playground", ":demo:playground",
":demo:plotly-fx", ":demo:plotly-fx",
":demo:js-playground", ":demo:js-playground",
":jupyter:jupyter-base", ":jupyter",
":jupyter:visionforge-jupyter-gdml" ":jupyter:visionforge-jupyter-gdml"
) )

View File

@ -82,8 +82,10 @@ public val ThreeCanvasWithControls: FC<ThreeCanvasWithControlsProps> = fc("Three
useEffect { useEffect {
props.context.launch { props.context.launch {
solid = props.builderOfSolid.await().also { solid = props.builderOfSolid.await()
it?.setAsRoot(props.context.visionManager) //ensure that the solid is properly rooted
if(solid?.parent == null){
solid?.setAsRoot(props.context.visionManager)
} }
} }
} }

View File

@ -4,12 +4,6 @@ plugins {
val dataforgeVersion: String by rootProject.extra val dataforgeVersion: String by rootProject.extra
kscience{
useSerialization{
json()
}
}
kotlin { kotlin {
sourceSets { sourceSets {
commonMain { commonMain {
@ -25,4 +19,14 @@ kotlin {
} }
} }
} }
}
kscience{
useSerialization{
json()
}
}
readme{
maturity = ru.mipt.npm.gradle.Maturity.DEVELOPMENT
} }

View File

@ -163,6 +163,6 @@ internal class RootVisionGroup(override val manager: VisionManager) : VisionGrou
* Designate this [VisionGroup] as a root and assign a [VisionManager] as its parent * Designate this [VisionGroup] as a root and assign a [VisionManager] as its parent
*/ */
public fun Vision.setAsRoot(manager: VisionManager) { public fun Vision.setAsRoot(manager: VisionManager) {
if (parent != null) error("This Vision already has a parent. It could not be set as root") if (parent != null) error("Vision $this already has a parent. It could not be set as root")
parent = RootVisionGroup(manager) parent = RootVisionGroup(manager)
} }

View File

@ -78,6 +78,10 @@ public abstract class VisionTagConsumer<R>(
} }
} }
/**
* Insert a vision in this HTML.
* TODO replace by multi-receiver
*/
@OptIn(DFExperimental::class) @OptIn(DFExperimental::class)
public inline fun <T> TagConsumer<T>.vision( public inline fun <T> TagConsumer<T>.vision(
name: Name, name: Name,
@ -122,6 +126,8 @@ public abstract class VisionTagConsumer<R>(
public const val OUTPUT_FETCH_ATTRIBUTE: String = "data-output-fetch" public const val OUTPUT_FETCH_ATTRIBUTE: String = "data-output-fetch"
public const val OUTPUT_CONNECT_ATTRIBUTE: String = "data-output-connect" 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_NAME_ATTRIBUTE: String = "data-output-name"
public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint" public const val OUTPUT_ENDPOINT_ATTRIBUTE: String = "data-output-endpoint"
public const val DEFAULT_ENDPOINT: String = "." public const val DEFAULT_ENDPOINT: String = "."

View File

@ -19,6 +19,7 @@ import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_CONNEC
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_ENDPOINT_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_FETCH_ATTRIBUTE
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_NAME_ATTRIBUTE import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_NAME_ATTRIBUTE
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_RENDERED
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
@ -64,7 +65,8 @@ public class VisionClient : AbstractPlugin() {
private fun renderVision(name: String, element: Element, vision: Vision?, outputMeta: Meta) { private fun renderVision(name: String, element: Element, vision: Vision?, outputMeta: Meta) {
if (vision != null) { if (vision != null) {
val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision") val renderer = findRendererFor(vision)
?: error("Could not find renderer for ${visionManager.encodeToString(vision)}")
renderer.render(element, vision, outputMeta) renderer.render(element, vision, outputMeta)
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr -> element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr ->
@ -138,10 +140,15 @@ public class VisionClient : AbstractPlugin() {
* 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 [VisionTagConsumer.OUTPUT_CLASS] class.
*/ */
public fun renderVisionIn(element: Element) { public fun renderVisionIn(element: Element) {
val name = resolveName(element) ?: error("The element is not a vision output")
logger.info { "Found DF output with name $name" }
if (!element.classList.contains(VisionTagConsumer.OUTPUT_CLASS)) error("The element $element is not an output element") if (!element.classList.contains(VisionTagConsumer.OUTPUT_CLASS)) error("The element $element is not an output element")
val name = resolveName(element) ?: error("The element is not a vision output")
if (element.attributes[OUTPUT_RENDERED]?.value == "true") {
logger.info { "VF output in element $element is already rendered" }
return
} else {
logger.info { "Rendering VF output with name $name" }
}
val outputMeta = element.getEmbeddedData(VisionTagConsumer.OUTPUT_META_CLASS)?.let { val outputMeta = element.getEmbeddedData(VisionTagConsumer.OUTPUT_META_CLASS)?.let {
VisionManager.defaultJson.decodeFromString(MetaSerializer, it) VisionManager.defaultJson.decodeFromString(MetaSerializer, it)
@ -186,6 +193,7 @@ public class VisionClient : AbstractPlugin() {
} }
else -> error("No embedded vision data / fetch url for $name") else -> error("No embedded vision data / fetch url for $name")
} }
element.setAttribute(OUTPUT_RENDERED, "true")
} }
override fun content(target: String): Map<Name, Any> = if (target == ElementVisionRenderer.TYPE) mapOf( override fun content(target: String): Map<Name, Any> = if (target == ElementVisionRenderer.TYPE) mapOf(
@ -254,5 +262,5 @@ public fun runVisionClient(contextBuilder: ContextBuilder.() -> Unit) {
val visionClient = context.fetch(VisionClient) val visionClient = context.fetch(VisionClient)
window.asDynamic()[RENDER_FUNCTION_NAME] = visionClient::renderAllVisionsById window.asDynamic()[RENDER_FUNCTION_NAME] = visionClient::renderAllVisionsById
visionClient.renderAllVisions() //visionClient.renderAllVisions()
} }

View File

@ -19,4 +19,8 @@ dependencies {
implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7") { implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7") {
exclude(module = "slf4j-simple") exclude(module = "slf4j-simple")
} }
}
readme{
maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE
} }

View File

@ -31,20 +31,16 @@ public actual class PlotlyPlugin : VisionPlugin(), ElementVisionRenderer {
override fun render(element: Element, vision: Vision, meta: Meta) { override fun render(element: Element, vision: Vision, meta: Meta) {
val plot = (vision as? VisionOfPlotly)?.plot ?: error("VisionOfPlotly expected but ${vision::class} found") val plot = (vision as? VisionOfPlotly)?.plot ?: error("VisionOfPlotly expected but ${vision::class} found")
val config = PlotlyConfig.read(meta) val config = PlotlyConfig.read(meta)
// println(plot.meta)
// println(plot.data[0].toMeta())
element.plot(plot, config) element.plot(plot, config)
} }
override fun content(target: String): Map<Name, Any> { override fun content(target: String): Map<Name, Any> = when (target) {
return when (target) { ElementVisionRenderer.TYPE -> mapOf("plotly".asName() to this)
ElementVisionRenderer.TYPE -> mapOf("plotly".asName() to this) else -> super.content(target)
else -> super.content(target)
}
} }
public actual companion object : PluginFactory<PlotlyPlugin> { public actual companion object : PluginFactory<PlotlyPlugin> {
override val tag: PluginTag = PluginTag("vision.plotly", PluginTag.DATAFORGE_GROUP) override val tag: PluginTag = PluginTag("vision.plotly.js", PluginTag.DATAFORGE_GROUP)
override val type: KClass<PlotlyPlugin> = PlotlyPlugin::class override val type: KClass<PlotlyPlugin> = PlotlyPlugin::class
override fun invoke(meta: Meta, context: Context): PlotlyPlugin = PlotlyPlugin() override fun invoke(meta: Meta, context: Context): PlotlyPlugin = PlotlyPlugin()
} }

View File

@ -1,22 +0,0 @@
package space.kscience.visionforge.plotly
//internal val plotlyScriptLocation = "js/visionforge-three.js"
//
///**
// * A header that stores/embeds plotly bundle and registers plotly renderer in the frontend
// */
//@OptIn(DFExperimental::class)
//public fun plotlyHeader(location: ResourceLocation, filePath: Path? = null): HtmlFragment = {
// scriptHeader(
// plotlyScriptLocation,
// resourceLocation = location,
// htmlPath = filePath
// ).invoke(this)
// script {
// type = "text/javascript"
// unsafe {
// //language=JavaScript
// +"space.kscience.visionforge.plotly.loadPlotly()"
// }
// }
//}

View File

@ -9,7 +9,7 @@ import space.kscience.dataforge.meta.Meta
import space.kscience.visionforge.VisionPlugin import space.kscience.visionforge.VisionPlugin
import kotlin.reflect.KClass import kotlin.reflect.KClass
public actual class PlotlyPlugin : VisionPlugin(), Plugin { public actual class PlotlyPlugin : VisionPlugin() {
override val tag: PluginTag get() = Companion.tag override val tag: PluginTag get() = Companion.tag

View File

@ -3,7 +3,9 @@ plugins {
} }
kscience{ kscience{
useSerialization() useSerialization{
json()
}
} }
kotlin { kotlin {
@ -14,4 +16,8 @@ kotlin {
} }
} }
} }
}
readme{
maturity = ru.mipt.npm.gradle.Maturity.DEVELOPMENT
} }

View File

@ -0,0 +1,40 @@
plugins {
id("ru.mipt.npm.gradle.mpp")
}
val tablesVersion = "0.1.4"
kscience {
useSerialization()
}
kotlin {
js {
useCommonJs()
binaries.library()
browser{
commonWebpackConfig{
cssSupport.enabled = true
}
}
}
sourceSets {
commonMain {
dependencies {
api(project(":visionforge-core"))
api("space.kscience:tables-kt:${tablesVersion}")
}
}
jsMain {
dependencies {
implementation(npm("tabulator-tables", "5.0.1"))
implementation(npm("@types/tabulator-tables", "5.0.1"))
}
}
}
}
readme{
maturity = ru.mipt.npm.gradle.Maturity.PROTOTYPE
}

View File

@ -0,0 +1,30 @@
package space.kscience.visionforge.tables
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.PluginFactory
import space.kscience.dataforge.context.PluginTag
import space.kscience.dataforge.meta.Meta
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionManager
import space.kscience.visionforge.VisionPlugin
import kotlin.reflect.KClass
public class TableVisionPlugin : VisionPlugin() {
override val tag: PluginTag get() = Companion.tag
override val visionSerializersModule: SerializersModule
get() = SerializersModule {
polymorphic(Vision::class) {
subclass(VisionOfTable.serializer())
}
}
public companion object : PluginFactory<TableVisionPlugin> {
override val tag: PluginTag = PluginTag("vision.table", PluginTag.DATAFORGE_GROUP)
override val type: KClass<TableVisionPlugin> = TableVisionPlugin::class
override fun invoke(meta: Meta, context: Context): TableVisionPlugin = TableVisionPlugin()
}
}

View File

@ -0,0 +1,113 @@
package space.kscience.visionforge.tables
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import space.kscience.dataforge.meta.*
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.values.Null
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
import space.kscience.tables.*
import space.kscience.visionforge.VisionBase
import space.kscience.visionforge.html.VisionOutput
import kotlin.jvm.JvmName
import kotlin.reflect.typeOf
internal object ColumnHeaderSerializer : KSerializer<ColumnHeader<Value>> {
override val descriptor: SerialDescriptor get() = MetaSerializer.descriptor
override fun deserialize(decoder: Decoder): ColumnHeader<Value> {
val meta = decoder.decodeSerializableValue(MetaSerializer)
return SimpleColumnHeader(meta["name"].string!!, typeOf<Value>(), meta["meta"] ?: Meta.EMPTY)
}
override fun serialize(encoder: Encoder, value: ColumnHeader<Value>) {
val meta = Meta {
"name" put value.name
"meta" put value.meta
}
encoder.encodeSerializableValue(MetaSerializer, meta)
}
}
public val ColumnHeader<Value>.properties: ValueColumnScheme get() = ValueColumnScheme.read(meta)
@Serializable
@SerialName("vision.table")
public class VisionOfTable(
override val headers: List<@Serializable(ColumnHeaderSerializer::class) ColumnHeader<Value>>,
) : VisionBase(), Rows<Value> {
public var data: List<Meta>
get() = meta.getIndexed("rows").entries.sortedBy { it.key?.toInt() }.map { it.value }
set(value) {
meta["rows"] = value
}
public val rows: List<MetaRow> get() = data.map(::MetaRow)
override fun rowSequence(): Sequence<Row<Value>> = rows.asSequence()
}
/**
* Convert a table to a serializable vision
*/
@Suppress("UNCHECKED_CAST")
public fun <T> Table<T>.toVision(
converter: (T?) -> Value,
): VisionOfTable = VisionOfTable(headers as TableHeader<Value>).also { vision ->
vision.data = rows.map { row ->
if (row is MetaRow) {
row.meta
} else {
Meta {
headers.forEach {
it.name put converter(row[it.name])
}
}
}
}
}
@JvmName("valueTableToVision")
public fun Table<Value>.toVision(): VisionOfTable = toVision { it ?: Null }
@JvmName("stringTableToVision")
public fun Table<String>.toVision(): VisionOfTable = toVision { (it ?: "").asValue() }
@JvmName("numberTableToVision")
public fun Table<Number>.toVision(): VisionOfTable = toVision { (it ?: Double.NaN).asValue() }
@DFExperimental
public inline fun VisionOutput.table(
vararg headers: ColumnHeader<Value>,
block: MutableRowTable<Value>.() -> Unit,
): VisionOfTable = RowTable(*headers, block = block).toVision()
@DFExperimental
public inline fun VisionOutput.columnTable(
columnSize: UInt,
block: MutableColumnTable<Value>.() -> Unit,
): VisionOfTable = ColumnTable(columnSize, block).toVision()
@DFExperimental
public fun VisionOutput.columnTable(
vararg dataAndHeaders: Pair<ColumnHeader<Value>, List<Any?>>,
): VisionOfTable {
val columns = dataAndHeaders.map { (header, data) ->
ListColumn(header, data.map { Value.of(it) })
}
return ColumnTable(columns).toVision()
}
//public val tabulatorCssHader: HtmlFragment = {
// link {
// href = "https://unpkg.com/tabulator-tables@5.0.10/dist/css/tabulator.min.css"
// rel = "stylesheet"
// }
//}

View File

@ -0,0 +1,32 @@
package space.kscience.visionforge.tables
import space.kscience.dataforge.values.Value
import space.kscience.dataforge.values.asValue
import space.kscience.dataforge.values.double
import space.kscience.dataforge.values.int
import space.kscience.tables.ColumnHeader
import space.kscience.tables.ColumnTable
import space.kscience.tables.get
import kotlin.math.pow
import kotlin.test.Test
import kotlin.test.assertEquals
internal class VisionOfTableTest {
@Test
fun tableSerialization() {
val x by ColumnHeader.typed<Value>()
val y by ColumnHeader.typed<Value>()
val table = ColumnTable<Value>(100U) {
x.fill { it.asValue() }
y.values = x.values.map { it?.double?.pow(2)?.asValue() }
}
val vision = table.toVision()
//println(Json.encodeToString(VisionOfTable.serializer(), table.toVision()))
val rows = vision.rowSequence().toList()
assertEquals(50, rows[50][x]?.int)
}
}

View File

@ -0,0 +1,96 @@
package space.kscience.visionforge.tables
import kotlinext.js.jso
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement
import space.kscience.dataforge.context.AbstractPlugin
import space.kscience.dataforge.context.Context
import space.kscience.dataforge.context.PluginFactory
import space.kscience.dataforge.context.PluginTag
import space.kscience.dataforge.meta.DynamicMeta
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.toDynamic
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.asName
import space.kscience.visionforge.ElementVisionRenderer
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionClient
import tabulator.Tabulator
import tabulator.TabulatorFull
import kotlin.reflect.KClass
public class TableVisionJsPlugin : AbstractPlugin(), ElementVisionRenderer {
public val visionClient: VisionClient by require(VisionClient)
public val tablesBase: TableVisionPlugin by require(TableVisionPlugin)
override val tag: PluginTag get() = Companion.tag
override fun attach(context: Context) {
super.attach(context)
kotlinext.js.require("tabulator-tables/dist/css/tabulator.min.css")
kotlinext.js.require("tabulator-tables/src/js/modules/ResizeColumns/ResizeColumns.js")
}
override fun rateVision(vision: Vision): Int = when (vision) {
is VisionOfTable -> ElementVisionRenderer.DEFAULT_RATING
else -> ElementVisionRenderer.ZERO_RATING
}
override fun render(element: Element, vision: Vision, meta: Meta) {
val table: VisionOfTable = (vision as? VisionOfTable)
?: error("VisionOfTable expected but ${vision::class} found")
val tableOptions = jso<Tabulator.Options> {
columns = table.headers.map { header ->
jso<Tabulator.ColumnDefinition> {
field = header.name
title = header.properties.title ?: header.name
resizable = true
}
}.toTypedArray()
columns = Array(table.headers.size + 1){
if(it==0){
jso {
field = "@index"
title = "#"
resizable = false
}
} else {
val header = table.headers[it-1]
jso {
field = header.name
title = header.properties.title ?: header.name
resizable = true
}
}
}
data = table.rows.mapIndexed { index, row->
val d = row.meta.toDynamic()
d["@index"] = index
d
}.toTypedArray()
//layout = "fitColumns"
pagination = true
paginationSize = 10
paginationSizeSelector = arrayOf(10, 25, 50, 100)
}
TabulatorFull(element as HTMLElement, tableOptions)
}
override fun content(target: String): Map<Name, Any> = when (target) {
ElementVisionRenderer.TYPE -> mapOf("table".asName() to this)
else -> super.content(target)
}
public companion object : PluginFactory<TableVisionJsPlugin> {
override val tag: PluginTag = PluginTag("vision.table.js", PluginTag.DATAFORGE_GROUP)
override val type: KClass<TableVisionJsPlugin> = TableVisionJsPlugin::class
override fun invoke(meta: Meta, context: Context): TableVisionJsPlugin = TableVisionJsPlugin()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
@file:Suppress("NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING")
package tabulator
import org.w3c.dom.events.UIEvent
@Suppress("UNUSED_TYPEALIAS_PARAMETER")
internal typealias Pick<T, K> = Any
@Suppress("UNUSED_TYPEALIAS_PARAMETER")
internal typealias Record<K, T> = Any
internal typealias FilterFunction = (field: String, type: String /* "=" | "!=" | "like" | "<" | ">" | "<=" | ">=" | "in" | "regex" | "starts" | "ends" */, value: Any, filterParams: Tabulator.FilterParams) -> Unit
internal typealias GroupValuesArg = Array<Array<Any>>
internal typealias CustomMutator = (value: Any, data: Any, type: String /* "data" | "edit" */, mutatorParams: Any, cell: Tabulator.CellComponent) -> Any
internal typealias CustomAccessor = (value: Any, data: Any, type: String /* "data" | "download" | "clipboard" */, AccessorParams: Any, column: Tabulator.ColumnComponent, row: Tabulator.RowComponent) -> Any
internal typealias ColumnCalcParams = (values: Any, data: Any) -> Any
internal typealias ValueStringCallback = (value: Any) -> String
internal typealias ValueBooleanCallback = (value: Any) -> Boolean
internal typealias ValueVoidCallback = (value: Any) -> Unit
internal typealias EmptyCallback = (callback: () -> Unit) -> Unit
internal typealias CellEventCallback = (e: UIEvent, cell: Tabulator.CellComponent) -> Unit
internal typealias CellEditEventCallback = (cell: Tabulator.CellComponent) -> Unit
internal typealias ColumnEventCallback = (e: UIEvent, column: Tabulator.ColumnComponent) -> Unit
internal typealias RowEventCallback = (e: UIEvent, row: Tabulator.RowComponent) -> Unit
internal typealias RowChangedCallback = (row: Tabulator.RowComponent) -> Unit
internal typealias GroupEventCallback = (e: UIEvent, group: Tabulator.GroupComponent) -> Unit
internal typealias JSONRecord = Record<String, dynamic /* String | Number | Boolean */>
internal typealias ColumnSorterParamLookupFunction = (column: Tabulator.ColumnComponent, dir: String /* "asc" | "desc" */) -> Any

View File

@ -5,6 +5,7 @@ import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.TextBufferGeometry import info.laht.threekt.geometries.TextBufferGeometry
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
import kotlinext.js.jsObject import kotlinext.js.jsObject
import kotlinext.js.jso
import space.kscience.dataforge.context.logger import space.kscience.dataforge.context.logger
import space.kscience.dataforge.context.warn import space.kscience.dataforge.context.warn
import space.kscience.visionforge.onPropertyChange import space.kscience.visionforge.onPropertyChange
@ -18,7 +19,7 @@ public object ThreeLabelFactory : ThreeFactory<SolidLabel> {
override val type: KClass<in SolidLabel> get() = SolidLabel::class override val type: KClass<in SolidLabel> get() = SolidLabel::class
override fun invoke(three: ThreePlugin, obj: SolidLabel): Object3D { override fun invoke(three: ThreePlugin, obj: SolidLabel): Object3D {
val textGeo = TextBufferGeometry(obj.text, jsObject { val textGeo = TextBufferGeometry(obj.text, jso {
font = obj.fontFamily font = obj.fontFamily
size = 20 size = 20
height = 1 height = 1