Compare commits
58 Commits
@hediet/vi
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e6dc9a5ad | ||
|
|
21fa200446 | ||
|
|
5b0d979a23 | ||
|
|
96115a9a70 | ||
|
|
ac15ceb29c | ||
|
|
f089760a72 | ||
|
|
1990749b4a | ||
|
|
0460009bd2 | ||
|
|
ffcd3c9ad1 | ||
|
|
97431a5c89 | ||
|
|
d8e18743c0 | ||
|
|
8e6c7f3c6f | ||
|
|
4be4717e61 | ||
|
|
2fcb84a84d | ||
|
|
fdd4927a50 | ||
|
|
1d0e559eb3 | ||
|
|
188d10128a | ||
|
|
4cab6cc1b5 | ||
|
|
b6bf5feee0 | ||
|
|
7576ed1b7d | ||
|
|
f337e22a64 | ||
|
|
7310851d2b | ||
|
|
814b33720d | ||
|
|
ec221b506e | ||
|
|
4029023074 | ||
|
|
cf382c71c9 | ||
|
|
5512c0b244 | ||
|
|
b358a5a7b5 | ||
|
|
810e100e66 | ||
|
|
16440b39c5 | ||
|
|
3ff96aff56 | ||
|
|
107ba3f8e8 | ||
|
|
cd9f7aa5ef | ||
|
|
27a616d43f | ||
|
|
c9143877f7 | ||
|
|
7f158bd500 | ||
|
|
340a5a5315 | ||
|
|
aa2502452e | ||
|
|
9277f7a99a | ||
|
|
620458924b | ||
|
|
3cdbd1d9b1 | ||
|
|
2c8ac3977c | ||
|
|
4a564eca9b | ||
|
|
33de54d696 | ||
|
|
3a64fd9704 | ||
|
|
6bdf8c3b61 | ||
|
|
84d59393ee | ||
|
|
743d0db6c9 | ||
|
|
f7fa31995e | ||
|
|
20c52ff4a7 | ||
|
|
8b6d65614d | ||
|
|
1fe6ae1270 | ||
|
|
1911a8092e | ||
|
|
b46c7155b2 | ||
|
|
3c41b3c0bf | ||
|
|
c0e10d9a97 | ||
|
|
41357e6821 | ||
|
|
93ea01fc79 |
23
.github/workflows/ci.yml
vendored
Normal file
23
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [macos-latest, ubuntu-latest, windows-latest]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 10.x
|
||||||
|
- run: yarn install
|
||||||
|
- run: yarn build
|
||||||
26
.github/workflows/playground.yml
vendored
Normal file
26
.github/workflows/playground.yml
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
name: Release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Release
|
||||||
|
runs-on: ubuntu-18.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 12
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn install
|
||||||
|
- name: Build
|
||||||
|
run: yarn build
|
||||||
|
- name: Publish github pages
|
||||||
|
run: |
|
||||||
|
git remote set-url origin https://git:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git
|
||||||
|
yarn workspace playground run pub
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@@ -1,3 +1,5 @@
|
|||||||
{
|
{
|
||||||
"tasksStatusbar.taskLabelFilter": "dev"
|
"tasksStatusbar.taskLabelFilter": "dev",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"typescript.tsdk": "node_modules\\typescript\\lib"
|
||||||
}
|
}
|
||||||
|
|||||||
78
README.md
78
README.md
@@ -1,76 +1,16 @@
|
|||||||
# Visualization Framework
|
# Visualization Framework
|
||||||
|
|
||||||
## Installation
|
This repository contains two packages.
|
||||||
|
|
||||||
You can use yarn to install the core library:
|
## @hediet/visualization-core
|
||||||
|
|
||||||
```
|
This package provides basic infrastructure for implementing visualizations.
|
||||||
yarn add @hediet/visualization-core
|
See its readme [here](./visualization-core/README.md).
|
||||||
```
|
|
||||||
|
|
||||||
If you want to use the existing visualizations, add the bundle library:
|
## @hediet/visualization-bundle
|
||||||
|
|
||||||
```
|
This package bundles all the visualizations.
|
||||||
yarn add @hediet/visualization-bundle
|
See its readme [here](./visualization-bundle/README.md).
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
If you want to add your own visualization to this project, visualization-bundle is the right place!
|
||||||
|
The readme documents how to implement your own visualization.
|
||||||
Import `@hediet/visualization-bundle` to register all default visualizations:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
// This registers all visualizations
|
|
||||||
import "@hediet/visualization-bundle";
|
|
||||||
```
|
|
||||||
|
|
||||||
Don't forget to import the styles in your scss file:
|
|
||||||
|
|
||||||
```scss
|
|
||||||
@import "~@hediet/visualization-bundle/style.scss";
|
|
||||||
```
|
|
||||||
|
|
||||||
Use `globalVisualizationFactory` to visualize some data:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { globalVisualizationFactory } from "@hediet/visualization-core";
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
kind: { graph: true as true },
|
|
||||||
nodes: [
|
|
||||||
{ id: "1", label: "1" },
|
|
||||||
{ id: "2", label: "2", color: "orange" },
|
|
||||||
{ id: "3", label: "3" },
|
|
||||||
],
|
|
||||||
edges: [
|
|
||||||
{ from: "1", to: "2", color: "red" },
|
|
||||||
{ from: "1", to: "3" },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const visualizations = globalVisualizationFactory.getVisualizations(
|
|
||||||
data,
|
|
||||||
/* preferred visualization id */ undefined
|
|
||||||
);
|
|
||||||
// `visualizations.bestVisualization` is the visualization that is best suited to visualize the data.
|
|
||||||
// `visualiztaions.allVisualizations` contains all other available visualizations than can visualize the data.
|
|
||||||
```
|
|
||||||
|
|
||||||
If you use react, you can use the `VisualizationView` component to render a visualization:
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
import { VisualizationView, Theme } from "@hediet/visualization-core";
|
|
||||||
|
|
||||||
// const visualizations = ...
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
if (!visualizations.bestVisualization) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<VisualizationView
|
|
||||||
theme={Theme.light}
|
|
||||||
visualization={visualizations.bestVisualization}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -6,7 +6,13 @@
|
|||||||
"./simple-demo",
|
"./simple-demo",
|
||||||
"./playground"
|
"./playground"
|
||||||
],
|
],
|
||||||
"scripts": {},
|
"scripts": {
|
||||||
|
"build": "yarn build-core && yarn build-bundle && yarn build-playground && yarn build-simple-demo",
|
||||||
|
"build-core": "yarn workspace @hediet/visualization-core run build",
|
||||||
|
"build-bundle": "yarn workspace @hediet/visualization-bundle run build",
|
||||||
|
"build-playground": "yarn workspace playground run build",
|
||||||
|
"build-simple-demo": "yarn workspace simple-demo run build"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "^1.19.1"
|
"prettier": "^1.19.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,25 +3,27 @@
|
|||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "webpack-dev-server --hot --port 8080",
|
"dev": "webpack-dev-server --hot",
|
||||||
"build": "webpack",
|
"build": "yarn build-webpack && yarn build-docs",
|
||||||
"pub": "gh-pages -d dist"
|
"build-webpack": "node --max_old_space_size=4096 --openssl-legacy-provider ../node_modules/webpack/bin/webpack.js --mode production",
|
||||||
|
"build-docs": "ts-node ./scripts/build-docs",
|
||||||
|
"pub": "gh-pages -d dist -u 'github-actions-bot <support+actions@github.com>'"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@blueprintjs/core": "^3.23.1",
|
"@blueprintjs/core": "^3.23.1",
|
||||||
"@hediet/std": "^0.6.0",
|
"@hediet/std": "^0.6.0",
|
||||||
"@hediet/semantic-json": "^0.3.11",
|
"@hediet/semantic-json": "^0.3.14",
|
||||||
"blueprintjs": "^0.0.8",
|
"blueprintjs": "^0.0.8",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"mobx": "^5.15.4",
|
"mobx": "^5.15.4",
|
||||||
"mobx-react": "^6.1.8",
|
"mobx-react": "^6.1.8",
|
||||||
"monaco-editor": "^0.20.0",
|
"monaco-editor": "^0.25.2",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
"messagepack": "^1.1.10",
|
"messagepack": "^1.1.10",
|
||||||
"base64-js": "^1.3.1",
|
"base64-js": "^1.3.1",
|
||||||
"lzma": "^2.3.2",
|
"lzma": "^2.3.2",
|
||||||
"@hediet/monaco-editor-react": "^0.1.0"
|
"@hediet/monaco-editor-react": "^0.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"gh-pages": "^2.2.0",
|
"gh-pages": "^2.2.0",
|
||||||
@@ -35,9 +37,10 @@
|
|||||||
"file-loader": "^5.1.0",
|
"file-loader": "^5.1.0",
|
||||||
"fork-ts-checker-webpack-plugin": "^4.0.4",
|
"fork-ts-checker-webpack-plugin": "^4.0.4",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"monaco-editor-webpack-plugin": "^1.9.0",
|
"monaco-editor-webpack-plugin": "^4.0.0",
|
||||||
"raw-loader": "^4.0.0",
|
"raw-loader": "^4.0.0",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
|
"sass": "^1.25.0",
|
||||||
"style-loader": "^1.1.3",
|
"style-loader": "^1.1.3",
|
||||||
"ts-loader": "^6.2.1",
|
"ts-loader": "^6.2.1",
|
||||||
"ts-node": "^8.6.2",
|
"ts-node": "^8.6.2",
|
||||||
|
|||||||
34
playground/scripts/build-docs.ts
Normal file
34
playground/scripts/build-docs.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import {
|
||||||
|
JsonSchemaGenerator,
|
||||||
|
TypeScriptTypeGenerator,
|
||||||
|
} from "@hediet/semantic-json";
|
||||||
|
import { writeFileSync, mkdirSync } from "fs";
|
||||||
|
import { join } from "path";
|
||||||
|
import { globalVisualizationFactory } from "@hediet/visualization-core";
|
||||||
|
import "../src/visualizations";
|
||||||
|
|
||||||
|
const tsGen = new TypeScriptTypeGenerator();
|
||||||
|
const tp = tsGen.getType(globalVisualizationFactory.getSerializer());
|
||||||
|
|
||||||
|
const tsSrc = `export type KnownVisualizationData = ${tp};\n\n\n${[
|
||||||
|
...tsGen.definitions.values(),
|
||||||
|
]
|
||||||
|
.map(v => v.getDefinitionSource({ exported: true }) + `\n\n`)
|
||||||
|
.join("")}`;
|
||||||
|
|
||||||
|
const targetDir = join(__dirname, "../dist/docs");
|
||||||
|
|
||||||
|
mkdirSync(targetDir, { recursive: true });
|
||||||
|
|
||||||
|
writeFileSync(join(targetDir, "visualization-data.ts"), tsSrc, {
|
||||||
|
encoding: "utf-8",
|
||||||
|
});
|
||||||
|
|
||||||
|
const schema = new JsonSchemaGenerator().getJsonSchemaWithDefinitions(
|
||||||
|
globalVisualizationFactory.getSerializer()
|
||||||
|
);
|
||||||
|
const jsonSchema = JSON.stringify(schema, undefined, 4);
|
||||||
|
|
||||||
|
writeFileSync(join(targetDir, "visualization-data-schema.json"), jsonSchema, {
|
||||||
|
encoding: "utf-8",
|
||||||
|
});
|
||||||
@@ -174,7 +174,7 @@ class VisualizationConfigView extends React.Component<{
|
|||||||
if (visualizations.bestVisualization) {
|
if (visualizations.bestVisualization) {
|
||||||
const tsTypeGenerator = new TypeScriptTypeGenerator();
|
const tsTypeGenerator = new TypeScriptTypeGenerator();
|
||||||
const typeSrc = tsTypeGenerator.getType(
|
const typeSrc = tsTypeGenerator.getType(
|
||||||
visualizations.bestVisualization.sourceVisualizer.serializer.asSerializer()
|
visualizations.bestVisualizationVisualizer!.serializer.asSerializer()
|
||||||
);
|
);
|
||||||
const decl = tsTypeGenerator.getDefinitionSource();
|
const decl = tsTypeGenerator.getDefinitionSource();
|
||||||
return {
|
return {
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,3 +1,5 @@
|
|||||||
|
(globalThis as any).MonacoEnvironment = { globalAPI: true };
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ReactDOM from "react-dom";
|
import * as ReactDOM from "react-dom";
|
||||||
import "./style.scss";
|
import "./style.scss";
|
||||||
|
|||||||
@@ -61,10 +61,7 @@ body {
|
|||||||
@import "./vscode-dark.scss";
|
@import "./vscode-dark.scss";
|
||||||
@import "./vscode-light.scss";
|
@import "./vscode-light.scss";
|
||||||
|
|
||||||
.visualization {
|
|
||||||
background: var(--vscode-editor-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.themeable {
|
.themeable {
|
||||||
|
background-color: violet; // To debug backgrounds
|
||||||
--visualizer-simple-text-color: var(--vscode-editor-foreground);
|
--visualizer-simple-text-color: var(--vscode-editor-foreground);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,27 @@
|
|||||||
//// Uncomment the visualization that you work on to increase webpack build speed!
|
//// Uncomment the visualization that you work on to increase webpack build speed!
|
||||||
|
//// If you create a new visualization, add it here!
|
||||||
// import "@hediet/visualization-bundle/dist/visualizers/ast-visualizer"; /*
|
|
||||||
// import "@hediet/visualization-bundle/dist/visualizers/graph/dot-graphviz-visualizer" /*
|
// import "@hediet/visualization-bundle/dist/visualizers/ast-visualizer"; /*
|
||||||
// import "@hediet/visualization-bundle/dist/visualizers/graph/graph-graphviz-visualizer" /*
|
// import "@hediet/visualization-bundle/dist/visualizers/graph/dot-graphviz-visualizer" /*
|
||||||
// import "@hediet/visualization-bundle/dist/visualizers/graph/graph-visjs-visualizer" /*
|
// import "@hediet/visualization-bundle/dist/visualizers/graph/graph-graphviz-visualizer" /*
|
||||||
// import "@hediet/visualization-bundle/dist/visualizers/grid-visualizer" /*
|
// import "@hediet/visualization-bundle/dist/visualizers/graph/graph-visjs-visualizer" /*
|
||||||
// import "@hediet/visualization-bundle/dist/visualizers/image-visualizer"; /*
|
// import "@hediet/visualization-bundle/dist/visualizers/grid-visualizer" /*
|
||||||
// import "@hediet/visualization-bundle/dist/visualizers/monaco-text-visualizer" /*
|
// import "@hediet/visualization-bundle/dist/visualizers/image-visualizer"; /*
|
||||||
// import "@hediet/visualization-bundle/dist/visualizers/perspective-table-visualizer" /*
|
// import "@hediet/visualization-bundle/dist/visualizers/monaco-text-visualizer" /*
|
||||||
// import "@hediet/visualization-bundle/dist/visualizers/plotly-visualizer" /*
|
// import "@hediet/visualization-bundle/dist/visualizers/monaco-text-diff-visualizer"; /*
|
||||||
// import "@hediet/visualization-bundle/dist/visualizers/simple-text-visualizer"; /*
|
// import "@hediet/visualization-bundle/dist/visualizers/perspective-table-visualizer" /*
|
||||||
// import "@hediet/visualization-bundle/dist/visualizers/source-visualizer" /*
|
// import "@hediet/visualization-bundle/dist/visualizers/plotly-visualizer" /*
|
||||||
// import "@hediet/visualization-bundle/dist/visualizers/svg-visualizer" /*
|
// import "@hediet/visualization-bundle/dist/visualizers/simple-text-visualizer"; /*
|
||||||
// import "@hediet/visualization-bundle/dist/visualizers/tree-visualizer"; /*
|
// import "@hediet/visualization-bundle/dist/visualizers/source-visualizer"; /*
|
||||||
|
// import "@hediet/visualization-bundle/dist/visualizers/svg-visualizer" /*
|
||||||
// This bundles the monaco editor. Uncomment it to load monaco dynamically.
|
// import "@hediet/visualization-bundle/dist/visualizers/tree-visualizer"; /*
|
||||||
// Dynamic loading increases webpack build speed significantly.
|
|
||||||
import "monaco-editor";
|
// This bundles the monaco editor. Uncomment it to load monaco dynamically.
|
||||||
|
// Dynamic loading increases webpack build speed significantly.
|
||||||
import "@hediet/visualization-bundle"; // */
|
if (typeof process === undefined) {
|
||||||
|
// We check for process so that we don't load monaco from nodejs.
|
||||||
|
require("monaco-editor");
|
||||||
|
}
|
||||||
|
|
||||||
|
// This import bundles *all* visualizations. This makes webpack super slow.
|
||||||
|
import "@hediet/visualization-bundle"; // */
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ module.exports = {
|
|||||||
plugins: (() => {
|
plugins: (() => {
|
||||||
const plugins: any[] = [
|
const plugins: any[] = [
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
title: "Debug Visualizer",
|
title: "Visualization Playground",
|
||||||
}),
|
}),
|
||||||
new ForkTsCheckerWebpackPlugin(),
|
new ForkTsCheckerWebpackPlugin(),
|
||||||
new CleanWebpackPlugin(),
|
new CleanWebpackPlugin(),
|
||||||
@@ -64,7 +64,6 @@ module.exports = {
|
|||||||
|
|
||||||
plugins.push(
|
plugins.push(
|
||||||
new MonacoWebpackPlugin({
|
new MonacoWebpackPlugin({
|
||||||
// Add more languages here once webworker issues are solved.
|
|
||||||
languages: ["typescript", "json"],
|
languages: ["typescript", "json"],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "webpack-dev-server --hot --port 8090",
|
"dev": "webpack-dev-server --hot --port 8090",
|
||||||
"build": "webpack"
|
"build": "node --max_old_space_size=4096 --openssl-legacy-provider ../node_modules/webpack/bin/webpack.js --mode production"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hediet/visualization-core": "^0.1.0",
|
"@hediet/visualization-core": "^0.1.0",
|
||||||
@@ -22,9 +22,10 @@
|
|||||||
"file-loader": "^5.1.0",
|
"file-loader": "^5.1.0",
|
||||||
"fork-ts-checker-webpack-plugin": "^4.0.4",
|
"fork-ts-checker-webpack-plugin": "^4.0.4",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"monaco-editor-webpack-plugin": "^1.9.0",
|
"monaco-editor-webpack-plugin": "^4.0.0",
|
||||||
"raw-loader": "^4.0.0",
|
"raw-loader": "^4.0.0",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
|
"sass": "^1.25.0",
|
||||||
"style-loader": "^1.1.3",
|
"style-loader": "^1.1.3",
|
||||||
"ts-loader": "^6.2.1",
|
"ts-loader": "^6.2.1",
|
||||||
"ts-node": "^8.6.2",
|
"ts-node": "^8.6.2",
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ module.exports = {
|
|||||||
|
|
||||||
plugins.push(
|
plugins.push(
|
||||||
new MonacoWebpackPlugin({
|
new MonacoWebpackPlugin({
|
||||||
// Add more languages here once webworker issues are solved.
|
|
||||||
languages: ["typescript", "json"],
|
languages: ["typescript", "json"],
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
# Visualization Bundle
|
# Visualization Bundle
|
||||||
|
|
||||||
|
This package registers various visualizations. If you have an idea for a cool visualization, add it here!
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
You can use yarn to install this library:
|
You can use yarn to install this library:
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn add @hediet/visualization-bundle
|
yarn add @hediet/visualization-bundle
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Install `@hediet/visualization-core` before!
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Import `@hediet/visualization-bundle` to register all default visualizations to `globalVisualizationFactory` of `@hediet/visualization-core`:
|
Import `@hediet/visualization-bundle` to register all default visualizations to `globalVisualizationFactory` of `@hediet/visualization-core`:
|
||||||
@@ -21,11 +27,38 @@ Don't forget to import the styles in your scss file:
|
|||||||
@import "~@hediet/visualization-bundle/style.scss";
|
@import "~@hediet/visualization-bundle/style.scss";
|
||||||
```
|
```
|
||||||
|
|
||||||
|
You will need to configure webpack
|
||||||
|
|
||||||
|
- to compile scss files:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{ test: /\.scss$/, loader: "style-loader!css-loader!sass-loader" },
|
||||||
|
```
|
||||||
|
|
||||||
|
- to compile less files:
|
||||||
|
|
||||||
|
```js
|
||||||
|
{ test: /\.less$/, loaders: ["style-loader", "css-loader", "less-loader"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
- to register monaco workers
|
||||||
|
|
||||||
|
```js
|
||||||
|
plugins: [
|
||||||
|
// ...
|
||||||
|
new MonacoWebpackPlugin({
|
||||||
|
languages: ["typescript", "json"],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
See docs of `@hediet/visualization-core` for how to find and render visualizations!
|
See docs of `@hediet/visualization-core` for how to find and render visualizations!
|
||||||
|
|
||||||
## Supported Visualizations
|
## Supported Visualizations
|
||||||
|
|
||||||
### Origin Visualizations
|
You can find a playground that demonstrates all visualizations [here](https://hediet.github.io/visualization/).
|
||||||
|
|
||||||
|
### Source Visualizations
|
||||||
|
|
||||||
- Tree Visualizer
|
- Tree Visualizer
|
||||||
- AST Visualizer (+ Monaco Source Code View)
|
- AST Visualizer (+ Monaco Source Code View)
|
||||||
@@ -34,7 +67,7 @@ See docs of `@hediet/visualization-core` for how to find and render visualizatio
|
|||||||
- Image Visualizer
|
- Image Visualizer
|
||||||
- Simple Text Visualizer
|
- Simple Text Visualizer
|
||||||
|
|
||||||
### Integrated Visualizations
|
### Integrated Visualization Libraries
|
||||||
|
|
||||||
- Plotly Visualizer
|
- Plotly Visualizer
|
||||||
- Perspective JS Visualizer
|
- Perspective JS Visualizer
|
||||||
@@ -43,3 +76,27 @@ See docs of `@hediet/visualization-core` for how to find and render visualizatio
|
|||||||
- Graphviz Dot Visualizer
|
- Graphviz Dot Visualizer
|
||||||
- Vis.js Visualizer
|
- Vis.js Visualizer
|
||||||
- Monaco Editor Source Code Visualizer
|
- Monaco Editor Source Code Visualizer
|
||||||
|
|
||||||
|
## Setup Local Development Copy
|
||||||
|
|
||||||
|
- Clone this repository.
|
||||||
|
- Run `yarn` in the root folder.
|
||||||
|
- Run `yarn dev` in the `visualization-bundle` folder to start tsc in watch mode.
|
||||||
|
- Edit [./playground/src/visualization.ts](../playground/src/visualizations.ts) to your needs to improve webpack performance. This is highly recommended, but not required.
|
||||||
|
- Run `yarn dev` in the `playground` folder to start the playground where you can debug your visualization.
|
||||||
|
|
||||||
|
Any changes to the source are now reflected in the playground!
|
||||||
|
|
||||||
|
## Implementing New Visualizations
|
||||||
|
|
||||||
|
First, setup your local development copy.
|
||||||
|
|
||||||
|
You can use the [`simple-text-visualizer`](./src/visualizers/simple-text-visualizer/index.tsx) as starting point!
|
||||||
|
Just copy the folder, rename the ids and names, export it [here](./src/visualizers/index.ts) and read through all the comments of the simple-text-visualizer!
|
||||||
|
|
||||||
|
Use the "open" button in the playground to debug your new visualization!
|
||||||
|
|
||||||
|
## Machine Readable Documents
|
||||||
|
|
||||||
|
- [TypeScript Declarations](https://hediet.github.io/visualization/docs/visualization-data.ts)
|
||||||
|
- [JSON Schema](https://hediet.github.io/visualization/docs/visualization-data-schema.json)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@hediet/visualization-bundle",
|
"name": "@hediet/visualization-bundle",
|
||||||
"description": "Bundles several visualizers",
|
"description": "Bundles several visualizers",
|
||||||
"version": "0.1.0",
|
"version": "0.2.5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsc --watch",
|
"dev": "tsc --watch",
|
||||||
@@ -14,46 +14,43 @@
|
|||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
"src"
|
"src",
|
||||||
|
"style.scss"
|
||||||
],
|
],
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hediet/std": "^0.6.0",
|
|
||||||
"@hediet/semantic-json": "^0.3.11",
|
|
||||||
"classnames": "^2.2.6",
|
|
||||||
"mobx": "^5.15.4",
|
|
||||||
"mobx-react": "^6.1.8",
|
|
||||||
"monaco-editor": "^0.20.0",
|
|
||||||
"react": "^16.12.0",
|
|
||||||
"react-dom": "^16.12.0",
|
|
||||||
"react-measure": "^2.3.0",
|
|
||||||
"react-svg-pan-zoom": "^3.8.0",
|
|
||||||
"line-column": "^1.0.2",
|
|
||||||
"vis-network": "^8.0.0",
|
|
||||||
"vis-data": "^7.0.0",
|
|
||||||
"viz.js": "^2.1.2",
|
|
||||||
"react-plotly.js": "^2.4.0",
|
|
||||||
"plotly.js": "^1.54.7",
|
|
||||||
"@finos/perspective-viewer": "^0.5.2",
|
"@finos/perspective-viewer": "^0.5.2",
|
||||||
"@finos/perspective-viewer-d3fc": "^0.5.2",
|
"@finos/perspective-viewer-d3fc": "^0.5.2",
|
||||||
"@finos/perspective-viewer-datagrid": "^0.5.2",
|
"@finos/perspective-viewer-datagrid": "^0.5.2",
|
||||||
"@hediet/monaco-editor-react": "^0.1.0"
|
"@hediet/monaco-editor-react": "^0.2.0",
|
||||||
|
"@hediet/semantic-json": "^0.3.15",
|
||||||
|
"@hediet/std": "^0.6.0",
|
||||||
|
"classnames": "^2.2.6",
|
||||||
|
"line-column": "^1.0.2",
|
||||||
|
"mobx": "^5.15.4",
|
||||||
|
"mobx-react": "^6.1.8",
|
||||||
|
"monaco-editor": "^0.25.2",
|
||||||
|
"plotly.js": "^1.54.7",
|
||||||
|
"react": "^16.12.0",
|
||||||
|
"react-dom": "^16.12.0",
|
||||||
|
"react-measure": "^2.3.0",
|
||||||
|
"react-plotly.js": "^2.4.0",
|
||||||
|
"react-svg-pan-zoom": "^3.8.0",
|
||||||
|
"vis-data": "^7.0.0",
|
||||||
|
"vis-network": "^8.0.0",
|
||||||
|
"viz.js": "^2.1.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@hediet/visualization-core": "^0.1.0"
|
"@hediet/visualization-core": "0.*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/plotly.js": "^1.50.16",
|
|
||||||
"@types/react-plotly.js": "^2.2.4",
|
|
||||||
"@types/react-measure": "^2.0.6",
|
|
||||||
"@types/react-svg-pan-zoom": "^3.3.0",
|
|
||||||
"@types/webpack": "^4.41.6",
|
|
||||||
"@types/classnames": "^2.2.9",
|
"@types/classnames": "^2.2.9",
|
||||||
|
"@types/line-column": "^1.0.0",
|
||||||
|
"@types/plotly.js": "^1.50.16",
|
||||||
"@types/react": "^16.9.22",
|
"@types/react": "^16.9.22",
|
||||||
"@types/react-dom": "^16.9.5",
|
"@types/react-dom": "^16.9.5",
|
||||||
"@types/line-column": "^1.0.0",
|
"@types/webpack": "^4.41.6",
|
||||||
"sass": "^1.25.0",
|
|
||||||
"ts-loader": "^6.2.1",
|
"ts-loader": "^6.2.1",
|
||||||
"ts-node": "^8.6.2",
|
"ts-node": "^8.6.2",
|
||||||
"typescript": "^3.9.7"
|
"typescript": "^3.9.7"
|
||||||
|
|||||||
18
visualization-bundle/src/types.d.ts
vendored
Normal file
18
visualization-bundle/src/types.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
declare module "react-measure" {
|
||||||
|
const content: any;
|
||||||
|
export default content;
|
||||||
|
|
||||||
|
export type ContentRect = any;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "react-plotly.js" {
|
||||||
|
const content: any;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "react-svg-pan-zoom" {
|
||||||
|
export class Tool { }
|
||||||
|
export type ReactSVGPanZoom = any;
|
||||||
|
export const ReactSVGPanZoom = undefined as any;
|
||||||
|
export class Value { }
|
||||||
|
}
|
||||||
@@ -1,29 +1,30 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { observable, runInAction } from "mobx";
|
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
|
import { Loadable } from "./Loadable";
|
||||||
|
|
||||||
export function makeLazyLoadable<TProps>(
|
export function makeLazyLoadable<TProps>(
|
||||||
selector: () => Promise<React.ComponentClass<TProps>>
|
selector: () => Promise<React.ComponentClass<TProps>>
|
||||||
): React.ComponentClass<TProps> {
|
): React.ComponentClass<TProps> & { preload(): Promise<void> } {
|
||||||
|
const loader = new Loadable(selector);
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class MyComponent extends React.Component<TProps> {
|
class MyComponent extends React.Component<TProps> {
|
||||||
@observable
|
constructor(props: any) {
|
||||||
component: React.ComponentClass<TProps> | undefined;
|
super(props);
|
||||||
|
loader.load();
|
||||||
async componentDidMount() {
|
|
||||||
const result = await selector();
|
|
||||||
runInAction("Set Lazily Loaded Component", () => {
|
|
||||||
this.component = result;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.component) {
|
if (!loader.result) {
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
const C = this.component;
|
const C = loader.result;
|
||||||
return <C {...this.props} />;
|
return <C {...this.props} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async preload(): Promise<void> {
|
||||||
|
await loader.load();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return MyComponent;
|
return MyComponent;
|
||||||
|
|||||||
22
visualization-bundle/src/utils/Loadable.tsx
Normal file
22
visualization-bundle/src/utils/Loadable.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { observable, runInAction } from "mobx";
|
||||||
|
|
||||||
|
export class Loadable<T> {
|
||||||
|
@observable.ref
|
||||||
|
public result: T | undefined;
|
||||||
|
|
||||||
|
private operation?: Promise<void> = undefined;
|
||||||
|
|
||||||
|
constructor(private readonly _load: () => Promise<T>) {}
|
||||||
|
|
||||||
|
public load(): Promise<void> {
|
||||||
|
if (!this.operation) {
|
||||||
|
this.operation = (async () => {
|
||||||
|
const r = await this._load();
|
||||||
|
runInAction(() => {
|
||||||
|
this.result = r;
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
return this.operation;
|
||||||
|
}
|
||||||
|
}
|
||||||
6
visualization-bundle/src/vars.scss
Normal file
6
visualization-bundle/src/vars.scss
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
.theme-dark {
|
||||||
|
--visualizer-background: #263238;
|
||||||
|
}
|
||||||
|
.theme-light {
|
||||||
|
--visualizer-background: white;
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import * as React from "react";
|
|||||||
import { observer, disposeOnUnmount } from "mobx-react";
|
import { observer, disposeOnUnmount } from "mobx-react";
|
||||||
import { observable, autorun, trace, computed } from "mobx";
|
import { observable, autorun, trace, computed } from "mobx";
|
||||||
import * as monacoTypes from "monaco-editor";
|
import * as monacoTypes from "monaco-editor";
|
||||||
import { getLanguageId } from "../monaco-text-visualizer/MonacoEditor";
|
import { getLanguageId } from "../monaco-text-visualizer/getLanguageId";
|
||||||
import {
|
import {
|
||||||
TreeWithPathView,
|
TreeWithPathView,
|
||||||
TreeViewModel,
|
TreeViewModel,
|
||||||
@@ -37,7 +37,13 @@ export class AstTree extends React.Component<{
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Measure client>
|
<Measure client>
|
||||||
{({ measureRef, contentRect }) => {
|
{({
|
||||||
|
measureRef,
|
||||||
|
contentRect,
|
||||||
|
}: {
|
||||||
|
measureRef: any;
|
||||||
|
contentRect: any;
|
||||||
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={measureRef}
|
ref={measureRef}
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ import {
|
|||||||
import { visualizationNs } from "../../consts";
|
import { visualizationNs } from "../../consts";
|
||||||
import {
|
import {
|
||||||
createVisualizer,
|
createVisualizer,
|
||||||
ReactVisualization,
|
|
||||||
globalVisualizationFactory,
|
globalVisualizationFactory,
|
||||||
|
createReactVisualization,
|
||||||
} from "@hediet/visualization-core";
|
} from "@hediet/visualization-core";
|
||||||
import { createTreeViewModelFromTreeNodeData } from "../tree-visualizer";
|
import { createTreeViewModelFromTreeNodeData } from "../tree-visualizer";
|
||||||
import { AstTree, NodeInfo } from "./AstVisualizer";
|
import { AstTree, NodeInfo } from "./AstVisualizer";
|
||||||
import React from "react";
|
import * as React from "react";
|
||||||
import LineColumn from "line-column";
|
import LineColumn = require("line-column");
|
||||||
import * as monacoTypes from "monaco-editor";
|
import * as monacoTypes from "monaco-editor";
|
||||||
import { getLoadedMonaco } from "@hediet/monaco-editor-react";
|
import { getLoadedMonaco } from "@hediet/monaco-editor-react";
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@ export const sAstTreeNode: Serializer<AstTreeNode> = sLazy(() =>
|
|||||||
{ inclusive: true }
|
{ inclusive: true }
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
}).defineAs(visualizationNs("TreeNodeItem"))
|
}).defineAs(visualizationNs("AstTreeNodeItem"))
|
||||||
),
|
),
|
||||||
segment: sOptionalProp(sString()),
|
segment: sOptionalProp(sString()),
|
||||||
isMarked: sOptionalProp(sBoolean()),
|
isMarked: sOptionalProp(sBoolean()),
|
||||||
@@ -62,25 +62,26 @@ export const sAstTreeNode: Serializer<AstTreeNode> = sLazy(() =>
|
|||||||
start: sNumber(),
|
start: sNumber(),
|
||||||
length: sNumber(),
|
length: sNumber(),
|
||||||
}),
|
}),
|
||||||
}).defineAs(visualizationNs("TreeNode"))
|
}).defineAs(visualizationNs("AstTreeNode"))
|
||||||
);
|
);
|
||||||
|
|
||||||
export const sAstTree = sOpenObject({
|
export const sAstTree = sOpenObject({
|
||||||
kind: sOpenObject({
|
kind: sOpenObject({
|
||||||
|
ast: sLiteral(true),
|
||||||
tree: sLiteral(true),
|
tree: sLiteral(true),
|
||||||
text: sLiteral(true),
|
text: sLiteral(true),
|
||||||
}),
|
}),
|
||||||
root: sAstTreeNode,
|
root: sAstTreeNode,
|
||||||
text: sString(),
|
text: sString(),
|
||||||
fileName: sOptionalProp(sString()),
|
fileName: sOptionalProp(sString()),
|
||||||
});
|
}).defineAs(visualizationNs("AstTreeVisualizationData"));
|
||||||
|
|
||||||
export const astVisualizer = createVisualizer({
|
export const astVisualizer = createVisualizer({
|
||||||
id: "ast",
|
id: "ast",
|
||||||
name: "AST",
|
name: "AST",
|
||||||
serializer: sAstTree,
|
serializer: sAstTree,
|
||||||
getVisualization: (data, self) =>
|
getVisualization: (data, self) =>
|
||||||
new ReactVisualization(self, { priority: 1500 }, theme => {
|
createReactVisualization(self, { priority: 1500 }, ({ theme }) => {
|
||||||
const m = createTreeViewModelFromTreeNodeData(
|
const m = createTreeViewModelFromTreeNodeData(
|
||||||
data.root,
|
data.root,
|
||||||
node => node.span
|
node => node.span
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
.themeable {
|
||||||
|
--visualizer-ast-background: var(--visualizer-background);
|
||||||
|
}
|
||||||
.theme-dark {
|
.theme-dark {
|
||||||
--visualizer-ast-marked-background: rgba(234, 92, 0, 0.33);
|
--visualizer-ast-marked-background: rgba(234, 92, 0, 0.33);
|
||||||
--visualizer-ast-selected-background: #515c6a;
|
--visualizer-ast-selected-background: #515c6a;
|
||||||
@@ -8,6 +11,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.component-AstTree {
|
.component-AstTree {
|
||||||
|
background: var(--visualizer-ast-background);
|
||||||
|
|
||||||
.part-editor {
|
.part-editor {
|
||||||
//border-top: solid gray 1px;
|
//border-top: solid gray 1px;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { observer, disposeOnUnmount } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { observable, autorun, runInAction } from "mobx";
|
|
||||||
import { SvgViewer } from "../../svg-visualizer/SvgViewer";
|
import { SvgViewer } from "../../svg-visualizer/SvgViewer";
|
||||||
|
import { Loadable } from "../../../utils/Loadable";
|
||||||
|
|
||||||
class VisLoader {
|
class VisLoader {
|
||||||
private result: any | undefined;
|
private result: any | undefined;
|
||||||
@@ -24,26 +24,29 @@ class VisLoader {
|
|||||||
|
|
||||||
const vizLoader = new VisLoader();
|
const vizLoader = new VisLoader();
|
||||||
|
|
||||||
|
export function getSvgFromDotCode(dotCode: string): Loadable<string> {
|
||||||
|
return new Loadable(async () => {
|
||||||
|
const viz = await vizLoader.getViz();
|
||||||
|
return await viz.renderString(dotCode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class GraphvizDotViewer extends React.Component<{
|
export class GraphvizDotViewer extends React.Component<{
|
||||||
dotCode: string;
|
svgSource: Loadable<string>;
|
||||||
svgRef?: (element: SVGSVGElement | null) => void;
|
svgRef?: (element: SVGSVGElement | null) => void;
|
||||||
}> {
|
}> {
|
||||||
@observable private svg: string | null = null;
|
|
||||||
|
|
||||||
@disposeOnUnmount
|
|
||||||
// @ts-ignore
|
|
||||||
private readonly _updateSvgAutorun = autorun(async () => {
|
|
||||||
const dotCode = this.props.dotCode; // to trigger mobx dependency
|
|
||||||
const viz = await vizLoader.getViz();
|
|
||||||
const svg = await viz.renderString(dotCode);
|
|
||||||
runInAction("Update svg", () => (this.svg = svg));
|
|
||||||
});
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (!this.svg) {
|
const { svgSource } = this.props;
|
||||||
|
svgSource.load();
|
||||||
|
if (!svgSource.result) {
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
return <SvgViewer svgRef={this.props.svgRef} svgContent={this.svg} />;
|
return (
|
||||||
|
<SvgViewer
|
||||||
|
svgRef={this.props.svgRef}
|
||||||
|
svgContent={svgSource.result}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import React from "react";
|
import * as React from "react";
|
||||||
import { sOpenObject, sLiteral, sString } from "@hediet/semantic-json";
|
import { sOpenObject, sLiteral, sString } from "@hediet/semantic-json";
|
||||||
import { GraphvizDotViewer } from "./GraphvizDotVisualizer";
|
import { getSvgFromDotCode, GraphvizDotViewer } from "./GraphvizDotVisualizer";
|
||||||
import {
|
import {
|
||||||
ReactVisualization,
|
|
||||||
globalVisualizationFactory,
|
globalVisualizationFactory,
|
||||||
createVisualizer,
|
createVisualizer,
|
||||||
|
createReactVisualization,
|
||||||
} from "@hediet/visualization-core";
|
} from "@hediet/visualization-core";
|
||||||
|
import { visualizationNs } from "../../../consts";
|
||||||
|
|
||||||
export const graphvizDotVisualizer = createVisualizer({
|
export const graphvizDotVisualizer = createVisualizer({
|
||||||
id: "graphviz-dot",
|
id: "graphviz-dot",
|
||||||
@@ -15,11 +16,18 @@ export const graphvizDotVisualizer = createVisualizer({
|
|||||||
dotGraph: sLiteral(true),
|
dotGraph: sLiteral(true),
|
||||||
}),
|
}),
|
||||||
text: sString(),
|
text: sString(),
|
||||||
}),
|
}).defineAs(visualizationNs("GraphvizDotVisualizationData")),
|
||||||
getVisualization: (data, self) =>
|
getVisualization: (data, self) => {
|
||||||
new ReactVisualization(self, { priority: 1500 }, () => (
|
const svgSource = getSvgFromDotCode(data.text);
|
||||||
<GraphvizDotViewer dotCode={data.text} />
|
return createReactVisualization(
|
||||||
)),
|
self,
|
||||||
|
{
|
||||||
|
priority: 1500,
|
||||||
|
preload: () => svgSource.load(),
|
||||||
|
},
|
||||||
|
() => <GraphvizDotViewer svgSource={svgSource} />
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
globalVisualizationFactory.addVisualizer(graphvizDotVisualizer);
|
globalVisualizationFactory.addVisualizer(graphvizDotVisualizer);
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
import { observer } from "mobx-react";
|
|
||||||
import * as React from "react";
|
|
||||||
import { GraphvizDotViewer } from "../dot-graphviz-visualizer/GraphvizDotVisualizer";
|
|
||||||
|
|
||||||
@observer
|
|
||||||
export class GraphvizGraphViewer extends React.Component<{
|
|
||||||
nodes: { id: string; label?: string }[];
|
|
||||||
edges: { from: string; to: string; label?: string }[];
|
|
||||||
}> {
|
|
||||||
render() {
|
|
||||||
const { nodes, edges } = this.props;
|
|
||||||
const dotContent = `
|
|
||||||
digraph MyGraph {
|
|
||||||
${nodes
|
|
||||||
.map(
|
|
||||||
n =>
|
|
||||||
`"${n.id}" [ label = ${JSON.stringify(
|
|
||||||
n.label !== undefined ? n.label : n.id
|
|
||||||
)} ];`
|
|
||||||
)
|
|
||||||
.join("\n ")}
|
|
||||||
${edges
|
|
||||||
.map(
|
|
||||||
e =>
|
|
||||||
`"${e.from}" -> "${
|
|
||||||
e.to
|
|
||||||
}" [ label = ${JSON.stringify(
|
|
||||||
e.label !== undefined ? e.label : ""
|
|
||||||
)} ];`
|
|
||||||
)
|
|
||||||
.join("\n")}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
return <GraphvizDotViewer dotCode={dotContent} />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { Loadable } from "../../../utils/Loadable";
|
||||||
|
import { getSvgFromDotCode } from "../dot-graphviz-visualizer/GraphvizDotVisualizer";
|
||||||
|
|
||||||
|
export function getSvgFromGraphData(
|
||||||
|
nodes: { id: string; label?: string }[],
|
||||||
|
edges: {
|
||||||
|
from: string;
|
||||||
|
to: string;
|
||||||
|
label?: string;
|
||||||
|
style?: string;
|
||||||
|
}[]
|
||||||
|
): Loadable<string> {
|
||||||
|
const dotContent = `
|
||||||
|
digraph MyGraph {
|
||||||
|
${nodes
|
||||||
|
.map(
|
||||||
|
n =>
|
||||||
|
`"${n.id}" [ label = ${JSON.stringify(
|
||||||
|
n.label !== undefined ? n.label : n.id
|
||||||
|
)} ];`
|
||||||
|
)
|
||||||
|
.join("\n ")}
|
||||||
|
${edges
|
||||||
|
.map(
|
||||||
|
e =>
|
||||||
|
`"${e.from}" -> "${e.to}" [ label = ${JSON.stringify(
|
||||||
|
e.label !== undefined ? e.label : ""
|
||||||
|
)} style = ${JSON.stringify(e.style || "")} ];`
|
||||||
|
)
|
||||||
|
.join("\n")}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
return getSvgFromDotCode(dotContent);
|
||||||
|
}
|
||||||
@@ -1,20 +1,25 @@
|
|||||||
import React from "react";
|
import * as React from "react";
|
||||||
import { GraphvizGraphViewer } from "./GraphvizGraphVisualizer";
|
|
||||||
import { sGraph } from "../sGraph";
|
import { sGraph } from "../sGraph";
|
||||||
import {
|
import {
|
||||||
createVisualizer,
|
createVisualizer,
|
||||||
ReactVisualization,
|
|
||||||
globalVisualizationFactory,
|
globalVisualizationFactory,
|
||||||
|
createReactVisualization,
|
||||||
} from "@hediet/visualization-core";
|
} from "@hediet/visualization-core";
|
||||||
|
import { GraphvizDotViewer } from "../dot-graphviz-visualizer/GraphvizDotVisualizer";
|
||||||
|
import { getSvgFromGraphData } from "./getGraphVizData";
|
||||||
|
|
||||||
export const graphvizGraphVisualizer = createVisualizer({
|
export const graphvizGraphVisualizer = createVisualizer({
|
||||||
id: "graphviz-graph",
|
id: "graphviz-graph",
|
||||||
name: "Graphviz",
|
name: "Graphviz",
|
||||||
serializer: sGraph,
|
serializer: sGraph,
|
||||||
getVisualization: (data, self) =>
|
getVisualization: (data, self) => {
|
||||||
new ReactVisualization(self, { priority: 1000 }, () => (
|
const svgSource = getSvgFromGraphData(data.nodes, data.edges);
|
||||||
<GraphvizGraphViewer edges={data.edges} nodes={data.nodes} />
|
return createReactVisualization(
|
||||||
)),
|
self,
|
||||||
|
{ priority: 1000, preload: () => svgSource.load() },
|
||||||
|
() => <GraphvizDotViewer svgSource={svgSource} />
|
||||||
|
);
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
globalVisualizationFactory.addVisualizer(graphvizGraphVisualizer);
|
globalVisualizationFactory.addVisualizer(graphvizGraphVisualizer);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Network, Options, DataSet } from "vis-network/standalone";
|
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { NodeGraphData, EdgeGraphData } from "../sGraph";
|
import { DataSet, Network, Options } from "vis-network/standalone";
|
||||||
|
import { EdgeGraphData, NodeGraphData } from "../sGraph";
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class VisJsGraphViewer extends React.Component<{
|
export class VisJsGraphViewer extends React.Component<{
|
||||||
@@ -12,7 +12,7 @@ export class VisJsGraphViewer extends React.Component<{
|
|||||||
private readonly nodes = new DataSet<{
|
private readonly nodes = new DataSet<{
|
||||||
id: string;
|
id: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
color?: string;
|
color?: { border: string; background: string } | string;
|
||||||
shape?: string;
|
shape?: string;
|
||||||
}>();
|
}>();
|
||||||
private readonly edges = new DataSet<{
|
private readonly edges = new DataSet<{
|
||||||
@@ -21,12 +21,18 @@ export class VisJsGraphViewer extends React.Component<{
|
|||||||
to: string;
|
to: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
color?: string;
|
color?: string;
|
||||||
dashes?: boolean;
|
dashes?: boolean | number[];
|
||||||
shape?: boolean;
|
shape?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div style={{ height: "100%" }} ref={this.divRef} />;
|
return (
|
||||||
|
<div
|
||||||
|
className="component-VisJsGraphViewer"
|
||||||
|
style={{ height: "100%" }}
|
||||||
|
ref={this.divRef}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronizeData() {
|
synchronizeData() {
|
||||||
@@ -36,11 +42,11 @@ export class VisJsGraphViewer extends React.Component<{
|
|||||||
this.nodes.update({
|
this.nodes.update({
|
||||||
id: n.id,
|
id: n.id,
|
||||||
label: n.label !== undefined ? n.label : n.id,
|
label: n.label !== undefined ? n.label : n.id,
|
||||||
color: n.color,
|
color: { background: n.color, border: n.borderColor },
|
||||||
shape: n.shape,
|
shape: n.shape,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.nodes.forEach(item => {
|
this.nodes.forEach((item) => {
|
||||||
if (!newNodes.has(item.id)) {
|
if (!newNodes.has(item.id)) {
|
||||||
this.nodes.remove(item);
|
this.nodes.remove(item);
|
||||||
}
|
}
|
||||||
@@ -63,10 +69,12 @@ export class VisJsGraphViewer extends React.Component<{
|
|||||||
from: n.from,
|
from: n.from,
|
||||||
to: n.to,
|
to: n.to,
|
||||||
color: n.color,
|
color: n.color,
|
||||||
dashes: n.dashes,
|
dashes: { dashed: true, dotted: [1, 4], solid: false }[
|
||||||
|
n.style || "solid"
|
||||||
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.edges.forEach(item => {
|
this.edges.forEach((item) => {
|
||||||
if (!newEdges.has(item.id)) {
|
if (!newEdges.has(item.id)) {
|
||||||
this.edges.remove(item);
|
this.edges.remove(item);
|
||||||
}
|
}
|
||||||
@@ -91,6 +99,92 @@ export class VisJsGraphViewer extends React.Component<{
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const network = new Network(this.divRef.current!, data, options);
|
this.network = new Network(this.divRef.current!, data, options);
|
||||||
|
this.divRef.current!.setAttribute("tabindex", "0");
|
||||||
|
document.addEventListener("copy", this.onCopy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private network: Network | undefined;
|
||||||
|
private readonly onCopy = (e: ClipboardEvent) => {
|
||||||
|
if (!this.network) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
document.activeElement &&
|
||||||
|
document.activeElement.className === "vis-network"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const n = this.network as any;
|
||||||
|
|
||||||
|
const nodesText: string[] = [];
|
||||||
|
|
||||||
|
let id = 10;
|
||||||
|
const visJsIdToNodeId = new Map<string, number>();
|
||||||
|
|
||||||
|
// export to draw.io
|
||||||
|
for (const node of Object.values(n.body.nodes) as any) {
|
||||||
|
const label = node.shape.options.label;
|
||||||
|
if (label === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let style = "";
|
||||||
|
if (node.shape.constructor.name === "Ellipse") {
|
||||||
|
style += "ellipse;";
|
||||||
|
} else {
|
||||||
|
style += "rounded=1;";
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeId = id++;
|
||||||
|
visJsIdToNodeId.set(node.id, nodeId);
|
||||||
|
|
||||||
|
nodesText.push(
|
||||||
|
`
|
||||||
|
<mxCell id="${nodeId}" value="${label}" style="${style}" vertex="1" parent="1">
|
||||||
|
<mxGeometry x="${node.x}" y="${node.y}" width="${node.shape.width}" height="${node.shape.height}" as="geometry"/>
|
||||||
|
</mxCell>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const edge of Object.values(n.body.edges) as any) {
|
||||||
|
const label = edge.options.label || "";
|
||||||
|
const edgeId = id++;
|
||||||
|
|
||||||
|
nodesText.push(
|
||||||
|
`
|
||||||
|
<mxCell id="${edgeId}" value="${label}" edge="1" source="${visJsIdToNodeId.get(
|
||||||
|
edge.from.id
|
||||||
|
)}" target="${visJsIdToNodeId.get(edge.to.id)}" parent="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = `
|
||||||
|
<mxGraphModel>
|
||||||
|
<root>
|
||||||
|
<mxCell id="0"/>
|
||||||
|
<mxCell id="1" parent="0"/>
|
||||||
|
|
||||||
|
${nodesText.join("\n")}
|
||||||
|
</root>
|
||||||
|
</mxGraphModel>`;
|
||||||
|
|
||||||
|
/*
|
||||||
|
<mxCell id="2" value="xxx" edge="1" source="3" target="4" parent="1">
|
||||||
|
<mxGeometry relative="1" as="geometry"/>
|
||||||
|
</mxCell>
|
||||||
|
*/
|
||||||
|
|
||||||
|
e.clipboardData!.setData("text/plain", encodeURIComponent(data));
|
||||||
|
e.preventDefault();
|
||||||
|
};
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
document.removeEventListener("copy", this.onCopy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { makeLazyLoadable } from "../../../utils/LazyLoadable";
|
import { makeLazyLoadable } from "../../../utils/LazyLoadable";
|
||||||
import {
|
import {
|
||||||
|
createReactVisualization,
|
||||||
createVisualizer,
|
createVisualizer,
|
||||||
ReactVisualization,
|
|
||||||
globalVisualizationFactory,
|
globalVisualizationFactory,
|
||||||
} from "@hediet/visualization-core";
|
} from "@hediet/visualization-core";
|
||||||
import { sGraph } from "../sGraph";
|
import { sGraph } from "../sGraph";
|
||||||
@@ -16,12 +16,19 @@ export const visJsGraphVisualizer = createVisualizer({
|
|||||||
name: "vis.js",
|
name: "vis.js",
|
||||||
serializer: sGraph,
|
serializer: sGraph,
|
||||||
getVisualization: (data, self) =>
|
getVisualization: (data, self) =>
|
||||||
new ReactVisualization(self, { priority: 1500 }, () => (
|
createReactVisualization(
|
||||||
<VisJsGraphViewerLazyLoadable
|
self,
|
||||||
edges={data.edges}
|
{
|
||||||
nodes={data.nodes}
|
priority: 1500,
|
||||||
/>
|
preload: VisJsGraphViewerLazyLoadable.preload,
|
||||||
)),
|
},
|
||||||
|
() => (
|
||||||
|
<VisJsGraphViewerLazyLoadable
|
||||||
|
edges={data.edges}
|
||||||
|
nodes={data.nodes}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
globalVisualizationFactory.addVisualizer(visJsGraphVisualizer);
|
globalVisualizationFactory.addVisualizer(visJsGraphVisualizer);
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
.themeable {
|
||||||
|
--visualizer-visjs-background: var(--visualizer-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-VisJsGraphViewer {
|
||||||
|
background-color: var(--visualizer-visjs-background);
|
||||||
|
}
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
sOpenObject,
|
|
||||||
sLiteral,
|
|
||||||
sArrayOf,
|
sArrayOf,
|
||||||
sString,
|
sLiteral,
|
||||||
|
sOpenObject,
|
||||||
sOptionalProp,
|
sOptionalProp,
|
||||||
|
sString,
|
||||||
sUnion,
|
sUnion,
|
||||||
sBoolean,
|
|
||||||
} from "@hediet/semantic-json";
|
} from "@hediet/semantic-json";
|
||||||
import { visualizationNs } from "../../consts";
|
import { visualizationNs } from "../../consts";
|
||||||
|
|
||||||
@@ -16,6 +15,7 @@ export const sGraphNode = sOpenObject({
|
|||||||
id: sString(),
|
id: sString(),
|
||||||
label: sOptionalProp(sString(), {}),
|
label: sOptionalProp(sString(), {}),
|
||||||
color: sOptionalProp(sString(), {}),
|
color: sOptionalProp(sString(), {}),
|
||||||
|
borderColor: sOptionalProp(sString(), {}),
|
||||||
shape: sOptionalProp(sUnion([sLiteral("ellipse"), sLiteral("box")]), {}),
|
shape: sOptionalProp(sUnion([sLiteral("ellipse"), sLiteral("box")]), {}),
|
||||||
}).defineAs(visualizationNs("GraphNode"));
|
}).defineAs(visualizationNs("GraphNode"));
|
||||||
|
|
||||||
@@ -25,11 +25,14 @@ export const sGraphEdge = sOpenObject({
|
|||||||
label: sOptionalProp(sString(), {}),
|
label: sOptionalProp(sString(), {}),
|
||||||
id: sOptionalProp(sString(), {}),
|
id: sOptionalProp(sString(), {}),
|
||||||
color: sOptionalProp(sString(), {}),
|
color: sOptionalProp(sString(), {}),
|
||||||
dashes: sOptionalProp(sBoolean(), {}),
|
style: sOptionalProp(
|
||||||
|
sUnion([sLiteral("solid"), sLiteral("dashed"), sLiteral("dotted")]),
|
||||||
|
{}
|
||||||
|
),
|
||||||
}).defineAs(visualizationNs("GraphEdge"));
|
}).defineAs(visualizationNs("GraphEdge"));
|
||||||
|
|
||||||
export const sGraph = sOpenObject({
|
export const sGraph = sOpenObject({
|
||||||
kind: sOpenObject({ graph: sLiteral(true) }),
|
kind: sOpenObject({ graph: sLiteral(true) }),
|
||||||
nodes: sArrayOf(sGraphNode),
|
nodes: sArrayOf(sGraphNode),
|
||||||
edges: sArrayOf(sGraphEdge),
|
edges: sArrayOf(sGraphEdge),
|
||||||
}).defineAs(visualizationNs("Graph"));
|
}).defineAs(visualizationNs("GraphVisualizationData"));
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { computed, action, observable } from "mobx";
|
import { computed, action, observable } from "mobx";
|
||||||
import { Icon } from "@blueprintjs/core";
|
|
||||||
import { sGrid } from ".";
|
import { sGrid } from ".";
|
||||||
|
|
||||||
export class DecoratedGridComponent extends React.Component<{
|
export class DecoratedGridComponent extends React.Component<{
|
||||||
@@ -72,21 +71,14 @@ export class DecoratedGridComponent extends React.Component<{
|
|||||||
r.columns.push({
|
r.columns.push({
|
||||||
id: m.id,
|
id: m.id,
|
||||||
kind: "empty",
|
kind: "empty",
|
||||||
content: (
|
content: <Cell>{m.label || m.id}</Cell>,
|
||||||
<Cell>
|
|
||||||
<Icon
|
|
||||||
icon="arrow-up"
|
|
||||||
color="var(--vscode-editor-foreground)"
|
|
||||||
/>
|
|
||||||
{m.label || m.id}
|
|
||||||
</Cell>
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
className="component-GridVisualizer"
|
||||||
style={{
|
style={{
|
||||||
height: "100%",
|
height: "100%",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
|
|||||||
@@ -1,28 +1,30 @@
|
|||||||
import React from "react";
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
sOpenObject,
|
sOpenObject,
|
||||||
sLiteral,
|
sLiteral,
|
||||||
sString,
|
sString,
|
||||||
sArrayOf,
|
sArrayOf,
|
||||||
sObject,
|
|
||||||
sOptionalProp,
|
sOptionalProp,
|
||||||
sNumber,
|
sNumber,
|
||||||
} from "@hediet/semantic-json";
|
} from "@hediet/semantic-json";
|
||||||
import { DecoratedGridComponent } from "./GridVisualizer";
|
import { DecoratedGridComponent } from "./GridVisualizer";
|
||||||
import {
|
import {
|
||||||
createVisualizer,
|
createVisualizer,
|
||||||
ReactVisualization,
|
|
||||||
globalVisualizationFactory,
|
globalVisualizationFactory,
|
||||||
|
createReactVisualization,
|
||||||
} from "@hediet/visualization-core";
|
} from "@hediet/visualization-core";
|
||||||
|
import { visualizationNs } from "../../consts";
|
||||||
|
|
||||||
export const sGrid = sOpenObject({
|
export const sGrid = sOpenObject({
|
||||||
kind: sOpenObject({
|
kind: sOpenObject({
|
||||||
array: sLiteral(true),
|
grid: sLiteral(true),
|
||||||
}),
|
}),
|
||||||
columnLabels: sArrayOf(
|
columnLabels: sOptionalProp(
|
||||||
sOpenObject({
|
sArrayOf(
|
||||||
label: sOptionalProp(sString(), {}),
|
sOpenObject({
|
||||||
})
|
label: sOptionalProp(sString(), {}),
|
||||||
|
})
|
||||||
|
)
|
||||||
),
|
),
|
||||||
rows: sArrayOf(
|
rows: sArrayOf(
|
||||||
sOpenObject({
|
sOpenObject({
|
||||||
@@ -55,14 +57,14 @@ export const sGrid = sOpenObject({
|
|||||||
),
|
),
|
||||||
{}
|
{}
|
||||||
),
|
),
|
||||||
});
|
}).defineAs(visualizationNs("GridVisualizationData"));
|
||||||
|
|
||||||
export const gridVisualizer = createVisualizer({
|
export const gridVisualizer = createVisualizer({
|
||||||
id: "grid",
|
id: "grid",
|
||||||
name: "Grid",
|
name: "Grid",
|
||||||
serializer: sGrid,
|
serializer: sGrid,
|
||||||
getVisualization: (data, self) =>
|
getVisualization: (data, self) =>
|
||||||
new ReactVisualization(self, { priority: 1000 }, () => (
|
createReactVisualization(self, { priority: 1000 }, () => (
|
||||||
<DecoratedGridComponent data={data} />
|
<DecoratedGridComponent data={data} />
|
||||||
)),
|
)),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,17 @@
|
|||||||
|
.themeable {
|
||||||
|
--visualizer-grid-background: var(--visualizer-background);
|
||||||
|
}
|
||||||
|
.theme-dark {
|
||||||
|
--visualizer-grid-empty-foreground: #f0f0f0;
|
||||||
|
}
|
||||||
|
.theme-light {
|
||||||
|
--visualizer-grid-empty-foreground: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-GridVisualizer {
|
||||||
|
background-color: var(--visualizer-grid-background);
|
||||||
|
}
|
||||||
|
|
||||||
.component-Grid {
|
.component-Grid {
|
||||||
.part-data {
|
.part-data {
|
||||||
border: 1px solid lightgray;
|
border: 1px solid lightgray;
|
||||||
@@ -9,6 +23,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.part-empty {
|
.part-empty {
|
||||||
color: var(--vscode-editor-foreground);
|
color: var(--visualizer-grid-empty-foreground);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { sOpenObject, sLiteral, sString } from "@hediet/semantic-json";
|
import { sOpenObject, sLiteral, sString, sProp } from "@hediet/semantic-json";
|
||||||
import React from "react";
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
createVisualizer,
|
createVisualizer,
|
||||||
ReactVisualization,
|
|
||||||
globalVisualizationFactory,
|
globalVisualizationFactory,
|
||||||
|
createReactVisualization,
|
||||||
} from "@hediet/visualization-core";
|
} from "@hediet/visualization-core";
|
||||||
import { SvgViewer } from "../svg-visualizer/SvgViewer";
|
import { SvgViewer } from "../svg-visualizer/SvgViewer";
|
||||||
import { Observer, observer, disposeOnUnmount } from "mobx-react";
|
import { Observer, observer, disposeOnUnmount } from "mobx-react";
|
||||||
import { observable, autorun } from "mobx";
|
import { observable, autorun } from "mobx";
|
||||||
|
import { visualizationNs } from "../../consts";
|
||||||
|
|
||||||
export const imageVisualizer = createVisualizer({
|
export const imageVisualizer = createVisualizer({
|
||||||
id: "image",
|
id: "image",
|
||||||
@@ -16,10 +17,12 @@ export const imageVisualizer = createVisualizer({
|
|||||||
kind: sOpenObject({
|
kind: sOpenObject({
|
||||||
imagePng: sLiteral(true),
|
imagePng: sLiteral(true),
|
||||||
}),
|
}),
|
||||||
base64Data: sString(),
|
base64Data: sProp(sString(), {
|
||||||
}),
|
description: "The base 64 encoded PNG representation of the image",
|
||||||
|
}),
|
||||||
|
}).defineAs(visualizationNs("ImageVisualizationData")),
|
||||||
getVisualization: (data, self) =>
|
getVisualization: (data, self) =>
|
||||||
new ReactVisualization(self, { priority: 1000 }, () => {
|
createReactVisualization(self, { priority: 1000 }, () => {
|
||||||
return <ImageViewer base64Data={data.base64Data} />;
|
return <ImageViewer base64Data={data.base64Data} />;
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ export * from "./graph";
|
|||||||
export * from "./grid-visualizer";
|
export * from "./grid-visualizer";
|
||||||
export * from "./image-visualizer";
|
export * from "./image-visualizer";
|
||||||
export * from "./monaco-text-visualizer";
|
export * from "./monaco-text-visualizer";
|
||||||
|
export * from "./monaco-text-diff-visualizer";
|
||||||
export * from "./perspective-table-visualizer";
|
export * from "./perspective-table-visualizer";
|
||||||
export * from "./plotly-visualizer";
|
export * from "./plotly-visualizer";
|
||||||
export * from "./simple-text-visualizer";
|
export * from "./simple-text-visualizer";
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { observer, disposeOnUnmount } from "mobx-react";
|
||||||
|
import { observable, autorun } from "mobx";
|
||||||
|
import * as monacoTypes from "monaco-editor";
|
||||||
|
import { Theme } from "@hediet/visualization-core";
|
||||||
|
import { getLoadedMonaco } from "@hediet/monaco-editor-react";
|
||||||
|
|
||||||
|
export function getLanguageId(fileName: string): string {
|
||||||
|
const l = getLoadedMonaco().languages.getLanguages();
|
||||||
|
const result = l.find((l) => {
|
||||||
|
if (l.filenamePatterns) {
|
||||||
|
for (const p of l.filenamePatterns) {
|
||||||
|
if (new RegExp(p).test(fileName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (l.extensions) {
|
||||||
|
for (const p of l.extensions) {
|
||||||
|
if (fileName.endsWith(p)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
return result.id;
|
||||||
|
}
|
||||||
|
return "text";
|
||||||
|
}
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export class MonacoDiffEditor extends React.Component<{
|
||||||
|
originalText: string;
|
||||||
|
modifiedText: string;
|
||||||
|
fileName?: string;
|
||||||
|
theme: Theme;
|
||||||
|
}> {
|
||||||
|
@observable private editor:
|
||||||
|
| monacoTypes.editor.IStandaloneDiffEditor
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.editor) {
|
||||||
|
this.editor.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get languageId(): string {
|
||||||
|
if (!this.props.fileName) {
|
||||||
|
return "text";
|
||||||
|
}
|
||||||
|
return getLanguageId(this.props.fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private originalModel: monacoTypes.editor.ITextModel | undefined =
|
||||||
|
undefined;
|
||||||
|
private modifiedModel: monacoTypes.editor.ITextModel | undefined =
|
||||||
|
undefined;
|
||||||
|
|
||||||
|
@disposeOnUnmount
|
||||||
|
private _updateText = autorun(() => {
|
||||||
|
if (this.editor) {
|
||||||
|
const originalModel = getLoadedMonaco().editor.createModel(
|
||||||
|
this.props.originalText,
|
||||||
|
this.languageId,
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
const modifiedModel = getLoadedMonaco().editor.createModel(
|
||||||
|
this.props.modifiedText,
|
||||||
|
this.languageId,
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
this.editor.setModel({
|
||||||
|
original: originalModel,
|
||||||
|
modified: modifiedModel,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.originalModel) {
|
||||||
|
this.originalModel.dispose();
|
||||||
|
}
|
||||||
|
this.originalModel = originalModel;
|
||||||
|
|
||||||
|
if (this.modifiedModel) {
|
||||||
|
this.modifiedModel.dispose();
|
||||||
|
}
|
||||||
|
this.modifiedModel = modifiedModel;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
private readonly setEditorDiv = (editorDiv: HTMLDivElement) => {
|
||||||
|
if (!editorDiv) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.editor = getLoadedMonaco().editor.createDiffEditor(editorDiv, {
|
||||||
|
automaticLayout: true,
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
minimap: { enabled: false },
|
||||||
|
fixedOverflowWidgets: true,
|
||||||
|
readOnly: true,
|
||||||
|
theme: this.props.theme.kind === "dark" ? "vs-dark" : "vs",
|
||||||
|
renderWhitespace: "all",
|
||||||
|
ignoreTrimWhitespace: false,
|
||||||
|
});
|
||||||
|
if (this.originalModel && this.modifiedModel) {
|
||||||
|
this.editor.setModel({
|
||||||
|
original: this.originalModel!,
|
||||||
|
modified: this.modifiedModel!,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="component-monaco-editor-diff-visualizer">
|
||||||
|
<div className="part-editor" ref={this.setEditorDiv} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import {
|
||||||
|
sOpenObject,
|
||||||
|
sLiteral,
|
||||||
|
sString,
|
||||||
|
sOptionalProp,
|
||||||
|
sProp,
|
||||||
|
} from "@hediet/semantic-json";
|
||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
createReactVisualization,
|
||||||
|
createVisualizer,
|
||||||
|
globalVisualizationFactory,
|
||||||
|
} from "@hediet/visualization-core";
|
||||||
|
import { visualizationNs } from "../../consts";
|
||||||
|
import { makeLazyLoadable } from "../../utils/LazyLoadable";
|
||||||
|
|
||||||
|
const MonacoDiffEditorLazyLoadable = makeLazyLoadable(
|
||||||
|
async () => (await import("./MonacoEditor")).MonacoDiffEditor
|
||||||
|
);
|
||||||
|
|
||||||
|
export const monacoTextDiffVisualizer = createVisualizer({
|
||||||
|
id: "monaco-text-diff",
|
||||||
|
name: "Text Diff",
|
||||||
|
serializer: sOpenObject({
|
||||||
|
kind: sOpenObject({
|
||||||
|
text: sLiteral(true),
|
||||||
|
}),
|
||||||
|
text: sProp(sString(), { description: "The text to show" }),
|
||||||
|
otherText: sProp(sString(), {
|
||||||
|
description: "The text to compare against",
|
||||||
|
}),
|
||||||
|
fileName: sOptionalProp(sString(), {
|
||||||
|
description:
|
||||||
|
"An optional filename that might be used for chosing a syntax highlighter",
|
||||||
|
}),
|
||||||
|
}).defineAs(visualizationNs("MonacoTextDiffVisualizationData")),
|
||||||
|
getVisualization: (data, self) =>
|
||||||
|
createReactVisualization(
|
||||||
|
self,
|
||||||
|
{ priority: 900, preload: MonacoDiffEditorLazyLoadable.preload },
|
||||||
|
({ theme }) => (
|
||||||
|
<MonacoDiffEditorLazyLoadable
|
||||||
|
originalText={data.otherText}
|
||||||
|
modifiedText={data.text}
|
||||||
|
fileName={data.fileName}
|
||||||
|
theme={theme}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
globalVisualizationFactory.addVisualizer(monacoTextDiffVisualizer);
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
.themeable {
|
||||||
|
--visualizer-monaco-editor-diff-visualizer-background: var(
|
||||||
|
--visualizer-background
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.component-monaco-editor-diff-visualizer {
|
||||||
|
background-color: var(
|
||||||
|
--visualizer-monaco-editor-diff-visualizer-background
|
||||||
|
);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
padding: 2px;
|
||||||
|
border: 0.5px solid gray;
|
||||||
|
|
||||||
|
.part-editor {
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.current-line {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
.selectionHighlight {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,43 +1,26 @@
|
|||||||
import React from "react";
|
import * as React from "react";
|
||||||
import { observer, disposeOnUnmount } from "mobx-react";
|
import { observer, disposeOnUnmount } from "mobx-react";
|
||||||
import { observable, autorun } from "mobx";
|
import { observable, autorun } from "mobx";
|
||||||
import * as monacoTypes from "monaco-editor";
|
import * as monacoTypes from "monaco-editor";
|
||||||
import { Theme } from "@hediet/visualization-core";
|
import { Theme } from "@hediet/visualization-core";
|
||||||
import { getLoadedMonaco } from "@hediet/monaco-editor-react";
|
import { getLoadedMonaco } from "@hediet/monaco-editor-react";
|
||||||
|
import { getLanguageId } from "./getLanguageId";
|
||||||
|
|
||||||
export function getLanguageId(fileName: string): string {
|
export interface Decoration {
|
||||||
const l = getLoadedMonaco().languages.getLanguages();
|
label?: string;
|
||||||
const result = l.find(l => {
|
range: monacoTypes.IRange;
|
||||||
if (l.filenamePatterns) {
|
|
||||||
for (const p of l.filenamePatterns) {
|
|
||||||
if (new RegExp(p).test(fileName)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (l.extensions) {
|
|
||||||
for (const p of l.extensions) {
|
|
||||||
if (fileName.endsWith(p)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result) {
|
|
||||||
return result.id;
|
|
||||||
}
|
|
||||||
return "text";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class MonacoEditor extends React.Component<{
|
export class MonacoEditor extends React.Component<{
|
||||||
text: string;
|
text: string;
|
||||||
languageId: string;
|
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
|
fileName?: string;
|
||||||
|
decorations?: Decoration[];
|
||||||
|
jsonSchemas?: { schema: unknown }[];
|
||||||
}> {
|
}> {
|
||||||
|
public static nextId = 0;
|
||||||
|
|
||||||
@observable private editor:
|
@observable private editor:
|
||||||
| monacoTypes.editor.IStandaloneCodeEditor
|
| monacoTypes.editor.IStandaloneCodeEditor
|
||||||
| undefined;
|
| undefined;
|
||||||
@@ -48,20 +31,119 @@ export class MonacoEditor extends React.Component<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get languageId(): string {
|
||||||
|
if (!this.props.fileName) {
|
||||||
|
return "text";
|
||||||
|
}
|
||||||
|
return getLanguageId(this.props.fileName);
|
||||||
|
}
|
||||||
|
|
||||||
private model: monacoTypes.editor.ITextModel | undefined = undefined;
|
private model: monacoTypes.editor.ITextModel | undefined = undefined;
|
||||||
|
|
||||||
|
private prevDecorations: string[] = [];
|
||||||
|
|
||||||
@disposeOnUnmount
|
@disposeOnUnmount
|
||||||
private _updateText = autorun(() => {
|
private _updateText = autorun(() => {
|
||||||
if (this.editor) {
|
if (this.editor) {
|
||||||
|
const decorations =
|
||||||
|
new Array<monacoTypes.editor.IModelDeltaDecoration>();
|
||||||
|
const propDecorations = this.props.decorations || [];
|
||||||
|
const lines = this.props.text.split("\n");
|
||||||
|
const mappedLines = lines.map((l, idx) => {
|
||||||
|
if (idx === lines.length - 1) {
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!propDecorations.some(
|
||||||
|
(d) =>
|
||||||
|
((d.range.endLineNumber === idx + 2 &&
|
||||||
|
d.range.endColumn === 1) ||
|
||||||
|
(d.range.startLineNumber === idx + 1 &&
|
||||||
|
d.range.startColumn >= l.length + 1)) &&
|
||||||
|
(d.range.startColumn !== d.range.endColumn ||
|
||||||
|
d.range.startLineNumber !==
|
||||||
|
d.range.endLineNumber)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return l;
|
||||||
|
}
|
||||||
|
|
||||||
|
decorations.push({
|
||||||
|
range: {
|
||||||
|
startLineNumber: idx + 1,
|
||||||
|
endLineNumber: idx + 1,
|
||||||
|
startColumn: l.length + 1,
|
||||||
|
endColumn: l.length + 2,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
inlineClassName: "decoration-whitespace",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return l + "␊"; // ␍␊
|
||||||
|
});
|
||||||
|
const text = mappedLines.join("\n");
|
||||||
|
|
||||||
|
const modelId = MonacoEditor.nextId++;
|
||||||
|
|
||||||
const model = getLoadedMonaco().editor.createModel(
|
const model = getLoadedMonaco().editor.createModel(
|
||||||
this.props.text,
|
text,
|
||||||
this.props.languageId,
|
this.languageId,
|
||||||
undefined
|
getLoadedMonaco().Uri.parse(
|
||||||
|
`inmemory://inmemory/model${modelId}`
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
this.editor.setModel(model);
|
this.editor.setModel(model);
|
||||||
|
|
||||||
/* this.editor!.updateOptions({ readOnly: false });
|
if (this.props.jsonSchemas) {
|
||||||
|
const existingSchemas =
|
||||||
|
getLoadedMonaco().languages.json.jsonDefaults
|
||||||
|
.diagnosticsOptions.schemas || [];
|
||||||
|
|
||||||
|
getLoadedMonaco().languages.json.jsonDefaults.setDiagnosticsOptions(
|
||||||
|
{
|
||||||
|
validate: true,
|
||||||
|
schemas: [
|
||||||
|
...existingSchemas.filter(
|
||||||
|
(s) =>
|
||||||
|
!s.uri.startsWith(
|
||||||
|
"https://example.org/schemas/temp/"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
...this.props.jsonSchemas.map((s, idx) => ({
|
||||||
|
uri: "https://example.org/schemas/temp/" + idx,
|
||||||
|
fileMatch: [`model${modelId}`],
|
||||||
|
schema: s.schema,
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.prevDecorations = this.editor.deltaDecorations(
|
||||||
|
[],
|
||||||
|
[
|
||||||
|
...decorations,
|
||||||
|
...propDecorations.map((d) => {
|
||||||
|
const r = getLoadedMonaco().Range.lift(d.range);
|
||||||
|
return {
|
||||||
|
range: r,
|
||||||
|
options: {
|
||||||
|
hoverMessage: d.label
|
||||||
|
? { value: d.label, isTrusted: false }
|
||||||
|
: undefined,
|
||||||
|
className: r.isEmpty()
|
||||||
|
? "decoration-empty"
|
||||||
|
: "decoration",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
this.editor!.updateOptions({ readOnly: false });
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const a = this.editor!.getAction(
|
const a = this.editor!.getAction(
|
||||||
@@ -71,6 +153,7 @@ export class MonacoEditor extends React.Component<{
|
|||||||
this.editor!.updateOptions({ readOnly: true });
|
this.editor!.updateOptions({ readOnly: true });
|
||||||
});
|
});
|
||||||
}, 200);*/
|
}, 200);*/
|
||||||
|
|
||||||
if (this.model) {
|
if (this.model) {
|
||||||
this.model.dispose();
|
this.model.dispose();
|
||||||
}
|
}
|
||||||
@@ -90,6 +173,8 @@ export class MonacoEditor extends React.Component<{
|
|||||||
fixedOverflowWidgets: true,
|
fixedOverflowWidgets: true,
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
theme: this.props.theme.kind === "dark" ? "vs-dark" : "vs",
|
theme: this.props.theme.kind === "dark" ? "vs-dark" : "vs",
|
||||||
|
renderWhitespace: "all",
|
||||||
|
renderValidationDecorations: "on",
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { getLoadedMonaco } from "@hediet/monaco-editor-react";
|
||||||
|
|
||||||
|
export function getLanguageId(fileName: string): string {
|
||||||
|
const l = getLoadedMonaco().languages.getLanguages();
|
||||||
|
const result = l.find(l => {
|
||||||
|
if (l.filenamePatterns) {
|
||||||
|
for (const p of l.filenamePatterns) {
|
||||||
|
if (new RegExp(p).test(fileName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (l.extensions) {
|
||||||
|
for (const p of l.extensions) {
|
||||||
|
if (fileName.endsWith(p)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
return result.id;
|
||||||
|
}
|
||||||
|
return "text";
|
||||||
|
}
|
||||||
@@ -3,14 +3,44 @@ import {
|
|||||||
sLiteral,
|
sLiteral,
|
||||||
sString,
|
sString,
|
||||||
sOptionalProp,
|
sOptionalProp,
|
||||||
|
sProp,
|
||||||
|
sArrayOf,
|
||||||
|
sNumber,
|
||||||
|
sUnion,
|
||||||
|
sAny,
|
||||||
} from "@hediet/semantic-json";
|
} from "@hediet/semantic-json";
|
||||||
import React from "react";
|
import * as React from "react";
|
||||||
import { getLanguageId, MonacoEditor } from "./MonacoEditor";
|
|
||||||
import {
|
import {
|
||||||
createVisualizer,
|
createVisualizer,
|
||||||
ReactVisualization,
|
|
||||||
globalVisualizationFactory,
|
globalVisualizationFactory,
|
||||||
|
createReactVisualization,
|
||||||
} from "@hediet/visualization-core";
|
} from "@hediet/visualization-core";
|
||||||
|
import { visualizationNs } from "../../consts";
|
||||||
|
import { makeLazyLoadable } from "../../utils/LazyLoadable";
|
||||||
|
import { Decoration } from "./MonacoEditor";
|
||||||
|
|
||||||
|
export const MonacoEditorLazyLoadable = makeLazyLoadable(
|
||||||
|
async () => (await import("./MonacoEditor")).MonacoEditor
|
||||||
|
);
|
||||||
|
|
||||||
|
export const LineColumnPosition = sOpenObject({
|
||||||
|
line: sProp(sNumber(), { description: "The 0-based line number" }),
|
||||||
|
column: sProp(sNumber(), { description: "The 0-based column number" }),
|
||||||
|
}).defineAs(visualizationNs("LineColumnPosition"));
|
||||||
|
|
||||||
|
export const LineColumnRange = sOpenObject({
|
||||||
|
start: sProp(LineColumnPosition, { description: "The start position" }),
|
||||||
|
end: sProp(LineColumnPosition, { description: "The end position" }),
|
||||||
|
}).defineAs(visualizationNs("LineColumnRange"));
|
||||||
|
|
||||||
|
export const OffsetPosition = sNumber().defineAs(
|
||||||
|
visualizationNs("OffsetPosition")
|
||||||
|
);
|
||||||
|
|
||||||
|
export const OffsetRange = sOpenObject({
|
||||||
|
start: sProp(OffsetPosition, { description: "The start position" }),
|
||||||
|
end: sProp(OffsetPosition, { description: "The end position" }),
|
||||||
|
}).defineAs(visualizationNs("OffsetRange"));
|
||||||
|
|
||||||
export const monacoTextVisualizer = createVisualizer({
|
export const monacoTextVisualizer = createVisualizer({
|
||||||
id: "monaco-text",
|
id: "monaco-text",
|
||||||
@@ -19,22 +49,62 @@ export const monacoTextVisualizer = createVisualizer({
|
|||||||
kind: sOpenObject({
|
kind: sOpenObject({
|
||||||
text: sLiteral(true),
|
text: sLiteral(true),
|
||||||
}),
|
}),
|
||||||
text: sString(),
|
text: sProp(sString(), { description: "The text to show" }),
|
||||||
|
decorations: sOptionalProp(
|
||||||
|
sArrayOf(
|
||||||
|
sOpenObject({
|
||||||
|
range: sUnion([LineColumnRange]),
|
||||||
|
label: sOptionalProp(sString()),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
jsonSchemas: sOptionalProp(
|
||||||
|
sArrayOf(
|
||||||
|
sOpenObject({
|
||||||
|
schema: sProp(sAny(), {
|
||||||
|
description:
|
||||||
|
"A json schema object that is applied when the fileName indicates a JSON document.",
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
"An array of schemas used to validate JSON documents.",
|
||||||
|
}
|
||||||
|
),
|
||||||
fileName: sOptionalProp(sString(), {
|
fileName: sOptionalProp(sString(), {
|
||||||
description:
|
description:
|
||||||
"An optional filename that might be used for chosing a syntax highlighter",
|
"An optional filename that might be used for chosing a syntax highlighter",
|
||||||
}),
|
}),
|
||||||
}),
|
}).defineAs(visualizationNs("MonacoTextVisualizationData")),
|
||||||
getVisualization: (data, self) =>
|
getVisualization: (data, self) =>
|
||||||
new ReactVisualization(self, { priority: 500 }, theme => {
|
createReactVisualization(
|
||||||
let id = "text";
|
self,
|
||||||
if (data.fileName) {
|
{ priority: 500, preload: MonacoEditorLazyLoadable.preload },
|
||||||
id = getLanguageId(data.fileName);
|
function ({ theme }) {
|
||||||
|
const decorations = data.decorations
|
||||||
|
? data.decorations.map<Decoration>((d) => ({
|
||||||
|
label: d.label,
|
||||||
|
range: {
|
||||||
|
startLineNumber: d.range.start.line + 1,
|
||||||
|
startColumn: d.range.start.column + 1,
|
||||||
|
endLineNumber: d.range.end.line + 1,
|
||||||
|
endColumn: d.range.end.column + 1,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MonacoEditorLazyLoadable
|
||||||
|
text={data.text}
|
||||||
|
fileName={data.fileName}
|
||||||
|
theme={theme}
|
||||||
|
decorations={decorations}
|
||||||
|
jsonSchemas={data.jsonSchemas}
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return (
|
),
|
||||||
<MonacoEditor text={data.text} languageId={id} theme={theme} />
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
globalVisualizationFactory.addVisualizer(monacoTextVisualizer);
|
globalVisualizationFactory.addVisualizer(monacoTextVisualizer);
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
|
.themeable {
|
||||||
|
--visualizer-monaco-editor-background: var(--visualizer-background);
|
||||||
|
}
|
||||||
|
|
||||||
.component-monaco-editor {
|
.component-monaco-editor {
|
||||||
|
background-color: var(--visualizer-monaco-editor-background);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -20,3 +25,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.decoration-whitespace {
|
||||||
|
color: black !important;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.decoration-empty {
|
||||||
|
margin-left: -1px;
|
||||||
|
border-left: solid rgba(255, 0, 0, 0.815) 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.decoration {
|
||||||
|
background-color: rgba(250, 36, 36, 0.459);
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { Table } from "@finos/perspective";
|
||||||
|
import "@finos/perspective-viewer";
|
||||||
|
import "@finos/perspective-viewer-datagrid";
|
||||||
|
import "@finos/perspective-viewer-d3fc";
|
||||||
|
import "@finos/perspective-viewer/themes/all-themes.css";
|
||||||
|
import { HTMLPerspectiveViewerElement } from "@finos/perspective-viewer";
|
||||||
|
import { observer } from "mobx-react";
|
||||||
|
import { computed, observable } from "mobx";
|
||||||
|
|
||||||
|
@observer
|
||||||
|
export class PerspectiveDataViewer extends React.Component<
|
||||||
|
{ data: object[] } & React.HTMLAttributes<HTMLDivElement>
|
||||||
|
> {
|
||||||
|
private readonly nodeRef = React.createRef<HTMLPerspectiveViewerElement>();
|
||||||
|
|
||||||
|
@observable key = 0;
|
||||||
|
|
||||||
|
private oldSchemaStr: string | undefined;
|
||||||
|
|
||||||
|
private async replaceData() {
|
||||||
|
const elem = this.nodeRef.current!;
|
||||||
|
const data = this.props.data;
|
||||||
|
const table = (elem as any).worker.table(data) as Table;
|
||||||
|
|
||||||
|
const newSchema = await table.schema();
|
||||||
|
const newSchemaStr = JSON.stringify(newSchema);
|
||||||
|
const oldSchemaStr = this.oldSchemaStr;
|
||||||
|
this.oldSchemaStr = newSchemaStr;
|
||||||
|
|
||||||
|
if (oldSchemaStr && oldSchemaStr !== newSchemaStr) {
|
||||||
|
// perspective js does not like it if new data arrives that has a different schema.
|
||||||
|
// We check if the schema changed an remount the component if it did.
|
||||||
|
this.key++;
|
||||||
|
} else {
|
||||||
|
await elem.load(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(elem as any)._show_config) {
|
||||||
|
elem.toggleConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
this.replaceData();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.replaceData();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<perspective-viewer
|
||||||
|
key={this.key}
|
||||||
|
class={this.props.className}
|
||||||
|
style={this.props.style}
|
||||||
|
ref={this.nodeRef}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,76 +1,55 @@
|
|||||||
import React from "react";
|
import * as React from "react";
|
||||||
import { sOpenObject, sLiteral, sArrayOf, sAny } from "@hediet/semantic-json";
|
import { sOpenObject, sLiteral, sArrayOf, sProp } from "@hediet/semantic-json";
|
||||||
import { toJS } from "mobx";
|
import { toJS } from "mobx";
|
||||||
import "@finos/perspective-viewer";
|
|
||||||
import "@finos/perspective-viewer-datagrid";
|
|
||||||
import "@finos/perspective-viewer-d3fc";
|
|
||||||
import "@finos/perspective-viewer/themes/all-themes.css";
|
|
||||||
import { HTMLPerspectiveViewerElement } from "@finos/perspective-viewer";
|
|
||||||
import {
|
import {
|
||||||
createVisualizer,
|
createVisualizer,
|
||||||
ReactVisualization,
|
|
||||||
globalVisualizationFactory,
|
globalVisualizationFactory,
|
||||||
|
createReactVisualization,
|
||||||
} from "@hediet/visualization-core";
|
} from "@hediet/visualization-core";
|
||||||
|
import { makeLazyLoadable } from "../../utils/LazyLoadable";
|
||||||
|
import { visualizationNs } from "../../consts";
|
||||||
|
|
||||||
export const sTable = sOpenObject({
|
export const sTable = sOpenObject({
|
||||||
kind: sOpenObject({
|
kind: sOpenObject({
|
||||||
table: sLiteral(true),
|
table: sLiteral(true),
|
||||||
}),
|
}),
|
||||||
rows: sArrayOf(
|
rows: sProp(sArrayOf(sOpenObject({})), {
|
||||||
sAny() // object
|
description:
|
||||||
),
|
"An array of objects. The properties of the objects are used as columns.",
|
||||||
});
|
}),
|
||||||
|
}).defineAs(visualizationNs("TableVisualizationData"));
|
||||||
|
|
||||||
|
const PerspectiveDataViewerLazyLoadable = makeLazyLoadable(
|
||||||
|
async () => (await import("./PerspectiveDataViewer")).PerspectiveDataViewer
|
||||||
|
);
|
||||||
|
|
||||||
export const perspectiveTableVisualizer = createVisualizer({
|
export const perspectiveTableVisualizer = createVisualizer({
|
||||||
id: "perspective-table",
|
id: "perspective-table",
|
||||||
name: "Perspective Table",
|
name: "Perspective Table",
|
||||||
serializer: sTable,
|
serializer: sTable,
|
||||||
getVisualization: (data, self) =>
|
getVisualization: (data, self) =>
|
||||||
new ReactVisualization(self, { priority: 1000 }, theme => (
|
createReactVisualization(
|
||||||
<PerspectiveDataViewer
|
self,
|
||||||
className={
|
{
|
||||||
{
|
priority: 1000,
|
||||||
light: "perspective-viewer-material",
|
preload: PerspectiveDataViewerLazyLoadable.preload,
|
||||||
dark: "perspective-viewer-material-dark",
|
},
|
||||||
}[theme.kind]
|
({ theme }) => (
|
||||||
}
|
<PerspectiveDataViewerLazyLoadable
|
||||||
style={{
|
className={
|
||||||
width: "100%",
|
{
|
||||||
height: "100%",
|
light: "perspective-viewer-material",
|
||||||
}}
|
dark: "perspective-viewer-material-dark",
|
||||||
data={toJS(data.rows)}
|
}[theme.kind]
|
||||||
/>
|
}
|
||||||
)),
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
|
data={toJS(data.rows)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
class PerspectiveDataViewer extends React.Component<
|
|
||||||
{ data: object[] } & React.HTMLAttributes<HTMLDivElement>
|
|
||||||
> {
|
|
||||||
private readonly nodeRef = React.createRef<HTMLPerspectiveViewerElement>();
|
|
||||||
|
|
||||||
componentDidUpdate() {
|
|
||||||
this.nodeRef.current!.load(this.props.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
setTimeout(() => {
|
|
||||||
// TODO: Why timeout?
|
|
||||||
// It does not work without it.
|
|
||||||
|
|
||||||
this.nodeRef.current!.load(this.props.data);
|
|
||||||
this.nodeRef.current!.toggleConfig();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<perspective-viewer
|
|
||||||
class={this.props.className}
|
|
||||||
style={this.props.style}
|
|
||||||
ref={this.nodeRef}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
globalVisualizationFactory.addVisualizer(perspectiveTableVisualizer);
|
globalVisualizationFactory.addVisualizer(perspectiveTableVisualizer);
|
||||||
|
|||||||
@@ -1,32 +1,163 @@
|
|||||||
|
import { Theme } from "@hediet/visualization-core";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import Plot from "react-plotly.js";
|
import Plot from "react-plotly.js";
|
||||||
|
|
||||||
|
// needs to be overwriten since the table layout is not defined in @types/plotly.js
|
||||||
|
interface PlotlyLayoutAnyTemplate extends Plotly.Layout {
|
||||||
|
template: any;
|
||||||
|
}
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
export class PlotlyViewer extends React.Component<{
|
export class PlotlyViewer extends React.Component<{
|
||||||
data: Plotly.Data[];
|
data: Plotly.Data[];
|
||||||
layout?: Partial<Plotly.Layout>;
|
layout?: Partial<Plotly.Layout>;
|
||||||
theme: "light" | "dark";
|
theme: Theme;
|
||||||
|
onReady: () => void;
|
||||||
}> {
|
}> {
|
||||||
render() {
|
render() {
|
||||||
const { theme, data, layout } = this.props;
|
const { theme, data, layout } = this.props;
|
||||||
|
const computedLayout = Object.assign(
|
||||||
|
{},
|
||||||
|
theme.id === "dark" ? getLayout(theme) : {},
|
||||||
|
layout || {}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
data.length === 1 &&
|
||||||
|
data[0].type === "mesh3d" &&
|
||||||
|
(layout || {}).margin === undefined
|
||||||
|
) {
|
||||||
|
// Fixes https://github.com/JetBrains/rider-debug-visualizer-web-view/issues/6
|
||||||
|
Object.assign(computedLayout, {
|
||||||
|
margin: {
|
||||||
|
l: 0,
|
||||||
|
r: 0,
|
||||||
|
b: 0,
|
||||||
|
t: 0,
|
||||||
|
pad: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Plot
|
<Plot
|
||||||
style={{ width: "100%", height: "100%" }}
|
style={{ width: "100%", height: "100%" }}
|
||||||
data={data}
|
data={data}
|
||||||
layout={Object.assign(
|
layout={computedLayout}
|
||||||
{},
|
|
||||||
theme === "dark" ? darkLayout : {},
|
|
||||||
layout || {}
|
|
||||||
)}
|
|
||||||
config={{ responsive: true }}
|
config={{ responsive: true }}
|
||||||
|
onInitialized={this.props.onReady}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getLayout(theme: Theme): Partial<PlotlyLayoutAnyTemplate> {
|
||||||
|
return {
|
||||||
|
colorway: [
|
||||||
|
"#636efa",
|
||||||
|
"#EF553B",
|
||||||
|
"#00cc96",
|
||||||
|
"#ab63fa",
|
||||||
|
"#19d3f3",
|
||||||
|
"#e763fa",
|
||||||
|
"#fecb52",
|
||||||
|
"#ffa15a",
|
||||||
|
"#ff6692",
|
||||||
|
"#b6e880",
|
||||||
|
],
|
||||||
|
autosize: true,
|
||||||
|
font: { color: "#f2f5fa" },
|
||||||
|
title: { x: 0.05 },
|
||||||
|
paper_bgcolor: theme.resolveVarToColor("--visualizer-background"), // "rgb(17,17,17)",
|
||||||
|
plot_bgcolor: theme.resolveVarToColor("--visualizer-background"), // "rgb(17,17,17)",
|
||||||
|
hovermode: "closest",
|
||||||
|
polar: {
|
||||||
|
bgcolor: theme.resolveVarToColor("--visualizer-background"),
|
||||||
|
angularaxis: {
|
||||||
|
gridcolor: "#506784",
|
||||||
|
linecolor: "#506784",
|
||||||
|
ticks: "",
|
||||||
|
},
|
||||||
|
radialaxis: {
|
||||||
|
gridcolor: "#506784",
|
||||||
|
linecolor: "#506784",
|
||||||
|
ticks: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ternary: {
|
||||||
|
bgcolor: theme.resolveVarToColor("--visualizer-background"),
|
||||||
|
aaxis: { gridcolor: "#506784", linecolor: "#506784", ticks: "" },
|
||||||
|
baxis: { gridcolor: "#506784", linecolor: "#506784", ticks: "" },
|
||||||
|
caxis: { gridcolor: "#506784", linecolor: "#506784", ticks: "" },
|
||||||
|
},
|
||||||
|
xaxis: {
|
||||||
|
gridcolor: "#283442",
|
||||||
|
linecolor: "#506784",
|
||||||
|
ticks: "",
|
||||||
|
zerolinecolor: "#283442",
|
||||||
|
zerolinewidth: 2,
|
||||||
|
automargin: true,
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
gridcolor: "#283442",
|
||||||
|
linecolor: "#506784",
|
||||||
|
ticks: "",
|
||||||
|
zerolinecolor: "#283442",
|
||||||
|
zerolinewidth: 2,
|
||||||
|
automargin: true,
|
||||||
|
},
|
||||||
|
scene: {
|
||||||
|
xaxis: {
|
||||||
|
gridcolor: "#506784",
|
||||||
|
linecolor: "#506784",
|
||||||
|
showbackground: false,
|
||||||
|
ticks: "",
|
||||||
|
zerolinecolor: "#C8D4E3",
|
||||||
|
gridwidth: 2,
|
||||||
|
},
|
||||||
|
yaxis: {
|
||||||
|
gridcolor: "#506784",
|
||||||
|
linecolor: "#506784",
|
||||||
|
showbackground: false,
|
||||||
|
ticks: "",
|
||||||
|
zerolinecolor: "#C8D4E3",
|
||||||
|
gridwidth: 2,
|
||||||
|
},
|
||||||
|
zaxis: {
|
||||||
|
gridcolor: "#506784",
|
||||||
|
linecolor: "#506784",
|
||||||
|
showbackground: false,
|
||||||
|
ticks: "",
|
||||||
|
zerolinecolor: "#C8D4E3",
|
||||||
|
gridwidth: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
geo: {
|
||||||
|
bgcolor: theme.resolveVarToColor("--visualizer-plotly-background"),
|
||||||
|
landcolor: theme.resolveVarToColor(
|
||||||
|
"--visualizer-plotly-background"
|
||||||
|
),
|
||||||
|
subunitcolor: "#506784",
|
||||||
|
showland: true,
|
||||||
|
showlakes: true,
|
||||||
|
lakecolor: theme.resolveVarToColor(
|
||||||
|
"--visualizer-plotly-background"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
template: {
|
||||||
|
data: {
|
||||||
|
table: [{
|
||||||
|
cells: { fill: { color: theme.resolveVarToColor("--visualizer-background") } },
|
||||||
|
header: { fill: { color: "rgb(17, 17, 17)" } },
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// extracted from the plotly website
|
// extracted from the plotly website
|
||||||
const darkLayout: Partial<Plotly.Layout> = {
|
const darkLayout: Partial<PlotlyLayoutAnyTemplate> = {
|
||||||
colorway: [
|
colorway: [
|
||||||
"#636efa",
|
"#636efa",
|
||||||
"#EF553B",
|
"#EF553B",
|
||||||
@@ -40,53 +171,6 @@ const darkLayout: Partial<Plotly.Layout> = {
|
|||||||
"#b6e880",
|
"#b6e880",
|
||||||
],
|
],
|
||||||
autosize: true,
|
autosize: true,
|
||||||
//margin: { b: 0, t: 0, pad: 0 },
|
|
||||||
//height: 300,
|
|
||||||
/*colorscale: {
|
|
||||||
sequential: [
|
|
||||||
[0, "#0508b8"],
|
|
||||||
[0.0893854748603352, "#1910d8"],
|
|
||||||
[0.1787709497206704, "#3c19f0"],
|
|
||||||
[0.2681564245810056, "#6b1cfb"],
|
|
||||||
[0.3575418994413408, "#981cfd"],
|
|
||||||
[0.44692737430167595, "#bf1cfd"],
|
|
||||||
[0.5363128491620112, "#dd2bfd"],
|
|
||||||
[0.6256983240223464, "#f246fe"],
|
|
||||||
[0.7150837988826816, "#fc67fd"],
|
|
||||||
[0.8044692737430168, "#fe88fc"],
|
|
||||||
[0.8938547486033519, "#fea5fd"],
|
|
||||||
[0.9832402234636871, "#febefe"],
|
|
||||||
[1, "#fec3fe"],
|
|
||||||
],
|
|
||||||
sequentialminus: [
|
|
||||||
[0, "#0508b8"],
|
|
||||||
[0.0893854748603352, "#1910d8"],
|
|
||||||
[0.1787709497206704, "#3c19f0"],
|
|
||||||
[0.2681564245810056, "#6b1cfb"],
|
|
||||||
[0.3575418994413408, "#981cfd"],
|
|
||||||
[0.44692737430167595, "#bf1cfd"],
|
|
||||||
[0.5363128491620112, "#dd2bfd"],
|
|
||||||
[0.6256983240223464, "#f246fe"],
|
|
||||||
[0.7150837988826816, "#fc67fd"],
|
|
||||||
[0.8044692737430168, "#fe88fc"],
|
|
||||||
[0.8938547486033519, "#fea5fd"],
|
|
||||||
[0.9832402234636871, "#febefe"],
|
|
||||||
[1, "#fec3fe"],
|
|
||||||
],
|
|
||||||
diverging: [
|
|
||||||
[0, "#8e0152"],
|
|
||||||
[0.1, "#c51b7d"],
|
|
||||||
[0.2, "#de77ae"],
|
|
||||||
[0.3, "#f1b6da"],
|
|
||||||
[0.4, "#fde0ef"],
|
|
||||||
[0.5, "#f7f7f7"],
|
|
||||||
[0.6, "#e6f5d0"],
|
|
||||||
[0.7, "#b8e186"],
|
|
||||||
[0.8, "#7fbc41"],
|
|
||||||
[0.9, "#4d9221"],
|
|
||||||
[1, "#276419"],
|
|
||||||
],
|
|
||||||
},*/
|
|
||||||
font: { color: "#f2f5fa" },
|
font: { color: "#f2f5fa" },
|
||||||
title: { x: 0.05 },
|
title: { x: 0.05 },
|
||||||
paper_bgcolor: "rgb(17,17,17)",
|
paper_bgcolor: "rgb(17,17,17)",
|
||||||
@@ -156,17 +240,6 @@ const darkLayout: Partial<Plotly.Layout> = {
|
|||||||
gridwidth: 2,
|
gridwidth: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
/*
|
|
||||||
shapedefaults: {
|
|
||||||
fillcolor: "#f2f5fa",
|
|
||||||
line: { width: 0 },
|
|
||||||
opacity: 0.4,
|
|
||||||
},
|
|
||||||
annotationdefaults: {
|
|
||||||
arrowcolor: "#f2f5fa",
|
|
||||||
arrowhead: 0,
|
|
||||||
arrowwidth: 1,
|
|
||||||
},*/
|
|
||||||
geo: {
|
geo: {
|
||||||
bgcolor: "rgb(17,17,17)",
|
bgcolor: "rgb(17,17,17)",
|
||||||
landcolor: "rgb(17,17,17)",
|
landcolor: "rgb(17,17,17)",
|
||||||
@@ -175,13 +248,4 @@ const darkLayout: Partial<Plotly.Layout> = {
|
|||||||
showlakes: true,
|
showlakes: true,
|
||||||
lakecolor: "rgb(17,17,17)",
|
lakecolor: "rgb(17,17,17)",
|
||||||
},
|
},
|
||||||
/*
|
|
||||||
updatemenudefaults: { bgcolor: "#506784", borderwidth: 0 },
|
|
||||||
sliderdefaults: {
|
|
||||||
bgcolor: "#C8D4E3",
|
|
||||||
borderwidth: 1,
|
|
||||||
bordercolor: "rgb(17,17,17)",
|
|
||||||
tickwidth: 0,
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,23 +1,31 @@
|
|||||||
import * as React from "react";
|
|
||||||
import {
|
import {
|
||||||
sOpenObject,
|
|
||||||
sLiteral,
|
|
||||||
sArrayOf,
|
sArrayOf,
|
||||||
|
sLiteral,
|
||||||
|
sNull,
|
||||||
|
sNumber,
|
||||||
|
sOpenObject,
|
||||||
sOptionalProp,
|
sOptionalProp,
|
||||||
sAny,
|
|
||||||
sProp,
|
sProp,
|
||||||
|
sString,
|
||||||
|
sUnion,
|
||||||
} from "@hediet/semantic-json";
|
} from "@hediet/semantic-json";
|
||||||
import { makeLazyLoadable } from "../../utils/LazyLoadable";
|
import { Deferred } from "@hediet/std/synchronization";
|
||||||
import {
|
import {
|
||||||
|
createLazyReactVisualization,
|
||||||
createVisualizer,
|
createVisualizer,
|
||||||
ReactVisualization,
|
|
||||||
globalVisualizationFactory,
|
globalVisualizationFactory,
|
||||||
} from "@hediet/visualization-core";
|
} from "@hediet/visualization-core";
|
||||||
|
import * as React from "react";
|
||||||
|
import { visualizationNs } from "../../consts";
|
||||||
|
import { makeLazyLoadable } from "../../utils/LazyLoadable";
|
||||||
|
|
||||||
const PlotlyViewerLazyLoadable = makeLazyLoadable(
|
const PlotlyViewerLazyLoadable = makeLazyLoadable(
|
||||||
async () => (await import("./PlotlyViewer")).PlotlyViewer
|
async () => (await import("./PlotlyViewer")).PlotlyViewer
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const sDatum = sUnion([sString(), sNumber(), sNull()]);
|
||||||
|
const sDatumArr = sUnion([sArrayOf(sDatum), sArrayOf(sArrayOf(sDatum))]);
|
||||||
|
//const x: Plotly.Data;
|
||||||
export const plotlyVisualizer = createVisualizer({
|
export const plotlyVisualizer = createVisualizer({
|
||||||
id: "plotly",
|
id: "plotly",
|
||||||
name: "plotly",
|
name: "plotly",
|
||||||
@@ -25,23 +33,122 @@ export const plotlyVisualizer = createVisualizer({
|
|||||||
kind: sOpenObject({
|
kind: sOpenObject({
|
||||||
plotly: sLiteral(true),
|
plotly: sLiteral(true),
|
||||||
}),
|
}),
|
||||||
data: sProp(sArrayOf(sAny()), {
|
data: sProp(
|
||||||
description:
|
sArrayOf(
|
||||||
"Expecting Plotly.Data[] (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/795ce172038dbafcb9cba030d637d733a7eea19c/types/plotly.js/index.d.ts#L1036)",
|
sOpenObject({
|
||||||
}),
|
text: sOptionalProp(
|
||||||
layout: sOptionalProp(sAny(), {
|
sUnion([sString(), sArrayOf(sString())])
|
||||||
description:
|
),
|
||||||
"Expecting Partial<Plotly.Layout> (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/795ce172038dbafcb9cba030d637d733a7eea19c/types/plotly.js/index.d.ts#L329)",
|
xaxis: sOptionalProp(sString()),
|
||||||
}),
|
yaxis: sOptionalProp(sString()),
|
||||||
}),
|
x: sOptionalProp(sDatumArr),
|
||||||
|
y: sOptionalProp(sDatumArr),
|
||||||
|
z: sOptionalProp(sDatumArr),
|
||||||
|
cells: sOptionalProp(
|
||||||
|
sOpenObject({
|
||||||
|
values: sArrayOf(sDatumArr),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
header: sOptionalProp(
|
||||||
|
sOpenObject({
|
||||||
|
values: sDatumArr,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
domain: sOptionalProp(
|
||||||
|
sOpenObject({
|
||||||
|
x: sArrayOf(sNumber()),
|
||||||
|
y: sArrayOf(sNumber()),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
type: sOptionalProp(
|
||||||
|
sUnion([
|
||||||
|
sLiteral("bar"),
|
||||||
|
sLiteral("box"),
|
||||||
|
sLiteral("candlestick"),
|
||||||
|
sLiteral("choropleth"),
|
||||||
|
sLiteral("contour"),
|
||||||
|
sLiteral("heatmap"),
|
||||||
|
sLiteral("histogram"),
|
||||||
|
sLiteral("indicator"),
|
||||||
|
sLiteral("mesh3d"),
|
||||||
|
sLiteral("ohlc"),
|
||||||
|
sLiteral("parcoords"),
|
||||||
|
sLiteral("pie"),
|
||||||
|
sLiteral("pointcloud"),
|
||||||
|
sLiteral("scatter"),
|
||||||
|
sLiteral("scatter3d"),
|
||||||
|
sLiteral("scattergeo"),
|
||||||
|
sLiteral("scattergl"),
|
||||||
|
sLiteral("scatterpolar"),
|
||||||
|
sLiteral("scatterternary"),
|
||||||
|
sLiteral("sunburst"),
|
||||||
|
sLiteral("surface"),
|
||||||
|
sLiteral("treemap"),
|
||||||
|
sLiteral("waterfall"),
|
||||||
|
sLiteral("funnel"),
|
||||||
|
sLiteral("funnelarea"),
|
||||||
|
sLiteral("scattermapbox"),
|
||||||
|
sLiteral("table"),
|
||||||
|
])
|
||||||
|
),
|
||||||
|
mode: sOptionalProp(
|
||||||
|
sUnion([
|
||||||
|
sLiteral("lines"),
|
||||||
|
sLiteral("markers"),
|
||||||
|
sLiteral("text"),
|
||||||
|
sLiteral("lines+markers"),
|
||||||
|
sLiteral("text+markers"),
|
||||||
|
sLiteral("text+lines"),
|
||||||
|
sLiteral("text+lines+markers"),
|
||||||
|
sLiteral("none"),
|
||||||
|
sLiteral("gauge"),
|
||||||
|
sLiteral("number"),
|
||||||
|
sLiteral("delta"),
|
||||||
|
sLiteral("number+delta"),
|
||||||
|
sLiteral("gauge+number"),
|
||||||
|
sLiteral("gauge+number+delta"),
|
||||||
|
sLiteral("gauge+delta"),
|
||||||
|
])
|
||||||
|
),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
"Expecting Plotly.Data[] (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/795ce172038dbafcb9cba030d637d733a7eea19c/types/plotly.js/index.d.ts#L1036)",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
layout: sOptionalProp(
|
||||||
|
sOpenObject({
|
||||||
|
title: sOptionalProp(sString()),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
description:
|
||||||
|
"Expecting Partial<Plotly.Layout> (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/795ce172038dbafcb9cba030d637d733a7eea19c/types/plotly.js/index.d.ts#L329)",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}).defineAs(visualizationNs("PlotlyVisualizationData")),
|
||||||
getVisualization: (data, self) =>
|
getVisualization: (data, self) =>
|
||||||
new ReactVisualization(self, { priority: 1000 }, theme => (
|
createLazyReactVisualization(
|
||||||
<PlotlyViewerLazyLoadable
|
self,
|
||||||
data={data.data}
|
{
|
||||||
layout={data.layout}
|
priority: 1000,
|
||||||
theme={theme.kind}
|
preload: PlotlyViewerLazyLoadable.preload,
|
||||||
/>
|
},
|
||||||
)),
|
({ theme }) => {
|
||||||
|
const b = new Deferred();
|
||||||
|
return {
|
||||||
|
node: (
|
||||||
|
<PlotlyViewerLazyLoadable
|
||||||
|
data={data.data}
|
||||||
|
layout={data.layout}
|
||||||
|
theme={theme}
|
||||||
|
onReady={() => b.resolve}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
ready: b.promise,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
globalVisualizationFactory.addVisualizer(plotlyVisualizer);
|
globalVisualizationFactory.addVisualizer(plotlyVisualizer);
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
.themeable {
|
||||||
|
--visualizer-plotly-background: var(--visualizer-background);
|
||||||
|
}
|
||||||
@@ -1,33 +1,62 @@
|
|||||||
import { sOpenObject, sLiteral, sString } from "@hediet/semantic-json";
|
import { sOpenObject, sLiteral, sString } from "@hediet/semantic-json";
|
||||||
import React from "react";
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
createVisualizer,
|
createVisualizer,
|
||||||
ReactVisualization,
|
|
||||||
globalVisualizationFactory,
|
globalVisualizationFactory,
|
||||||
|
createReactVisualization,
|
||||||
} from "@hediet/visualization-core";
|
} from "@hediet/visualization-core";
|
||||||
|
import { visualizationNs } from "../../consts";
|
||||||
|
|
||||||
export const simpleTextVisualizer = createVisualizer({
|
export const simpleTextVisualizer = createVisualizer({
|
||||||
|
// The id must be unique
|
||||||
id: "simple-text",
|
id: "simple-text",
|
||||||
|
// The name can be some display text
|
||||||
name: "Simple Text",
|
name: "Simple Text",
|
||||||
|
// Here you can define the schema for the data that this visualizer can handle.
|
||||||
|
// You must have a kind property of type object whose properties must be `true`.
|
||||||
serializer: sOpenObject({
|
serializer: sOpenObject({
|
||||||
kind: sOpenObject({
|
kind: sOpenObject({
|
||||||
text: sLiteral(true),
|
text: sLiteral(true),
|
||||||
}),
|
}),
|
||||||
text: sString(),
|
text: sString(),
|
||||||
}),
|
}).defineAs(
|
||||||
getVisualization: (data, self) =>
|
// This name is important for schema and code generation of the interface types.
|
||||||
new ReactVisualization(self, { priority: 100 }, () => (
|
// Make sure it is unique. It should end with "VisualizationData".
|
||||||
<pre
|
visualizationNs("SimpleTextVisualizationData")
|
||||||
className="visualizer-simple-text"
|
),
|
||||||
style={{
|
getVisualization: (
|
||||||
margin: 0,
|
/* The type of data is specified by the serializer above. */ data,
|
||||||
padding: 10,
|
self
|
||||||
color: "var(--visualizer-simple-text-color)",
|
) =>
|
||||||
}}
|
// If you want to use react, this is the way to go.
|
||||||
>
|
// Return your own implementation if you want to render directly to the DOM.
|
||||||
{data.text}
|
createReactVisualization(
|
||||||
</pre>
|
self,
|
||||||
)),
|
{
|
||||||
|
// The priority is used for automatically selecting the best visualization
|
||||||
|
// if multiple visualizations can handle the data.
|
||||||
|
priority: 100,
|
||||||
|
},
|
||||||
|
() => (
|
||||||
|
<pre
|
||||||
|
className="visualizer-simple-text"
|
||||||
|
style={{
|
||||||
|
margin: 0,
|
||||||
|
padding: 10,
|
||||||
|
// Use css vars for theming.
|
||||||
|
// See ./style.scss.
|
||||||
|
color: "var(--visualizer-simple-text-color)",
|
||||||
|
background: "var(--visualizer-simple-text-background)",
|
||||||
|
height: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{data.text}
|
||||||
|
</pre>
|
||||||
|
)
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// This registers the visualizer for automatic discovery.
|
||||||
|
// Make sure to import this file in "../index.ts" so that it gets loaded when
|
||||||
|
// someone imports "@hediet/visualization-bundle"!
|
||||||
globalVisualizationFactory.addVisualizer(simpleTextVisualizer);
|
globalVisualizationFactory.addVisualizer(simpleTextVisualizer);
|
||||||
|
|||||||
@@ -1,3 +1,8 @@
|
|||||||
|
// Provide default colors for both the dark and light theme here!
|
||||||
|
|
||||||
|
.themeable {
|
||||||
|
--visualizer-simple-text-background: var(--visualizer-background);
|
||||||
|
}
|
||||||
.theme-dark {
|
.theme-dark {
|
||||||
--visualizer-simple-text-color: white;
|
--visualizer-simple-text-color: white;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,38 @@
|
|||||||
import { sAny } from "@hediet/semantic-json";
|
import { JsonSchemaGenerator, sOpenObject } from "@hediet/semantic-json";
|
||||||
import React from "react";
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
|
createReactVisualization,
|
||||||
createVisualizer,
|
createVisualizer,
|
||||||
ReactVisualization,
|
|
||||||
globalVisualizationFactory,
|
globalVisualizationFactory,
|
||||||
} from "@hediet/visualization-core";
|
} from "@hediet/visualization-core";
|
||||||
import { MonacoEditor } from "../monaco-text-visualizer/MonacoEditor";
|
import { visualizationNs } from "../../consts";
|
||||||
|
import { MonacoEditorLazyLoadable } from "../monaco-text-visualizer";
|
||||||
|
|
||||||
export const sourceVisualizer = createVisualizer({
|
export const sourceVisualizer = createVisualizer({
|
||||||
id: "source",
|
id: "source",
|
||||||
name: "JSON Source",
|
name: "JSON Source",
|
||||||
serializer: sAny(),
|
serializer: sOpenObject({
|
||||||
|
kind: sOpenObject({}),
|
||||||
|
}).defineAs(visualizationNs("SourceVisualizationData")),
|
||||||
getVisualization: (data, self) =>
|
getVisualization: (data, self) =>
|
||||||
new ReactVisualization(self, { priority: -100 }, theme => (
|
createReactVisualization(self, { priority: -100 }, ({ theme }) => {
|
||||||
<MonacoEditor
|
const s = new JsonSchemaGenerator();
|
||||||
text={JSON.stringify(data, undefined, 4)}
|
|
||||||
languageId={"json"}
|
return (
|
||||||
theme={theme}
|
<MonacoEditorLazyLoadable
|
||||||
/>
|
text={JSON.stringify(data, undefined, 4)}
|
||||||
)),
|
fileName={"main.json"}
|
||||||
|
theme={theme}
|
||||||
|
jsonSchemas={[
|
||||||
|
{
|
||||||
|
schema: s.getJsonSchemaWithDefinitions(
|
||||||
|
globalVisualizationFactory.getSerializer()
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
globalVisualizationFactory.addHiddenVisualizer(sourceVisualizer);
|
globalVisualizationFactory.addHiddenVisualizer(sourceVisualizer);
|
||||||
|
|||||||
@@ -74,7 +74,13 @@ export class SvgViewer extends React.Component<{
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ measureRef, contentRect }) => (
|
{({
|
||||||
|
measureRef,
|
||||||
|
contentRect,
|
||||||
|
}: {
|
||||||
|
measureRef: any;
|
||||||
|
contentRect: any;
|
||||||
|
}) => (
|
||||||
<div
|
<div
|
||||||
ref={measureRef}
|
ref={measureRef}
|
||||||
className="svgViewer"
|
className="svgViewer"
|
||||||
@@ -85,7 +91,7 @@ export class SvgViewer extends React.Component<{
|
|||||||
height={heightOrDefault(contentRect)}
|
height={heightOrDefault(contentRect)}
|
||||||
tool={tool}
|
tool={tool}
|
||||||
value={val as any}
|
value={val as any}
|
||||||
onChangeValue={v => (this.value = v)}
|
onChangeValue={(v: any) => (this.value = v)}
|
||||||
onChangeTool={this.setTool}
|
onChangeTool={this.setTool}
|
||||||
ref={this.svgPanZoomRef}
|
ref={this.svgPanZoomRef}
|
||||||
toolbarProps={{
|
toolbarProps={{
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import React from "react";
|
import * as React from "react";
|
||||||
import { sOpenObject, sLiteral, sString } from "@hediet/semantic-json";
|
import { sOpenObject, sLiteral, sString, sProp } from "@hediet/semantic-json";
|
||||||
import { SvgViewer } from "./SvgViewer";
|
import { SvgViewer } from "./SvgViewer";
|
||||||
import {
|
import {
|
||||||
createVisualizer,
|
createVisualizer,
|
||||||
ReactVisualization,
|
|
||||||
globalVisualizationFactory,
|
globalVisualizationFactory,
|
||||||
|
createReactVisualization,
|
||||||
} from "@hediet/visualization-core";
|
} from "@hediet/visualization-core";
|
||||||
|
import { visualizationNs } from "../../consts";
|
||||||
|
|
||||||
export const svgVisualizer = createVisualizer({
|
export const svgVisualizer = createVisualizer({
|
||||||
id: "svg",
|
id: "svg",
|
||||||
@@ -14,10 +15,10 @@ export const svgVisualizer = createVisualizer({
|
|||||||
kind: sOpenObject({
|
kind: sOpenObject({
|
||||||
svg: sLiteral(true),
|
svg: sLiteral(true),
|
||||||
}),
|
}),
|
||||||
text: sString(),
|
text: sProp(sString(), { description: "The svg content" }),
|
||||||
}),
|
}).defineAs(visualizationNs("SvgVisualizationData")),
|
||||||
getVisualization: (data, self) =>
|
getVisualization: (data, self) =>
|
||||||
new ReactVisualization(self, { priority: 1500 }, () => (
|
createReactVisualization(self, { priority: 1500 }, () => (
|
||||||
<SvgViewer svgContent={data.text} />
|
<SvgViewer svgContent={data.text} />
|
||||||
)),
|
)),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ import { Properties } from "csstype";
|
|||||||
|
|
||||||
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
|
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
|
||||||
|
|
||||||
export function omit<T, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
|
export function omit<T extends {}, K extends keyof T>(
|
||||||
|
obj: T,
|
||||||
|
keys: K[]
|
||||||
|
): Omit<T, K> {
|
||||||
const newObj: Omit<T, K> = {} as any;
|
const newObj: Omit<T, K> = {} as any;
|
||||||
for (const [key, val] of Object.entries(obj)) {
|
for (const [key, val] of Object.entries(obj)) {
|
||||||
if (!keys.includes(key as K)) {
|
if (!keys.includes(key as K)) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { observer, disposeOnUnmount } from "mobx-react";
|
import { observer, disposeOnUnmount } from "mobx-react";
|
||||||
import { observable, autorun, computed, action, runInAction } from "mobx";
|
import { observable, autorun, computed, action, runInAction } from "mobx";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Rectangle, Point } from "./Point";
|
import { Rectangle, Point } from "./Point";
|
||||||
import { SvgLine } from "./SvgElements";
|
import { SvgLine } from "./SvgElements";
|
||||||
import { Item } from ".";
|
import { Item } from ".";
|
||||||
@@ -226,33 +226,14 @@ export class TreeWithPathView extends React.Component<{
|
|||||||
<span>
|
<span>
|
||||||
{model.selected.path.reduce((acc, v) => {
|
{model.selected.path.reduce((acc, v) => {
|
||||||
acc = acc.slice();
|
acc = acc.slice();
|
||||||
function add() {
|
acc.push(
|
||||||
acc.push(
|
<span
|
||||||
<span
|
className="part-path-item"
|
||||||
className="part-path-item"
|
key={acc.length}
|
||||||
key={acc.length}
|
>
|
||||||
>
|
{v}
|
||||||
{v}
|
</span>
|
||||||
</span>
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
if (isValidFunctionName(v)) {
|
|
||||||
if (acc.length > 0) {
|
|
||||||
acc.push(
|
|
||||||
<span key={acc.length}>.</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
add();
|
|
||||||
} else {
|
|
||||||
acc.push(
|
|
||||||
<span key={acc.length}>[</span>
|
|
||||||
);
|
|
||||||
add();
|
|
||||||
acc.push(
|
|
||||||
<span key={acc.length}>]</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, new Array<React.ReactElement>())}
|
}, new Array<React.ReactElement>())}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -15,11 +15,12 @@ import {
|
|||||||
Serializer,
|
Serializer,
|
||||||
sOptionalProp,
|
sOptionalProp,
|
||||||
sUnion,
|
sUnion,
|
||||||
|
sProp,
|
||||||
} from "@hediet/semantic-json";
|
} from "@hediet/semantic-json";
|
||||||
import {
|
import {
|
||||||
createVisualizer,
|
createVisualizer,
|
||||||
ReactVisualization,
|
|
||||||
globalVisualizationFactory,
|
globalVisualizationFactory,
|
||||||
|
createReactVisualization,
|
||||||
} from "@hediet/visualization-core";
|
} from "@hediet/visualization-core";
|
||||||
import { visualizationNs } from "../../consts";
|
import { visualizationNs } from "../../consts";
|
||||||
|
|
||||||
@@ -37,25 +38,39 @@ export interface Item {
|
|||||||
|
|
||||||
const sTreeNode: Serializer<TreeNode> = sLazy(() =>
|
const sTreeNode: Serializer<TreeNode> = sLazy(() =>
|
||||||
sOpenObject({
|
sOpenObject({
|
||||||
children: sArrayOf(sTreeNode),
|
children: sProp(sArrayOf(sTreeNode), {
|
||||||
items: sArrayOf(
|
description: "The children of this tree-node",
|
||||||
sOpenObject({
|
}),
|
||||||
text: sString(),
|
items: sProp(
|
||||||
emphasis: sOptionalProp(
|
sArrayOf(
|
||||||
sUnion(
|
sOpenObject({
|
||||||
[
|
text: sProp(sString(), { description: "The text to show" }),
|
||||||
sLiteral("style1"),
|
emphasis: sOptionalProp(
|
||||||
sLiteral("style2"),
|
sUnion(
|
||||||
sLiteral("style3"),
|
[
|
||||||
sString(),
|
sLiteral("style1"),
|
||||||
],
|
sLiteral("style2"),
|
||||||
{ inclusive: true }
|
sLiteral("style3"),
|
||||||
)
|
sString(),
|
||||||
),
|
],
|
||||||
}).defineAs(visualizationNs("TreeNodeItem"))
|
{ inclusive: true }
|
||||||
|
),
|
||||||
|
{
|
||||||
|
description: "The style of the text",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}).defineAs(visualizationNs("TreeNodeItem"))
|
||||||
|
),
|
||||||
|
{ description: "The parts that make up the text of this item" }
|
||||||
),
|
),
|
||||||
segment: sOptionalProp(sString()),
|
segment: sOptionalProp(sString(), {
|
||||||
isMarked: sOptionalProp(sBoolean()),
|
description:
|
||||||
|
"If a node is selected, the concatenation of all segment values from root to the selected node is shown to the user.",
|
||||||
|
}),
|
||||||
|
isMarked: sOptionalProp(sBoolean(), {
|
||||||
|
description:
|
||||||
|
"Marked nodes are highlighted and scrolled into view on every visualization update.",
|
||||||
|
}),
|
||||||
}).defineAs(visualizationNs("TreeNode"))
|
}).defineAs(visualizationNs("TreeNode"))
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -64,14 +79,14 @@ const sTree = sOpenObject({
|
|||||||
tree: sLiteral(true),
|
tree: sLiteral(true),
|
||||||
}),
|
}),
|
||||||
root: sTreeNode,
|
root: sTreeNode,
|
||||||
});
|
}).defineAs(visualizationNs("TreeVisualizationData"));
|
||||||
|
|
||||||
export const treeVisualizer = createVisualizer({
|
export const treeVisualizer = createVisualizer({
|
||||||
id: "tree",
|
id: "tree",
|
||||||
name: "Tree",
|
name: "Tree",
|
||||||
serializer: sTree,
|
serializer: sTree,
|
||||||
getVisualization: (data, self) =>
|
getVisualization: (data, self) =>
|
||||||
new ReactVisualization(self, { priority: 1200 }, () => {
|
createReactVisualization(self, { priority: 1200 }, () => {
|
||||||
const m = createTreeViewModelFromTreeNodeData(
|
const m = createTreeViewModelFromTreeNodeData(
|
||||||
data.root,
|
data.root,
|
||||||
() => undefined
|
() => undefined
|
||||||
@@ -98,8 +113,10 @@ export function createTreeViewModelFromTreeNodeData<TData, TNodeData>(
|
|||||||
recurse(c, viewModel)
|
recurse(c, viewModel)
|
||||||
);
|
);
|
||||||
|
|
||||||
let segment = node.segment;
|
let segment;
|
||||||
if (!segment && node.items.length > 0) {
|
if (node.segment) {
|
||||||
|
segment = node.segment;
|
||||||
|
} else if (node.items.length > 0) {
|
||||||
segment = node.items[0].text;
|
segment = node.items[0].text;
|
||||||
} else {
|
} else {
|
||||||
segment = "";
|
segment = "";
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
.themeable {
|
||||||
|
--visualizer-tree-background: var(--visualizer-background);
|
||||||
|
}
|
||||||
|
|
||||||
.theme-dark {
|
.theme-dark {
|
||||||
--visualizer-tree-childOrThisMarked-background: orange;
|
--visualizer-tree-childOrThisMarked-background: orange;
|
||||||
--visualizer-tree-childOrThisSelected-background: white;
|
--visualizer-tree-childOrThisSelected-background: white;
|
||||||
@@ -124,6 +128,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.component-TreeWithPathView {
|
.component-TreeWithPathView {
|
||||||
|
background-color: var(--visualizer-tree-background);
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
@import "./src/vars.scss";
|
||||||
@import "./src/visualizers/grid-visualizer/style.scss";
|
@import "./src/visualizers/grid-visualizer/style.scss";
|
||||||
@import "./src/visualizers/simple-text-visualizer/style.scss";
|
@import "./src/visualizers/simple-text-visualizer/style.scss";
|
||||||
@import "./src/visualizers/monaco-text-visualizer/style.scss";
|
@import "./src/visualizers/monaco-text-visualizer/style.scss";
|
||||||
|
@import "./src/visualizers/monaco-text-diff-visualizer/style.scss";
|
||||||
@import "./src/visualizers/tree-visualizer/style.scss";
|
@import "./src/visualizers/tree-visualizer/style.scss";
|
||||||
@import "./src/visualizers/ast-visualizer/style.scss";
|
@import "./src/visualizers/ast-visualizer/style.scss";
|
||||||
@import "./src/visualizers/svg-visualizer/style.scss";
|
@import "./src/visualizers/svg-visualizer/style.scss";
|
||||||
|
@import "./src/visualizers/graph/graph-visjs-visualizer/style.scss";
|
||||||
|
@import "./src/visualizers/plotly-visualizer/style.scss";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"module": "es2020",
|
"module": "CommonJS",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ const visualizations = globalVisualizationFactory.getVisualizations(
|
|||||||
/* preferred visualization id */ undefined
|
/* preferred visualization id */ undefined
|
||||||
);
|
);
|
||||||
// `visualizations.bestVisualization` is the visualization that is best suited to visualize the data.
|
// `visualizations.bestVisualization` is the visualization that is best suited to visualize the data.
|
||||||
// `visualiztaions.allVisualizations` contains all other available visualizations than can visualize the data.
|
// `visualiztaions.allVisualizations` contains all suitable visualizations.
|
||||||
```
|
```
|
||||||
|
|
||||||
If you use react, you can use the `VisualizationView` component to render a visualization:
|
If you use react, you can use the `VisualizationView` component to render a visualization:
|
||||||
@@ -57,3 +57,7 @@ function App() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|

|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@hediet/visualization-core",
|
"name": "@hediet/visualization-core",
|
||||||
"description": "Provides infrastructure to find registered visualizations for given json data",
|
"description": "Provides infrastructure to find registered visualizations for given json data",
|
||||||
"version": "0.1.0",
|
"version": "0.3.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://github.com/hediet/visualization",
|
"homepage": "https://github.com/hediet/visualization",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -27,8 +27,9 @@
|
|||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@hediet/semantic-json": "^0.3.15",
|
||||||
"@hediet/std": "^0.6.0",
|
"@hediet/std": "^0.6.0",
|
||||||
"@hediet/semantic-json": "^0.3.11",
|
"mobx": "^5.15.4",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
"react-measure": "^2.3.0"
|
"react-measure": "^2.3.0"
|
||||||
|
|||||||
30
visualization-core/src/CanvasVisualization.tsx
Normal file
30
visualization-core/src/CanvasVisualization.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
import { createReactVisualization } from "./ReactVisualization";
|
||||||
|
import { Visualization, Visualizer } from "./Visualizer";
|
||||||
|
import { Disposable } from "@hediet/std/disposable";
|
||||||
|
|
||||||
|
export function createCanvas2DVisualization(
|
||||||
|
sourceVisualizer: Visualizer,
|
||||||
|
options: { priority: number },
|
||||||
|
render: (context: CanvasRenderingContext2D) => Disposable | void
|
||||||
|
): Visualization {
|
||||||
|
return createReactVisualization(
|
||||||
|
sourceVisualizer,
|
||||||
|
{ priority: options.priority },
|
||||||
|
() => (
|
||||||
|
<canvas
|
||||||
|
ref={(canvas) => {
|
||||||
|
if (canvas) {
|
||||||
|
const ctx = canvas.getContext("2d")!;
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
// We don't want to use try/catch to swallow the error, so we use setTimeout to protect the caller
|
||||||
|
setTimeout(() => {
|
||||||
|
render(ctx);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,56 +1,105 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import * as ReactDom from "react-dom";
|
import * as ReactDom from "react-dom";
|
||||||
import { Visualization, Theme } from "./index";
|
import { Visualization, Theme } from "./index";
|
||||||
import { Visualizer, VisualizationId, asVisualizationId } from "./Visualizer";
|
import {
|
||||||
|
Visualizer,
|
||||||
|
VisualizationId,
|
||||||
|
asVisualizationId,
|
||||||
|
VisualizationRenderOptions,
|
||||||
|
} from "./Visualizer";
|
||||||
|
import { Deferred } from "@hediet/std/synchronization";
|
||||||
|
|
||||||
interface Options {
|
interface ReactVisualizationArgs {
|
||||||
id?: string;
|
|
||||||
name?: string;
|
|
||||||
priority: number;
|
priority: number;
|
||||||
|
preload?: (() => Promise<void>) | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export abstract class BaseReactVisualization implements Visualization<unknown> {
|
class ReactVisualization implements Visualization<unknown> {
|
||||||
public readonly id: VisualizationId;
|
public readonly id: VisualizationId;
|
||||||
public readonly name: string;
|
public readonly name: string;
|
||||||
public readonly priority: number;
|
public readonly priority: number;
|
||||||
|
private readonly _preload: (() => Promise<void>) | undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly sourceVisualizer: Visualizer,
|
public readonly sourceVisualizer: Visualizer,
|
||||||
options: Options
|
options: ReactVisualizationArgs,
|
||||||
|
private readonly getReactNode: (
|
||||||
|
options: ReactVisualizationRenderArgs
|
||||||
|
) => {
|
||||||
|
node: React.ReactChild;
|
||||||
|
ready: Promise<void>;
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
this.id = asVisualizationId(
|
this.id = asVisualizationId(sourceVisualizer.id.toString());
|
||||||
options.id || sourceVisualizer.id.toString()
|
this.name = sourceVisualizer.name;
|
||||||
);
|
|
||||||
this.name = options.name || sourceVisualizer.name;
|
|
||||||
this.priority = options.priority;
|
this.priority = options.priority;
|
||||||
|
this._preload = options.preload;
|
||||||
|
}
|
||||||
|
|
||||||
|
public preload(): Promise<void> {
|
||||||
|
return this._preload ? this._preload() : Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
public render(
|
public render(
|
||||||
target: HTMLDivElement,
|
target: HTMLDivElement,
|
||||||
theme: Theme,
|
options: VisualizationRenderOptions<unknown>
|
||||||
previous: unknown | undefined
|
): { renderState: unknown; ready: Promise<void> } {
|
||||||
): unknown {
|
const { node, ready } = this.getReactNode({
|
||||||
const node = this.getReactNode(theme);
|
theme: options.theme,
|
||||||
|
});
|
||||||
// We could use this to get access to theme vars:
|
// We could use this to get access to theme vars:
|
||||||
// getComputedStyle(target).getPropertyValue("--background-color")
|
// getComputedStyle(target).getPropertyValue("--background-color")
|
||||||
|
|
||||||
ReactDom.render(<>{node}</>, target);
|
ReactDom.render(<>{node}</>, target);
|
||||||
return undefined;
|
return { renderState: undefined, ready };
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract getReactNode(theme: Theme): React.ReactChild;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ReactVisualization extends BaseReactVisualization {
|
|
||||||
constructor(
|
|
||||||
sourceVisualizer: Visualizer,
|
|
||||||
options: Options,
|
|
||||||
private readonly renderReactNode: (theme: Theme) => React.ReactChild
|
|
||||||
) {
|
|
||||||
super(sourceVisualizer, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getReactNode(theme: Theme): React.ReactChild {
|
|
||||||
return this.renderReactNode(theme);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ReactVisualizationRenderArgs {
|
||||||
|
theme: Theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createReactVisualization(
|
||||||
|
sourceVisualizer: Visualizer,
|
||||||
|
args: ReactVisualizationArgs,
|
||||||
|
renderReactNode: (args: ReactVisualizationRenderArgs) => React.ReactChild
|
||||||
|
) {
|
||||||
|
return new ReactVisualization(sourceVisualizer, args, options => {
|
||||||
|
const deferred = new Deferred();
|
||||||
|
return {
|
||||||
|
node: (
|
||||||
|
<ComponentWithOnMount
|
||||||
|
onDidMount={() => deferred.resolve()}
|
||||||
|
child={renderReactNode(options)}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
ready: deferred.promise,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class ComponentWithOnMount extends React.Component<{
|
||||||
|
child: React.ReactChild;
|
||||||
|
onDidMount: () => void;
|
||||||
|
}> {
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.onDidMount();
|
||||||
|
}
|
||||||
|
|
||||||
|
render(): React.ReactNode {
|
||||||
|
return this.props.child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createLazyReactVisualization(
|
||||||
|
sourceVisualizer: Visualizer,
|
||||||
|
args: ReactVisualizationArgs,
|
||||||
|
renderReactNode: (
|
||||||
|
args: ReactVisualizationRenderArgs
|
||||||
|
) => {
|
||||||
|
node: React.ReactChild;
|
||||||
|
ready: Promise<void>;
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
return new ReactVisualization(sourceVisualizer, args, renderReactNode);
|
||||||
|
}
|
||||||
|
|||||||
26
visualization-core/src/RegisterVisualizerFn.ts
Normal file
26
visualization-core/src/RegisterVisualizerFn.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import * as semanticJson from "@hediet/semantic-json";
|
||||||
|
import { createCanvas2DVisualization } from "./CanvasVisualization";
|
||||||
|
import { CreateVisualizerOptions } from "./createVisualizer";
|
||||||
|
import { Visualization, Visualizer } from "./Visualizer";
|
||||||
|
|
||||||
|
export type RegisterVisualizerFn = (
|
||||||
|
register: <TData>(
|
||||||
|
visualizationOptions: CreateVisualizerOptions<TData>
|
||||||
|
) => void,
|
||||||
|
lib: Lib
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
interface Lib {
|
||||||
|
semanticJson: typeof semanticJson;
|
||||||
|
|
||||||
|
createCanvas2DVisualization: (
|
||||||
|
visualizer: Visualizer,
|
||||||
|
options: { priority: number },
|
||||||
|
render: (context: CanvasRenderingContext2D) => void
|
||||||
|
) => Visualization;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const libImplementation: Lib = {
|
||||||
|
semanticJson,
|
||||||
|
createCanvas2DVisualization,
|
||||||
|
};
|
||||||
@@ -1,9 +1,36 @@
|
|||||||
export class Theme {
|
export class Theme {
|
||||||
public static readonly light = new Theme("light", "light");
|
private static _light: Theme;
|
||||||
public static readonly dark = new Theme("dark", "dark");
|
public static get light(): Theme {
|
||||||
|
if (!this._light) {
|
||||||
|
this._light = new Theme("light", "light");
|
||||||
|
}
|
||||||
|
return this._light;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static _dark: Theme;
|
||||||
|
public static get dark(): Theme {
|
||||||
|
if (!this._dark) {
|
||||||
|
this._dark = new Theme("dark", "dark");
|
||||||
|
}
|
||||||
|
return this._dark;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly divElement = document.createElement("div");
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly id: string,
|
public readonly id: string,
|
||||||
public readonly kind: "light" | "dark"
|
public readonly kind: "light" | "dark"
|
||||||
) {}
|
) {
|
||||||
|
this.divElement.classList.add(
|
||||||
|
"themeable",
|
||||||
|
`theme-${this.id}`,
|
||||||
|
"visualization"
|
||||||
|
);
|
||||||
|
this.divElement.style.display = "none";
|
||||||
|
document.body.append(this.divElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
public resolveVarToColor(varName: string): string {
|
||||||
|
return getComputedStyle(this.divElement).getPropertyValue(varName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { Theme } from "./Theme";
|
|||||||
export class VisualizationView extends React.Component<{
|
export class VisualizationView extends React.Component<{
|
||||||
visualization: Visualization;
|
visualization: Visualization;
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
|
onReady?: () => void;
|
||||||
}> {
|
}> {
|
||||||
private readonly ref = React.createRef<HTMLDivElement>();
|
private readonly ref = React.createRef<HTMLDivElement>();
|
||||||
private visualizationRenderState: unknown = undefined;
|
private visualizationRenderState: unknown = undefined;
|
||||||
@@ -19,11 +20,18 @@ export class VisualizationView extends React.Component<{
|
|||||||
|
|
||||||
renderVisualization() {
|
renderVisualization() {
|
||||||
const { visualization, theme } = this.props;
|
const { visualization, theme } = this.props;
|
||||||
this.visualizationRenderState = visualization.render(
|
const { renderState, ready } = visualization.render(this.ref.current!, {
|
||||||
this.ref.current!,
|
|
||||||
theme,
|
theme,
|
||||||
this.visualizationRenderState
|
previousRenderState: this.visualizationRenderState,
|
||||||
);
|
});
|
||||||
|
|
||||||
|
ready.then(() => {
|
||||||
|
if (this.props.onReady) {
|
||||||
|
return this.props.onReady();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.visualizationRenderState = renderState;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|||||||
59
visualization-core/src/Visualizer.ts
Normal file
59
visualization-core/src/Visualizer.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { BaseSerializer } from "@hediet/semantic-json";
|
||||||
|
import { Theme } from "./Theme";
|
||||||
|
|
||||||
|
export interface Visualizer {
|
||||||
|
readonly id: VisualizerId;
|
||||||
|
readonly name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows to deserialize JSON into an visualization.
|
||||||
|
* This object is also used to inspect the schema of that JSON.
|
||||||
|
*/
|
||||||
|
readonly serializer: BaseSerializer<Visualization>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VisualizerId extends String {
|
||||||
|
__brand: "VisualizerId";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function asVisualizerId(id: string): VisualizerId {
|
||||||
|
return id as unknown as VisualizerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Visualization<TRenderState = any> {
|
||||||
|
readonly id: VisualizationId;
|
||||||
|
readonly name: string;
|
||||||
|
readonly priority: number;
|
||||||
|
|
||||||
|
render(
|
||||||
|
target: HTMLDivElement,
|
||||||
|
options: VisualizationRenderOptions<TRenderState>
|
||||||
|
): {
|
||||||
|
renderState: TRenderState;
|
||||||
|
/**
|
||||||
|
* Is resolved when the visualization stabilized and is ready to be shown.
|
||||||
|
* Note that the visualization can be shown before this promise resolves.
|
||||||
|
* This promise is useful for loading animations or integrations.
|
||||||
|
*/
|
||||||
|
ready: Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preloads resources to speed up first rendering.
|
||||||
|
* The visualization does not need to be rendered for this.
|
||||||
|
*/
|
||||||
|
preload(): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VisualizationId extends String {
|
||||||
|
__brand: "VisualizationId";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function asVisualizationId(id: string): VisualizationId {
|
||||||
|
return id as unknown as VisualizationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VisualizationRenderOptions<TRenderState> {
|
||||||
|
theme: Theme;
|
||||||
|
previousRenderState: TRenderState | undefined;
|
||||||
|
}
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { BaseSerializer } from "@hediet/semantic-json";
|
|
||||||
import { Theme } from "./Theme";
|
|
||||||
|
|
||||||
export interface VisualizerId extends String {
|
|
||||||
__brand: "VisualizerId";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function asVisualizerId(id: string): VisualizerId {
|
|
||||||
return (id as unknown) as VisualizerId;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Visualizer {
|
|
||||||
readonly id: VisualizerId;
|
|
||||||
readonly name: string;
|
|
||||||
readonly serializer: BaseSerializer<Visualization>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface VisualizationId extends String {
|
|
||||||
__brand: "VisualizationId";
|
|
||||||
}
|
|
||||||
|
|
||||||
export function asVisualizationId(id: string): VisualizationId {
|
|
||||||
return (id as unknown) as VisualizationId;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Visualization<TRenderState = any> {
|
|
||||||
readonly id: VisualizationId;
|
|
||||||
readonly name: string;
|
|
||||||
readonly priority: number;
|
|
||||||
readonly sourceVisualizer: Visualizer;
|
|
||||||
|
|
||||||
render(
|
|
||||||
target: HTMLDivElement,
|
|
||||||
theme: Theme,
|
|
||||||
previousRenderState: TRenderState | undefined
|
|
||||||
): TRenderState;
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Visualizer, Visualization, VisualizationId } from "./Visualizer";
|
import { Visualizer, Visualization, VisualizationId } from "./Visualizer";
|
||||||
import { VisualizationData } from "./VisualizationData";
|
import { VisualizationData } from "./VisualizationData";
|
||||||
import { JSONValue, sUnionMany } from "@hediet/semantic-json";
|
import { JSONValue, Serializer, sUnionMany } from "@hediet/semantic-json";
|
||||||
import { ObservableMap } from "mobx";
|
import { ObservableMap } from "mobx";
|
||||||
|
|
||||||
export class VisualizationFactory {
|
export class VisualizationFactory {
|
||||||
@@ -10,13 +10,17 @@ export class VisualizationFactory {
|
|||||||
Visualizer
|
Visualizer
|
||||||
>();
|
>();
|
||||||
|
|
||||||
public getSerializer() {
|
public getSerializer(): Serializer<[Visualization<any>, Visualizer][]> {
|
||||||
return this._getSerializer(this.visualizers);
|
return this._getSerializer(this.visualizers);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _getSerializer(map: ObservableMap<string, Visualizer>) {
|
private _getSerializer(map: ObservableMap<string, Visualizer>): Serializer<[Visualization<any>, Visualizer][]> {
|
||||||
return sUnionMany(
|
return sUnionMany(
|
||||||
[...map.values()].map(v => v.serializer.asSerializer()),
|
[...map.values()].map((v) => v.serializer.asSerializer().refine<[Visualization, Visualizer]>({
|
||||||
|
canSerialize: (v): v is [Visualization, Visualizer] => false,
|
||||||
|
fromIntermediate: value => [value, v],
|
||||||
|
toIntermediate: () => { throw new Error("not supported"); }
|
||||||
|
})),
|
||||||
{ processingStrategy: "all" }
|
{ processingStrategy: "all" }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -25,6 +29,14 @@ export class VisualizationFactory {
|
|||||||
this.visualizers.set(visualizer.id.toString(), visualizer);
|
this.visualizers.set(visualizer.id.toString(), visualizer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getRegisteredVisualizers(): Visualizer[] {
|
||||||
|
return [...this.visualizers.values()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getRegisteredHiddenVisualizer(): Visualizer[] {
|
||||||
|
return [...this.hiddenVisualizers.values()];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hidden visualizers don't appear in `getSerializer` and should only be presented to the user if he explicitly selects it.
|
* Hidden visualizers don't appear in `getSerializer` and should only be presented to the user if he explicitly selects it.
|
||||||
* `getVisualizations(...).bestVisualization` is only the result of a hidden visualizer if the user requests a hidden visualization id.
|
* `getVisualizations(...).bestVisualization` is only the result of a hidden visualizer if the user requests a hidden visualization id.
|
||||||
@@ -38,22 +50,22 @@ export class VisualizationFactory {
|
|||||||
preferredVisualization: VisualizationId | undefined
|
preferredVisualization: VisualizationId | undefined
|
||||||
): Visualizations {
|
): Visualizations {
|
||||||
const u = this.getSerializer();
|
const u = this.getSerializer();
|
||||||
const result = u.deserialize((data as unknown) as JSONValue);
|
const result = u.deserialize(data as unknown as JSONValue);
|
||||||
const allVisualizations = result.value || [];
|
const allVisualizations = result.value || [];
|
||||||
|
|
||||||
const u2 = this._getSerializer(this.hiddenVisualizers);
|
const u2 = this._getSerializer(this.hiddenVisualizers);
|
||||||
const hiddenResult = u2.deserialize((data as unknown) as JSONValue);
|
const hiddenResult = u2.deserialize(data as unknown as JSONValue);
|
||||||
allVisualizations.push(...(hiddenResult.value || []));
|
allVisualizations.push(...(hiddenResult.value || []));
|
||||||
|
|
||||||
allVisualizations.sort((a, b) => b.priority - a.priority);
|
allVisualizations.sort(([a], [b]) => b.priority - a.priority);
|
||||||
|
|
||||||
let bestVisualization: Visualization | undefined = allVisualizations[0];
|
let bestVisualization: [Visualization, Visualizer] | undefined = allVisualizations[0];
|
||||||
if (bestVisualization && bestVisualization.priority < 0) {
|
if (bestVisualization && bestVisualization[0].priority < 0) {
|
||||||
bestVisualization = undefined;
|
bestVisualization = undefined;
|
||||||
}
|
}
|
||||||
if (preferredVisualization) {
|
if (preferredVisualization) {
|
||||||
const preferred = allVisualizations.find(
|
const preferred = allVisualizations.find(
|
||||||
vis => vis.id === preferredVisualization
|
([vis]) => vis.id === preferredVisualization
|
||||||
);
|
);
|
||||||
if (preferred) {
|
if (preferred) {
|
||||||
bestVisualization = preferred;
|
bestVisualization = preferred;
|
||||||
@@ -61,8 +73,9 @@ export class VisualizationFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bestVisualization,
|
bestVisualization: bestVisualization ? bestVisualization[0] : undefined,
|
||||||
allVisualizations,
|
bestVisualizationVisualizer: bestVisualization ? bestVisualization[1] : undefined,
|
||||||
|
allVisualizations: allVisualizations.map(v => v[0]),
|
||||||
visualizationDataErrors: [],
|
visualizationDataErrors: [],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -70,6 +83,7 @@ export class VisualizationFactory {
|
|||||||
|
|
||||||
export interface Visualizations {
|
export interface Visualizations {
|
||||||
bestVisualization: Visualization | undefined;
|
bestVisualization: Visualization | undefined;
|
||||||
|
bestVisualizationVisualizer: Visualizer | undefined;
|
||||||
allVisualizations: Visualization[];
|
allVisualizations: Visualization[];
|
||||||
visualizationDataErrors: VisualizationDataError[];
|
visualizationDataErrors: VisualizationDataError[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,30 @@
|
|||||||
import { BaseSerializer } from "@hediet/semantic-json";
|
import { BaseSerializer } from "@hediet/semantic-json";
|
||||||
import { Visualization, Visualizer, asVisualizerId } from "./Visualizer";
|
import { Visualization, Visualizer, asVisualizerId } from "./Visualizer";
|
||||||
|
|
||||||
export function createVisualizer<TData>(options: {
|
export interface CreateVisualizerOptions<TData> {
|
||||||
/**
|
/**
|
||||||
* A unique id for this visualizer.
|
* A unique id for this visualizer.
|
||||||
*/
|
*/
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A human readable name of this visualizer.
|
* A human readable name of this visualizer.
|
||||||
* Should be in English language.
|
* Should be in English language.
|
||||||
*/
|
*/
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
serializer: BaseSerializer<TData>;
|
serializer: BaseSerializer<TData>;
|
||||||
getVisualization: (data: TData, self: Visualizer) => Visualization;
|
getVisualization: (data: TData, self: Visualizer) => Visualization;
|
||||||
}): Visualizer {
|
}
|
||||||
|
|
||||||
|
export function createVisualizer<TData>(
|
||||||
|
options: CreateVisualizerOptions<TData>
|
||||||
|
): Visualizer {
|
||||||
const result: Visualizer = {
|
const result: Visualizer = {
|
||||||
id: asVisualizerId(options.id),
|
id: asVisualizerId(options.id),
|
||||||
name: options.name,
|
name: options.name,
|
||||||
serializer: options.serializer.refine({
|
serializer: options.serializer.refine({
|
||||||
fromIntermediate: data => options.getVisualization(data, result),
|
fromIntermediate: (data) => options.getVisualization(data, result),
|
||||||
canSerialize: (val): val is Visualization => false,
|
canSerialize: (val): val is Visualization => false,
|
||||||
toIntermediate: () => {
|
toIntermediate: () => {
|
||||||
throw new Error("not supported");
|
throw new Error("not supported");
|
||||||
@@ -5,3 +5,4 @@ export * from "./VisualizerRegistry";
|
|||||||
export * from "./VisualizationView";
|
export * from "./VisualizationView";
|
||||||
export * from "./Theme";
|
export * from "./Theme";
|
||||||
export * from "./createVisualizer";
|
export * from "./createVisualizer";
|
||||||
|
export * from "./RegisterVisualizerFn";
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "ES2020",
|
||||||
"module": "es2020",
|
"module": "CommonJS",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
|
|||||||
Reference in New Issue
Block a user