initial commit
This commit is contained in:
51
.gitignore
vendored
Normal file
51
.gitignore
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
jspm_packages
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
dist
|
||||
api
|
||||
|
||||
dist/
|
||||
|
||||
.npmrc
|
||||
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.js
|
||||
6
.prettierrc.json
Normal file
6
.prettierrc.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4,
|
||||
"semi": true,
|
||||
"useTabs": true
|
||||
}
|
||||
12
.vscode/launch.json
vendored
Normal file
12
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "pwa-chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:8080",
|
||||
"webRoot": "${workspaceFolder}/playground"
|
||||
}
|
||||
]
|
||||
}
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"tasksStatusbar.taskLabelFilter": "dev"
|
||||
}
|
||||
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Henning Dieterichs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
76
README.md
Normal file
76
README.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Visualization Framework
|
||||
|
||||
## Installation
|
||||
|
||||
You can use yarn to install the core library:
|
||||
|
||||
```
|
||||
yarn add @hediet/visualization-core
|
||||
```
|
||||
|
||||
If you want to use the existing visualizations, add the bundle library:
|
||||
|
||||
```
|
||||
yarn add @hediet/visualization-bundle
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
13
package.json
Normal file
13
package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"./visualization-core",
|
||||
"./visualization-bundle",
|
||||
"./simple-demo",
|
||||
"./playground"
|
||||
],
|
||||
"scripts": {},
|
||||
"devDependencies": {
|
||||
"prettier": "^1.19.1"
|
||||
}
|
||||
}
|
||||
52
playground/package.json
Normal file
52
playground/package.json
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "playground",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server --hot --port 8080",
|
||||
"build": "webpack",
|
||||
"pub": "gh-pages -d dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "^3.23.1",
|
||||
"@hediet/std": "^0.6.0",
|
||||
"@hediet/semantic-json": "^0.3.11",
|
||||
"blueprintjs": "^0.0.8",
|
||||
"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",
|
||||
"messagepack": "^1.1.10",
|
||||
"base64-js": "^1.3.1",
|
||||
"lzma": "^2.3.2",
|
||||
"@hediet/monaco-editor-react": "^0.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gh-pages": "^2.2.0",
|
||||
"@types/html-webpack-plugin": "^3.2.2",
|
||||
"@types/webpack": "^4.41.6",
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/react": "^16.9.22",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"css-loader": "^3.4.2",
|
||||
"file-loader": "^5.1.0",
|
||||
"fork-ts-checker-webpack-plugin": "^4.0.4",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"monaco-editor-webpack-plugin": "^1.9.0",
|
||||
"raw-loader": "^4.0.0",
|
||||
"sass-loader": "^8.0.2",
|
||||
"style-loader": "^1.1.3",
|
||||
"ts-loader": "^6.2.1",
|
||||
"ts-node": "^8.6.2",
|
||||
"typescript": "^3.8.2",
|
||||
"webpack": "^4.41.6",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-dev-server": "^3.10.3",
|
||||
"@types/base64-js": "^1.3.0",
|
||||
"source-map-loader": "^1.0.1",
|
||||
"less-loader": "^6.2.0"
|
||||
}
|
||||
}
|
||||
49
playground/src/common/hotComponent.tsx
Normal file
49
playground/src/common/hotComponent.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import Module = require("module");
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { observable, runInAction } from "mobx";
|
||||
|
||||
type ReactComponent<P = any> =
|
||||
| React.ComponentClass<P>
|
||||
| React.FunctionComponent<P>;
|
||||
|
||||
const allComponents = new Map<string, { component: ReactComponent }>();
|
||||
|
||||
export function hotComponent(
|
||||
module: Module
|
||||
): <T extends ReactComponent>(Component: T) => T {
|
||||
return <T extends ReactComponent>(component: T): T => {
|
||||
const key = JSON.stringify({ id: module.id, name: component.name });
|
||||
|
||||
let result = allComponents.get(key);
|
||||
if (!result) {
|
||||
result = observable({ component: component });
|
||||
allComponents.set(key, result);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
runInAction(`Update Component ${component.name}`, () => {
|
||||
result!.component = component;
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
const m = module as {
|
||||
hot?: {
|
||||
accept: ((
|
||||
componentName: string,
|
||||
callback: () => void
|
||||
) => void) &
|
||||
((callback: () => void) => void);
|
||||
};
|
||||
};
|
||||
|
||||
if (m.hot) {
|
||||
m.hot.accept(() => {});
|
||||
}
|
||||
|
||||
return observer((props: any) => {
|
||||
const C = result!.component;
|
||||
return <C {...props} />;
|
||||
}) as any;
|
||||
};
|
||||
}
|
||||
53
playground/src/common/utils.ts
Normal file
53
playground/src/common/utils.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { observable } from "mobx";
|
||||
import { wait } from "@hediet/std/timer";
|
||||
|
||||
export function ref<T, TKey extends keyof T & string>(
|
||||
obj: T,
|
||||
key: TKey
|
||||
): Ref<T[TKey]> {
|
||||
return new Ref(
|
||||
() => obj[key],
|
||||
v => (obj[key] = v)
|
||||
);
|
||||
}
|
||||
|
||||
export interface IRef<T> {
|
||||
get: () => T;
|
||||
set: (value: T) => void;
|
||||
}
|
||||
|
||||
export class Ref<T> implements IRef<T> {
|
||||
constructor(
|
||||
public readonly get: () => T,
|
||||
public readonly set: (value: T) => void
|
||||
) {}
|
||||
|
||||
public map<TNew>(to: (t: T) => TNew, from: (tNew: TNew) => T): Ref<TNew> {
|
||||
return new Ref(
|
||||
() => to(this.get()),
|
||||
val => this.set(from(val))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Animator {
|
||||
public readonly dispose = Disposable.fn();
|
||||
|
||||
@observable public index: number = 0;
|
||||
|
||||
constructor(private readonly times: number[]) {
|
||||
if (times.length > 1) {
|
||||
this.run();
|
||||
}
|
||||
}
|
||||
|
||||
private async run() {
|
||||
let index = 0;
|
||||
while (true) {
|
||||
await wait(this.times[index]);
|
||||
index = (index + 1) % this.times.length;
|
||||
this.index = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
35
playground/src/components/App.tsx
Normal file
35
playground/src/components/App.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import React = require("react");
|
||||
import { hotComponent } from "../common/hotComponent";
|
||||
import { Model } from "../model/Model";
|
||||
import { MainView } from "./MainView";
|
||||
import { observable, runInAction } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
import "../visualizations";
|
||||
import { loadMonaco, getMonaco } from "@hediet/monaco-editor-react";
|
||||
|
||||
@hotComponent(module)
|
||||
@observer
|
||||
export class App extends React.Component {
|
||||
@observable model: Model | undefined;
|
||||
|
||||
constructor(props: {}) {
|
||||
super(props);
|
||||
this.init();
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!getMonaco()) {
|
||||
await loadMonaco();
|
||||
}
|
||||
runInAction(() => {
|
||||
this.model = new Model();
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.model) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
return <MainView model={this.model} />;
|
||||
}
|
||||
}
|
||||
25
playground/src/components/DemoView.tsx
Normal file
25
playground/src/components/DemoView.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import { hotComponent } from "../common/hotComponent";
|
||||
import { observer } from "mobx-react";
|
||||
import React = require("react");
|
||||
import { PlaygroundDocumentEditor } from "./PlaygroundDocumentEditor";
|
||||
import { observable } from "mobx";
|
||||
import { Model } from "../model/Model";
|
||||
|
||||
const demoData = require("../demo.editable.json");
|
||||
|
||||
@hotComponent(module)
|
||||
@observer
|
||||
export class DemoView extends React.Component<{ model: Model }> {
|
||||
render() {
|
||||
return (
|
||||
<div style={{ margin: 10 }}>
|
||||
<h1>Visualization Playground</h1>
|
||||
<PlaygroundDocumentEditor
|
||||
model={this.props.model}
|
||||
document={observable(demoData)}
|
||||
editableCaptions={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
25
playground/src/components/EditorView.tsx
Normal file
25
playground/src/components/EditorView.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React = require("react");
|
||||
import { observer } from "mobx-react";
|
||||
import { hotComponent } from "../common/hotComponent";
|
||||
import { PlaygroundDocumentEditor } from "./PlaygroundDocumentEditor";
|
||||
import { VSCodeBrigde } from "./VSCodeBrigde";
|
||||
import { Model } from "../model/Model";
|
||||
|
||||
@hotComponent(module)
|
||||
@observer
|
||||
export class EditorView extends React.Component<{ model: Model }> {
|
||||
private readonly vscodeBridge = new VSCodeBrigde();
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div style={{ margin: 10 }}>
|
||||
<h1>Visualization Playground Editor</h1>
|
||||
<PlaygroundDocumentEditor
|
||||
model={this.props.model}
|
||||
document={this.vscodeBridge.currentDocument}
|
||||
editableCaptions={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
107
playground/src/components/JsonEditor.tsx
Normal file
107
playground/src/components/JsonEditor.tsx
Normal file
@@ -0,0 +1,107 @@
|
||||
import * as React from "react";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { observable, autorun, reaction } from "mobx";
|
||||
import { IRef } from "../common/utils";
|
||||
import { Serializer, JsonSchemaGenerator } from "@hediet/semantic-json";
|
||||
import { MonacoEditor, getLoadedMonaco } from "@hediet/monaco-editor-react";
|
||||
import { modelCount } from "./modelCount";
|
||||
|
||||
@observer
|
||||
export class JsonEditor extends React.Component<{
|
||||
jsonSrc: IRef<string>;
|
||||
serializer: Serializer<any>;
|
||||
height?: "dynamic" | "fill";
|
||||
}> {
|
||||
private readonly editorModel = getLoadedMonaco().editor.createModel(
|
||||
this.props.jsonSrc.get(),
|
||||
"json",
|
||||
getLoadedMonaco().Uri.parse(
|
||||
`inmemory://inmemory/${modelCount.cur++}.main.json`
|
||||
)
|
||||
);
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
const s = new JsonSchemaGenerator();
|
||||
|
||||
getLoadedMonaco().languages.json.jsonDefaults.setDiagnosticsOptions({
|
||||
validate: true,
|
||||
schemas: [
|
||||
{
|
||||
uri: "https://visualization.hediet.de/schema/v0.1",
|
||||
fileMatch: [".main.json"],
|
||||
schema: s.getJsonSchemaWithDefinitions(
|
||||
this.props.serializer
|
||||
),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<MonacoEditor
|
||||
height={{ kind: this.props.height || "fill", maxHeight: 200 }}
|
||||
model={this.editorModel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private isUpdating = false;
|
||||
|
||||
@disposeOnUnmount _updateModel = reaction(
|
||||
() => this.props.jsonSrc.get(),
|
||||
val => {
|
||||
if (!this.isUpdating) {
|
||||
this.editorModel.setValue(val);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
componentDidMount() {
|
||||
this.editorModel.onDidChangeContent(() => {
|
||||
this.isUpdating = true;
|
||||
this.props.jsonSrc.set(this.editorModel.getValue());
|
||||
this.isUpdating = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class TypeScriptPreviewEditor extends React.Component<{
|
||||
src: string;
|
||||
}> {
|
||||
private readonly editorModel = getLoadedMonaco().editor.createModel(
|
||||
this.props.src,
|
||||
"typescript",
|
||||
getLoadedMonaco().Uri.parse(
|
||||
`inmemory://inmemory/${modelCount.cur++}.ts`
|
||||
)
|
||||
);
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
const s = new JsonSchemaGenerator();
|
||||
|
||||
getLoadedMonaco().languages.typescript.typescriptDefaults.setCompilerOptions(
|
||||
{
|
||||
noLib: true,
|
||||
}
|
||||
);
|
||||
getLoadedMonaco().languages.typescript.typescriptDefaults.setDiagnosticsOptions(
|
||||
{
|
||||
noSemanticValidation: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <MonacoEditor model={this.editorModel} />;
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.editorModel.setValue(this.props.src);
|
||||
}
|
||||
}
|
||||
28
playground/src/components/MainView.tsx
Normal file
28
playground/src/components/MainView.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React = require("react");
|
||||
import { observer } from "mobx-react";
|
||||
import { hotComponent } from "../common/hotComponent";
|
||||
import { Model } from "../model/Model";
|
||||
import { DemoView } from "./DemoView";
|
||||
import { getQueryParam } from "../model/QueryController";
|
||||
import { EditorView } from "./EditorView";
|
||||
import { VisualizationWithEditorView } from "./VisualizationWithEditorView";
|
||||
|
||||
@hotComponent(module)
|
||||
@observer
|
||||
export class MainView extends React.Component<{ model: Model }> {
|
||||
render() {
|
||||
const { model } = this.props;
|
||||
if (model.visualization) {
|
||||
return (
|
||||
<VisualizationWithEditorView
|
||||
model={model}
|
||||
visualizationModel={model.visualization}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (getQueryParam("editor") !== null) {
|
||||
return <EditorView model={model} />;
|
||||
}
|
||||
return <DemoView model={model} />;
|
||||
}
|
||||
}
|
||||
312
playground/src/components/PlaygroundDocumentEditor.tsx
Normal file
312
playground/src/components/PlaygroundDocumentEditor.tsx
Normal file
@@ -0,0 +1,312 @@
|
||||
import { hotComponent } from "../common/hotComponent";
|
||||
import * as React from "react";
|
||||
import { Model, VisualizationModel } from "../model/Model";
|
||||
import {
|
||||
VisualizationData,
|
||||
VisualizationId,
|
||||
Theme,
|
||||
VisualizationFactory,
|
||||
globalVisualizationFactory,
|
||||
VisualizationView,
|
||||
isVisualizationData,
|
||||
Visualization,
|
||||
asVisualizationId,
|
||||
} from "@hediet/visualization-core";
|
||||
import { observer } from "mobx-react";
|
||||
import { EditableText, Checkbox, Button } from "@blueprintjs/core";
|
||||
import { JsonEditor, TypeScriptPreviewEditor } from "./JsonEditor";
|
||||
import { ref } from "../common/utils";
|
||||
import { computed, observable, runInAction, autorun } from "mobx";
|
||||
import { TypeScriptTypeGenerator } from "@hediet/semantic-json";
|
||||
import { QueryBinding } from "../model/QueryController";
|
||||
import { JsonDataSource } from "../model/JsonDataSource";
|
||||
import { Select } from "./Select";
|
||||
|
||||
@hotComponent(module)
|
||||
@observer
|
||||
export class PlaygroundDocumentEditor extends React.Component<{
|
||||
document: PlaygroundDocument;
|
||||
model: Model;
|
||||
editableCaptions: boolean;
|
||||
}> {
|
||||
@observable showLightTheme = false;
|
||||
@observable showDarkTheme = true;
|
||||
@observable showTypeScriptTypes = false;
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
|
||||
new QueryBinding(
|
||||
"lightTheme",
|
||||
ref(this, "showLightTheme").map(
|
||||
val => (val ? ("1" as string) : undefined),
|
||||
val => val !== undefined
|
||||
)
|
||||
);
|
||||
new QueryBinding(
|
||||
"darkTheme",
|
||||
ref(this, "showDarkTheme").map(
|
||||
val => (val ? ("1" as string) : undefined),
|
||||
val => val !== undefined
|
||||
)
|
||||
);
|
||||
new QueryBinding(
|
||||
"tsTypes",
|
||||
ref(this, "showTypeScriptTypes").map(
|
||||
val => (val ? ("1" as string) : undefined),
|
||||
val => val !== undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { document, model } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: "flex" }}>
|
||||
<Checkbox
|
||||
style={{ marginRight: 16 }}
|
||||
label="Show Light Theme"
|
||||
checked={this.showLightTheme}
|
||||
onChange={e =>
|
||||
(this.showLightTheme = e.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
<Checkbox
|
||||
style={{ marginRight: 16 }}
|
||||
label="Show Dark Theme"
|
||||
checked={this.showDarkTheme}
|
||||
onChange={e =>
|
||||
(this.showDarkTheme = e.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
<Checkbox
|
||||
style={{ marginRight: 16 }}
|
||||
label="Show TypeScript Types"
|
||||
checked={this.showTypeScriptTypes}
|
||||
onChange={e =>
|
||||
(this.showTypeScriptTypes = e.currentTarget.checked)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{document.visualizations.map((v, idx) => (
|
||||
<VisualizationConfigView
|
||||
model={model}
|
||||
showLightTheme={this.showLightTheme}
|
||||
showDarkTheme={this.showDarkTheme}
|
||||
showTypeScriptTypes={this.showTypeScriptTypes}
|
||||
key={idx}
|
||||
config={v}
|
||||
editableCaptions={this.props.editableCaptions}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface PlaygroundDocument {
|
||||
visualizations: VisualizationInfo[];
|
||||
}
|
||||
|
||||
export interface VisualizationInfo {
|
||||
name: string;
|
||||
dataSrc: string;
|
||||
preferredVisualizationId: VisualizationId | undefined;
|
||||
}
|
||||
|
||||
type VisualizationDataResult =
|
||||
| {
|
||||
kind: "ok";
|
||||
value: VisualizationData;
|
||||
}
|
||||
| { kind: "error"; message: string };
|
||||
|
||||
function parseVisualizationData(json: string): VisualizationDataResult {
|
||||
try {
|
||||
const value = JSON.parse(json);
|
||||
if (isVisualizationData(value)) {
|
||||
return { kind: "ok", value };
|
||||
}
|
||||
return {
|
||||
kind: "error",
|
||||
message: "Data is not welformed visualization data.",
|
||||
};
|
||||
} catch (e) {
|
||||
return { kind: "error", message: "" + e };
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
class VisualizationConfigView extends React.Component<{
|
||||
config: VisualizationInfo;
|
||||
showLightTheme: boolean;
|
||||
showDarkTheme: boolean;
|
||||
showTypeScriptTypes: boolean;
|
||||
model: Model;
|
||||
editableCaptions: boolean;
|
||||
}> {
|
||||
@computed get data():
|
||||
| {
|
||||
kind: "ok";
|
||||
visualization: Visualization;
|
||||
typeScriptDeclarationSrc: string;
|
||||
availableVisualizations: Visualization[];
|
||||
}
|
||||
| {
|
||||
kind: "error";
|
||||
message: string;
|
||||
availableVisualizations: Visualization[];
|
||||
} {
|
||||
const r = parseVisualizationData(this.props.config.dataSrc);
|
||||
|
||||
if (r.kind === "error") {
|
||||
return Object.assign({ availableVisualizations: [] }, r);
|
||||
}
|
||||
|
||||
const visualizations = globalVisualizationFactory.getVisualizations(
|
||||
r.value,
|
||||
this.props.config.preferredVisualizationId
|
||||
);
|
||||
|
||||
if (visualizations.bestVisualization) {
|
||||
const tsTypeGenerator = new TypeScriptTypeGenerator();
|
||||
const typeSrc = tsTypeGenerator.getType(
|
||||
visualizations.bestVisualization.sourceVisualizer.serializer.asSerializer()
|
||||
);
|
||||
const decl = tsTypeGenerator.getDefinitionSource();
|
||||
return {
|
||||
kind: "ok",
|
||||
visualization: visualizations.bestVisualization,
|
||||
availableVisualizations: visualizations.allVisualizations,
|
||||
typeScriptDeclarationSrc: `type ExpectedType = ${typeSrc};\n\n${decl}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
kind: "error",
|
||||
message: "No Visualization Available",
|
||||
availableVisualizations: visualizations.allVisualizations,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
config,
|
||||
showDarkTheme,
|
||||
showLightTheme,
|
||||
showTypeScriptTypes,
|
||||
editableCaptions,
|
||||
} = this.props;
|
||||
|
||||
const themes = new Array<Theme>();
|
||||
if (showDarkTheme) {
|
||||
themes.push(Theme.dark);
|
||||
}
|
||||
if (showLightTheme) {
|
||||
themes.push(Theme.light);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: "flex", alignItems: "center" }}>
|
||||
<h2>
|
||||
<EditableText
|
||||
disabled={!editableCaptions}
|
||||
value={config.name}
|
||||
onChange={e => (config.name = e)}
|
||||
/>
|
||||
</h2>
|
||||
<div style={{ width: 32 }} />
|
||||
<Select<Visualization<any>>
|
||||
style={{ minWidth: 100 }}
|
||||
options={this.data.availableVisualizations}
|
||||
idSelector={v => v.id.toString()}
|
||||
labelSelector={v => v.name}
|
||||
selected={
|
||||
this.data.kind === "ok"
|
||||
? this.data.visualization
|
||||
: undefined
|
||||
}
|
||||
onSelect={e =>
|
||||
runInAction(() => {
|
||||
config.preferredVisualizationId = e.id;
|
||||
})
|
||||
}
|
||||
/>
|
||||
<div style={{ width: 8 }} />
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.props.model.visualization = new VisualizationModel(
|
||||
new JsonDataSource({
|
||||
json: config.dataSrc,
|
||||
prefVisId: config.preferredVisualizationId as any,
|
||||
})
|
||||
);
|
||||
}}
|
||||
>
|
||||
Open
|
||||
</Button>
|
||||
</div>
|
||||
<div style={{ display: "flex" }}>
|
||||
{themes.map((theme, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
style={{
|
||||
width: 500,
|
||||
height: 300,
|
||||
marginRight: 10,
|
||||
overflow: "auto",
|
||||
border: "1px solid black",
|
||||
}}
|
||||
>
|
||||
{this.data.kind === "ok" ? (
|
||||
<VisualizationView
|
||||
theme={theme}
|
||||
visualization={this.data.visualization}
|
||||
/>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div
|
||||
style={{
|
||||
width: 500,
|
||||
height: 300,
|
||||
marginRight: 10,
|
||||
border: "1px solid black",
|
||||
overflow: "visible",
|
||||
}}
|
||||
>
|
||||
<JsonEditor
|
||||
jsonSrc={ref(config, "dataSrc")}
|
||||
serializer={globalVisualizationFactory.getSerializer()}
|
||||
/>
|
||||
</div>
|
||||
{showTypeScriptTypes && (
|
||||
<div
|
||||
style={{
|
||||
width: 500,
|
||||
height: 300,
|
||||
marginRight: 10,
|
||||
border: "1px solid black",
|
||||
overflow: "visible",
|
||||
}}
|
||||
>
|
||||
<TypeScriptPreviewEditor
|
||||
src={
|
||||
this.data.kind === "ok"
|
||||
? this.data.typeScriptDeclarationSrc
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
49
playground/src/components/Select.tsx
Normal file
49
playground/src/components/Select.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import * as React from "react";
|
||||
import { HTMLSelect } from "@blueprintjs/core";
|
||||
|
||||
export class Select<T> extends React.Component<{
|
||||
options: T[];
|
||||
idSelector: (value: T) => string;
|
||||
labelSelector: (value: T) => string;
|
||||
selected: T | undefined;
|
||||
onSelect: (selected: T) => void;
|
||||
style: React.CSSProperties;
|
||||
}> {
|
||||
render() {
|
||||
const {
|
||||
idSelector,
|
||||
labelSelector,
|
||||
onSelect,
|
||||
options,
|
||||
selected,
|
||||
style,
|
||||
} = this.props;
|
||||
const ops = options.map(o => ({
|
||||
value: idSelector(o),
|
||||
label: labelSelector(o),
|
||||
}));
|
||||
let value: string;
|
||||
if (selected === undefined) {
|
||||
value = "undefined";
|
||||
ops.unshift({ value: "undefined", label: "(Nothing)" });
|
||||
} else {
|
||||
value = idSelector(selected);
|
||||
}
|
||||
|
||||
return (
|
||||
<HTMLSelect
|
||||
style={style}
|
||||
options={ops}
|
||||
value={value}
|
||||
onChange={e => {
|
||||
const selected = options.find(
|
||||
o => idSelector(o) === e.currentTarget.value
|
||||
);
|
||||
if (selected) {
|
||||
onSelect(selected);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
59
playground/src/components/VSCodeBrigde.tsx
Normal file
59
playground/src/components/VSCodeBrigde.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { observable, autorun, runInAction, toJS, reaction } from "mobx";
|
||||
import { PlaygroundDocument } from "./PlaygroundDocumentEditor";
|
||||
|
||||
export class VSCodeBrigde {
|
||||
@observable public currentDocument: PlaygroundDocument = {
|
||||
visualizations: [],
|
||||
};
|
||||
private ignoreChanges = false;
|
||||
|
||||
constructor() {
|
||||
window.addEventListener("message", e => {
|
||||
const msg = e.data as MessageFromHost;
|
||||
|
||||
if (msg.kind === "loadContent") {
|
||||
this.ignoreChanges = true;
|
||||
runInAction("Update data", () => {
|
||||
this.currentDocument = msg.content as PlaygroundDocument;
|
||||
});
|
||||
this.ignoreChanges = false;
|
||||
}
|
||||
});
|
||||
|
||||
reaction(
|
||||
() => toJS(this.currentDocument),
|
||||
newContent => {
|
||||
if (this.ignoreChanges) {
|
||||
return;
|
||||
}
|
||||
this.sendMessage({
|
||||
kind: "onChange",
|
||||
newContent,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
this.sendMessage({
|
||||
kind: "onInit",
|
||||
});
|
||||
}
|
||||
|
||||
private sendMessage(m: MesssageToHost) {
|
||||
window.parent.postMessage(m, "*");
|
||||
}
|
||||
}
|
||||
|
||||
type JsonValue = unknown;
|
||||
|
||||
type MessageFromHost = {
|
||||
kind: "loadContent";
|
||||
content: JsonValue;
|
||||
};
|
||||
|
||||
type MesssageToHost =
|
||||
| { kind: "onInit" }
|
||||
| { kind: "log"; message: string }
|
||||
| {
|
||||
kind: "onChange";
|
||||
newContent: JsonValue;
|
||||
};
|
||||
159
playground/src/components/VisualizationWithEditorView.tsx
Normal file
159
playground/src/components/VisualizationWithEditorView.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import * as React from "react";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { Model, VisualizationModel } from "../model/Model";
|
||||
import classnames = require("classnames");
|
||||
import {
|
||||
VisualizationView,
|
||||
Theme,
|
||||
Visualization,
|
||||
} from "@hediet/visualization-core";
|
||||
import { Button, HTMLSelect, ButtonGroup } from "@blueprintjs/core";
|
||||
import { observable, computed, runInAction } from "mobx";
|
||||
import { QueryBinding } from "../model/QueryController";
|
||||
import { ref } from "../common/utils";
|
||||
import { Select } from "./Select";
|
||||
|
||||
@observer
|
||||
export class VisualizationWithEditorView extends React.Component<{
|
||||
model: Model;
|
||||
visualizationModel: VisualizationModel;
|
||||
}> {
|
||||
@observable showEditor = true;
|
||||
@computed get theme(): Theme {
|
||||
if (this.themeId === "dark") {
|
||||
return Theme.dark;
|
||||
}
|
||||
return Theme.light;
|
||||
}
|
||||
|
||||
@observable themeId: string | undefined;
|
||||
|
||||
private readonly themeQueryBinding = new QueryBinding(
|
||||
"theme",
|
||||
ref(this, "themeId")
|
||||
);
|
||||
|
||||
render() {
|
||||
const { visualizationModel, model } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="component-GUI"
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
minHeight: 0,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="part-header"
|
||||
style={{ margin: 6, display: "flex" }}
|
||||
>
|
||||
<Button
|
||||
icon={"arrow-left"}
|
||||
onClick={() => {
|
||||
model.visualization = undefined;
|
||||
}}
|
||||
>
|
||||
Demos
|
||||
</Button>
|
||||
<div style={{ width: 6 }} />
|
||||
<Button
|
||||
active={this.showEditor}
|
||||
onClick={() => (this.showEditor = !this.showEditor)}
|
||||
>
|
||||
Show Editor
|
||||
</Button>
|
||||
<div style={{ width: 6 }} />
|
||||
<HTMLSelect
|
||||
options={[
|
||||
{ value: "light", label: "Light" },
|
||||
{ value: "dark", label: "Dark" },
|
||||
]}
|
||||
value={this.themeId}
|
||||
onChange={e => (this.themeId = e.currentTarget.value)}
|
||||
/>
|
||||
<div style={{ width: 6 }} />
|
||||
|
||||
<Select<Visualization<any>>
|
||||
style={{ minWidth: 100 }}
|
||||
options={
|
||||
(
|
||||
visualizationModel.visualizations || {
|
||||
allVisualizations: [],
|
||||
}
|
||||
).allVisualizations
|
||||
}
|
||||
idSelector={v => v.id.toString()}
|
||||
labelSelector={v => v.name}
|
||||
selected={
|
||||
visualizationModel.visualizations
|
||||
? visualizationModel.visualizations
|
||||
.bestVisualization
|
||||
: undefined
|
||||
}
|
||||
onSelect={e =>
|
||||
runInAction(() => {
|
||||
visualizationModel.dataSource.setPreferredVisualizationId(
|
||||
e.id
|
||||
);
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "row-reverse",
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
}}
|
||||
>
|
||||
{this.showEditor && (
|
||||
<div
|
||||
className="part-header"
|
||||
style={{
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{visualizationModel.dataSource.editorView}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="part-visualization"
|
||||
style={{
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
flex: 1,
|
||||
border: "1px solid rgba(128, 128, 128, 0.15)",
|
||||
}}
|
||||
>
|
||||
{this.renderVisualization()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderVisualization() {
|
||||
const { visualizationModel: model } = this.props;
|
||||
const visualizations = model.visualizations;
|
||||
|
||||
if (!visualizations) {
|
||||
return <div>No/Invalid Data</div>;
|
||||
}
|
||||
if (!visualizations.bestVisualization) {
|
||||
return <div>Data cannot be visualized</div>;
|
||||
}
|
||||
return (
|
||||
<VisualizationView
|
||||
theme={this.theme}
|
||||
visualization={visualizations.bestVisualization}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
1
playground/src/components/modelCount.ts
Normal file
1
playground/src/components/modelCount.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const modelCount = { cur: 0 };
|
||||
55
playground/src/demo.editable.json
Normal file
55
playground/src/demo.editable.json
Normal file
File diff suppressed because one or more lines are too long
9
playground/src/index.tsx
Normal file
9
playground/src/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import "./style.scss";
|
||||
import { App } from "./components/App";
|
||||
|
||||
const elem = document.createElement("div");
|
||||
elem.className = "react-root";
|
||||
document.body.append(elem);
|
||||
ReactDOM.render(<App />, elem);
|
||||
123
playground/src/model/JsonDataSource.tsx
Normal file
123
playground/src/model/JsonDataSource.tsx
Normal file
@@ -0,0 +1,123 @@
|
||||
import {
|
||||
VisualizationDataSource,
|
||||
VisualizationDataSourceState,
|
||||
} from "./VisualizationDataSource";
|
||||
import { observable, computed } from "mobx";
|
||||
import {
|
||||
VisualizationData,
|
||||
VisualizationId,
|
||||
globalVisualizationFactory,
|
||||
} from "@hediet/visualization-core";
|
||||
import * as React from "react";
|
||||
import { JsonEditor } from "../components/JsonEditor";
|
||||
import { ref, Animator, Ref } from "../common/utils";
|
||||
|
||||
type State = { json: string; prefVisId: string | undefined };
|
||||
|
||||
export class JsonDataSource implements VisualizationDataSource<State> {
|
||||
@observable jsonSrc: string = "";
|
||||
@observable preferredVisualizationId: VisualizationId | undefined;
|
||||
|
||||
readonly editorView = (<EditorView jsonSrc={ref(this, "jsonSrc")} />);
|
||||
|
||||
@observable timePassed = 0;
|
||||
|
||||
@computed
|
||||
get state(): State {
|
||||
return {
|
||||
json: this.jsonSrc,
|
||||
prefVisId: this.preferredVisualizationId as any,
|
||||
};
|
||||
}
|
||||
|
||||
public setState(state: State): void {
|
||||
this.jsonSrc = state.json;
|
||||
this.preferredVisualizationId = state.prefVisId as any;
|
||||
}
|
||||
|
||||
constructor(initialState?: State) {
|
||||
if (initialState) {
|
||||
this.setState(initialState);
|
||||
}
|
||||
}
|
||||
|
||||
public setPreferredVisualizationId(id: VisualizationId | undefined): void {
|
||||
this.preferredVisualizationId = id;
|
||||
}
|
||||
|
||||
private get normalizedData():
|
||||
| {
|
||||
kind: "ok";
|
||||
items: {
|
||||
data: VisualizationData;
|
||||
durationMs: number;
|
||||
preferredVisualizationId: VisualizationId | undefined;
|
||||
}[];
|
||||
}
|
||||
| { kind: "error"; error: string } {
|
||||
try {
|
||||
var value = JSON.parse(this.jsonSrc);
|
||||
} catch (e) {
|
||||
return {
|
||||
kind: "error",
|
||||
error: "" + e,
|
||||
};
|
||||
}
|
||||
|
||||
if (!Array.isArray(value)) {
|
||||
value = [value];
|
||||
}
|
||||
|
||||
if (value.length === 0) {
|
||||
return { kind: "error", error: "bla" };
|
||||
}
|
||||
|
||||
const items = value.map((v: any) => ({
|
||||
data: v,
|
||||
durationMs: Number(v["playground-durationMs"]) || 1500,
|
||||
preferredVisualizationId: v["playground-preferredVisualizationId"],
|
||||
}));
|
||||
|
||||
return { kind: "ok", items };
|
||||
}
|
||||
|
||||
@computed.struct get times(): number[] {
|
||||
if (this.normalizedData.kind === "ok") {
|
||||
return this.normalizedData.items.map(i => i.durationMs);
|
||||
}
|
||||
return [1000];
|
||||
}
|
||||
|
||||
private lastAnimator: Animator | undefined;
|
||||
|
||||
@computed get animator(): Animator {
|
||||
if (this.lastAnimator) {
|
||||
this.lastAnimator.dispose();
|
||||
}
|
||||
return (this.lastAnimator = new Animator(this.times));
|
||||
}
|
||||
|
||||
@computed
|
||||
get data(): VisualizationDataSourceState {
|
||||
if (this.normalizedData.kind === "error") {
|
||||
return this.normalizedData;
|
||||
}
|
||||
|
||||
return {
|
||||
kind: "ok",
|
||||
value: this.normalizedData.items[this.animator.index].data,
|
||||
preferredVisualizationId: this.preferredVisualizationId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class EditorView extends React.Component<{ jsonSrc: Ref<string> }> {
|
||||
render() {
|
||||
return (
|
||||
<JsonEditor
|
||||
serializer={globalVisualizationFactory.getSerializer()}
|
||||
jsonSrc={this.props.jsonSrc}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
81
playground/src/model/Model.ts
Normal file
81
playground/src/model/Model.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { observable, action, computed } from "mobx";
|
||||
import {
|
||||
Visualization,
|
||||
VisualizationId,
|
||||
globalVisualizationFactory,
|
||||
} from "@hediet/visualization-core";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { QueryBinding } from "./QueryController";
|
||||
import { VisualizationDataSource } from "./VisualizationDataSource";
|
||||
import { JsonDataSource } from "./JsonDataSource";
|
||||
import { encodeData, decodeData } from "./lzmaCompressor";
|
||||
import { MonacoBridge } from "./MonacoBridge";
|
||||
import { Ref } from "../common/utils";
|
||||
|
||||
export class Model {
|
||||
public readonly dispose = Disposable.fn();
|
||||
|
||||
@observable
|
||||
public theme: "dark" | "light" = "light";
|
||||
|
||||
private readonly monacoBridge = new MonacoBridge(this);
|
||||
|
||||
@observable
|
||||
public visualization: VisualizationModel | undefined = undefined;
|
||||
|
||||
private readonly q = new QueryBinding(
|
||||
"state",
|
||||
new Ref(
|
||||
() =>
|
||||
this.visualization
|
||||
? this.visualization.dataSource.state
|
||||
: undefined,
|
||||
newState => {
|
||||
if (newState !== undefined) {
|
||||
if (!this.visualization) {
|
||||
this.visualization = new VisualizationModel(
|
||||
new JsonDataSource(newState as any)
|
||||
);
|
||||
} else {
|
||||
this.visualization.dataSource.setState(newState);
|
||||
}
|
||||
} else {
|
||||
this.visualization = undefined;
|
||||
}
|
||||
}
|
||||
).map(encodeData, decodeData)
|
||||
);
|
||||
|
||||
constructor() {
|
||||
const url = new URL(window.location.href);
|
||||
|
||||
const theme = url.searchParams.get("theme");
|
||||
if (theme && theme === "dark") {
|
||||
this.theme = "dark";
|
||||
} else {
|
||||
this.theme = "light";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class VisualizationModel {
|
||||
constructor(public readonly dataSource: VisualizationDataSource<unknown>) {}
|
||||
|
||||
@computed get visualizations():
|
||||
| {
|
||||
bestVisualization: Visualization | undefined;
|
||||
allVisualizations: Visualization[];
|
||||
}
|
||||
| undefined {
|
||||
const data = this.dataSource.data;
|
||||
if (data.kind === "ok") {
|
||||
const vis = globalVisualizationFactory.getVisualizations(
|
||||
data.value,
|
||||
data.preferredVisualizationId
|
||||
);
|
||||
return vis;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
playground/src/model/MonacoBridge.ts
Normal file
19
playground/src/model/MonacoBridge.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Model } from "./Model";
|
||||
import { Disposable } from "@hediet/std/disposable";
|
||||
import { autorun } from "mobx";
|
||||
import { getLoadedMonaco } from "@hediet/monaco-editor-react";
|
||||
|
||||
export class MonacoBridge {
|
||||
public readonly dispose = Disposable.fn();
|
||||
|
||||
constructor(private readonly model: Model) {
|
||||
return;
|
||||
this.dispose.track({
|
||||
dispose: autorun(() => {
|
||||
getLoadedMonaco().editor.setTheme(
|
||||
model.theme === "light" ? "vs-light" : "vs-dark"
|
||||
);
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
38
playground/src/model/QueryController.ts
Normal file
38
playground/src/model/QueryController.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { autorun } from "mobx";
|
||||
import { IRef } from "../common/utils";
|
||||
|
||||
export class QueryBinding {
|
||||
constructor(
|
||||
private readonly queryParamName: string,
|
||||
private readonly value: IRef<string | undefined>
|
||||
) {
|
||||
this.loadFromQuery();
|
||||
autorun(() => {
|
||||
this.updateQuery();
|
||||
});
|
||||
}
|
||||
|
||||
private loadFromQuery() {
|
||||
const val = getQueryParam(this.queryParamName);
|
||||
if (val) {
|
||||
this.value.set(val);
|
||||
}
|
||||
}
|
||||
|
||||
private updateQuery() {
|
||||
const url = new URL(window.location.href);
|
||||
const val = this.value.get();
|
||||
if (val !== undefined) {
|
||||
url.searchParams.set(this.queryParamName, val);
|
||||
} else {
|
||||
url.searchParams.delete(this.queryParamName);
|
||||
}
|
||||
history.replaceState(null, document.title, url.toString());
|
||||
}
|
||||
}
|
||||
|
||||
export function getQueryParam(queryParamName: string): string | null {
|
||||
const url = new URL(window.location.href);
|
||||
const val = url.searchParams.get(queryParamName);
|
||||
return val;
|
||||
}
|
||||
22
playground/src/model/VisualizationDataSource.ts
Normal file
22
playground/src/model/VisualizationDataSource.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { VisualizationData, VisualizationId } from "@hediet/visualization-core";
|
||||
|
||||
export interface VisualizationDataSource</* must be json */ TState> {
|
||||
readonly editorView: React.ReactElement;
|
||||
|
||||
setState(state: TState): void;
|
||||
setPreferredVisualizationId(id: VisualizationId | undefined): void;
|
||||
|
||||
// is observable
|
||||
readonly state: TState;
|
||||
|
||||
// is observable
|
||||
readonly data: VisualizationDataSourceState;
|
||||
}
|
||||
|
||||
export type VisualizationDataSourceState =
|
||||
| {
|
||||
kind: "ok";
|
||||
value: VisualizationData;
|
||||
preferredVisualizationId: VisualizationId | undefined;
|
||||
}
|
||||
| { kind: "error"; error: string };
|
||||
36
playground/src/model/lzmaCompressor.ts
Normal file
36
playground/src/model/lzmaCompressor.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import * as lzma from "lzma/src/lzma_worker";
|
||||
import * as msgpack from "messagepack";
|
||||
import * as base64 from "base64-js";
|
||||
|
||||
export function encodeData(json: unknown | undefined): string | undefined {
|
||||
if (json === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
// normalize undefined
|
||||
json = JSON.parse(JSON.stringify(json));
|
||||
const data = msgpack.encode(json);
|
||||
const compressed = lzma.LZMA.compress(data, 9);
|
||||
const compressedStr = base64.fromByteArray(compressed);
|
||||
|
||||
return compressedStr
|
||||
.replace(/\+/g, "-") // Convert '+' to '-'
|
||||
.replace(/\//g, "_") // Convert '/' to '_'
|
||||
.replace(/=+$/, ""); // Remove ending '='
|
||||
}
|
||||
|
||||
export function decodeData(
|
||||
compressedStr: string | undefined
|
||||
): unknown | undefined {
|
||||
if (compressedStr === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
compressedStr += Array(5 - (compressedStr.length % 4)).join("=");
|
||||
compressedStr = compressedStr
|
||||
.replace(/\-/g, "+") // Convert '-' to '+'
|
||||
.replace(/\_/g, "/"); // Convert '_' to '/'
|
||||
|
||||
const compressed2 = base64.toByteArray(compressedStr);
|
||||
const decompressed = lzma.LZMA.decompress(compressed2);
|
||||
const origData = msgpack.decode(new Uint8Array(decompressed));
|
||||
return origData;
|
||||
}
|
||||
9
playground/src/model/types.d.ts
vendored
Normal file
9
playground/src/model/types.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
declare module "lzma/src/lzma_worker" {
|
||||
const x: any;
|
||||
export = x;
|
||||
}
|
||||
|
||||
declare module "base64-js" {
|
||||
const x: any;
|
||||
export = x;
|
||||
}
|
||||
70
playground/src/style.scss
Normal file
70
playground/src/style.scss
Normal file
@@ -0,0 +1,70 @@
|
||||
@import "~normalize.css";
|
||||
@import "~@blueprintjs/core/lib/css/blueprint.css";
|
||||
@import "~@blueprintjs/icons/lib/css/blueprint-icons.css";
|
||||
@import "~@hediet/visualization-bundle/style.scss";
|
||||
|
||||
html,
|
||||
body,
|
||||
.react-root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: 0px;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
//background-color: var(--vscode-editor-background);
|
||||
}
|
||||
|
||||
.component-GUI {
|
||||
& > .part-Header {
|
||||
//padding: 6px 20px 6px 0;
|
||||
//background-color: var(--vscode-sideBar-background);
|
||||
|
||||
// border-top: solid 1px var(--vscode-button-background);
|
||||
// rgba(128, 128, 128, 0.15)
|
||||
}
|
||||
}
|
||||
|
||||
.bp3-input,
|
||||
.bp3-button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.bp3-icon {
|
||||
svg {
|
||||
color: lighten(black, 20);
|
||||
width: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.component-DynamicHeightJsonEditor {
|
||||
.part-editor {
|
||||
.current-line {
|
||||
border: none !important;
|
||||
}
|
||||
.selectionHighlight {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bp3-transition-container {
|
||||
z-index: 100000;
|
||||
}
|
||||
|
||||
@import "./vscode-dark.scss";
|
||||
@import "./vscode-light.scss";
|
||||
|
||||
.visualization {
|
||||
background: var(--vscode-editor-background);
|
||||
}
|
||||
|
||||
.themeable {
|
||||
--visualizer-simple-text-color: var(--vscode-editor-foreground);
|
||||
}
|
||||
21
playground/src/visualizations.ts
Normal file
21
playground/src/visualizations.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
//// Uncomment the visualization that you work on to increase webpack build speed!
|
||||
|
||||
// import "@hediet/visualization-bundle/dist/visualizers/ast-visualizer"; /*
|
||||
// import "@hediet/visualization-bundle/dist/visualizers/graph/dot-graphviz-visualizer" /*
|
||||
// import "@hediet/visualization-bundle/dist/visualizers/graph/graph-graphviz-visualizer" /*
|
||||
// import "@hediet/visualization-bundle/dist/visualizers/graph/graph-visjs-visualizer" /*
|
||||
// import "@hediet/visualization-bundle/dist/visualizers/grid-visualizer" /*
|
||||
// import "@hediet/visualization-bundle/dist/visualizers/image-visualizer"; /*
|
||||
// import "@hediet/visualization-bundle/dist/visualizers/monaco-text-visualizer" /*
|
||||
// import "@hediet/visualization-bundle/dist/visualizers/perspective-table-visualizer" /*
|
||||
// import "@hediet/visualization-bundle/dist/visualizers/plotly-visualizer" /*
|
||||
// import "@hediet/visualization-bundle/dist/visualizers/simple-text-visualizer"; /*
|
||||
// import "@hediet/visualization-bundle/dist/visualizers/source-visualizer" /*
|
||||
// import "@hediet/visualization-bundle/dist/visualizers/svg-visualizer" /*
|
||||
// import "@hediet/visualization-bundle/dist/visualizers/tree-visualizer"; /*
|
||||
|
||||
// This bundles the monaco editor. Uncomment it to load monaco dynamically.
|
||||
// Dynamic loading increases webpack build speed significantly.
|
||||
import "monaco-editor";
|
||||
|
||||
import "@hediet/visualization-bundle"; // */
|
||||
344
playground/src/vscode-dark.scss
Normal file
344
playground/src/vscode-dark.scss
Normal file
@@ -0,0 +1,344 @@
|
||||
.theme-dark {
|
||||
--background-color: #263238;
|
||||
--color: #eeffff;
|
||||
--font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI",
|
||||
HelveticaNeue-Light, Ubuntu, "Droid Sans", sans-serif;
|
||||
--font-size: 13px;
|
||||
--font-weight: normal;
|
||||
--link-active-color: #eeffff;
|
||||
--link-color: #80cbc4;
|
||||
--vscode-activityBar-background: #192227;
|
||||
--vscode-activityBar-border: rgba(0, 0, 0, 0.38);
|
||||
--vscode-activityBar-dropBackground: rgba(255, 255, 255, 0.12);
|
||||
--vscode-activityBar-foreground: #eeffff;
|
||||
--vscode-activityBar-inactiveForeground: rgba(238, 255, 255, 0.6);
|
||||
--vscode-activityBarBadge-background: #80cbc4;
|
||||
--vscode-activityBarBadge-foreground: #000000;
|
||||
--vscode-badge-background: rgba(0, 0, 0, 0.19);
|
||||
--vscode-badge-foreground: #546e7a;
|
||||
--vscode-breadcrumb-activeSelectionForeground: #80cbc4;
|
||||
--vscode-breadcrumb-background: #263238;
|
||||
--vscode-breadcrumb-focusForeground: #eeffff;
|
||||
--vscode-breadcrumb-foreground: #5f7a87;
|
||||
--vscode-breadcrumbPicker-background: #192227;
|
||||
--vscode-button-background: rgba(128, 203, 196, 0.13);
|
||||
--vscode-button-foreground: #ffffff;
|
||||
--vscode-button-hoverBackground: rgba(175, 222, 218, 0.13);
|
||||
--vscode-debugExceptionWidget-background: #420b0d;
|
||||
--vscode-debugExceptionWidget-border: #a31515;
|
||||
--vscode-debugToolBar-background: #263238;
|
||||
--vscode-descriptionForeground: rgba(204, 204, 204, 0.7);
|
||||
--vscode-diffEditor-insertedTextBackground: rgba(195, 232, 141, 0.08);
|
||||
--vscode-diffEditor-removedTextBackground: rgba(255, 83, 112, 0.13);
|
||||
--vscode-dropdown-background: #263238;
|
||||
--vscode-dropdown-border: rgba(255, 255, 255, 0.06);
|
||||
--vscode-dropdown-foreground: #f0f0f0;
|
||||
--vscode-editor-background: #263238;
|
||||
--vscode-editor-findMatchBackground: #515c6a;
|
||||
--vscode-editor-findMatchHighlightBackground: rgba(234, 92, 0, 0.33);
|
||||
--vscode-editor-findRangeHighlightBackground: rgba(58, 61, 65, 0.4);
|
||||
--vscode-editor-focusedStackFrameHighlightBackground: rgba(
|
||||
122,
|
||||
189,
|
||||
122,
|
||||
0.3
|
||||
);
|
||||
--vscode-editor-font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC",
|
||||
"Segoe UI", HelveticaNeue-Light, Ubuntu, "Droid Sans", sans-serif;
|
||||
--vscode-editor-font-size: 13px;
|
||||
--vscode-editor-font-weight: normal;
|
||||
--vscode-editor-foreground: #eeffff;
|
||||
--vscode-editor-hoverHighlightBackground: rgba(38, 79, 120, 0.25);
|
||||
--vscode-editor-inactiveSelectionBackground: rgba(128, 203, 196, 0.06);
|
||||
--vscode-editor-lineHighlightBackground: rgba(0, 0, 0, 0.31);
|
||||
--vscode-editor-lineHighlightBorder: #282828;
|
||||
--vscode-editor-rangeHighlightBackground: rgba(255, 255, 255, 0.04);
|
||||
--vscode-editor-selectionBackground: rgba(128, 203, 196, 0.13);
|
||||
--vscode-editor-selectionHighlightBackground: rgba(255, 204, 0, 0.13);
|
||||
--vscode-editor-snippetFinalTabstopHighlightBorder: #525252;
|
||||
--vscode-editor-snippetTabstopHighlightBackground: rgba(124, 124, 124, 0.3);
|
||||
--vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 0, 0.2);
|
||||
--vscode-editor-wordHighlightBackground: rgba(87, 87, 87, 0.72);
|
||||
--vscode-editor-wordHighlightStrongBackground: rgba(0, 73, 114, 0.72);
|
||||
--vscode-editorActiveLineNumber-foreground: #c6c6c6;
|
||||
--vscode-editorBracketMatch-background: #263238;
|
||||
--vscode-editorBracketMatch-border: rgba(255, 204, 0, 0.31);
|
||||
--vscode-editorCodeLens-foreground: #999999;
|
||||
--vscode-editorCursor-foreground: #ffcc00;
|
||||
--vscode-editorError-foreground: rgba(255, 83, 112, 0.44);
|
||||
--vscode-editorGroup-border: rgba(0, 0, 0, 0.19);
|
||||
--vscode-editorGroup-dropBackground: rgba(83, 89, 93, 0.5);
|
||||
--vscode-editorGroupHeader-noTabsBackground: #263238;
|
||||
--vscode-editorGroupHeader-tabsBackground: #263238;
|
||||
--vscode-editorGutter-addedBackground: rgba(195, 232, 141, 0.38);
|
||||
--vscode-editorGutter-background: #263238;
|
||||
--vscode-editorGutter-commentRangeForeground: #c5c5c5;
|
||||
--vscode-editorGutter-deletedBackground: rgba(255, 83, 112, 0.38);
|
||||
--vscode-editorGutter-modifiedBackground: rgba(130, 170, 255, 0.38);
|
||||
--vscode-editorHint-foreground: rgba(238, 238, 238, 0.7);
|
||||
--vscode-editorHoverWidget-background: #263238;
|
||||
--vscode-editorHoverWidget-border: rgba(255, 255, 255, 0.06);
|
||||
--vscode-editorIndentGuide-activeBackground: #37474f;
|
||||
--vscode-editorIndentGuide-background: rgba(55, 71, 79, 0.44);
|
||||
--vscode-editorInfo-foreground: #008000;
|
||||
--vscode-editorLineNumber-activeForeground: #5f7a87;
|
||||
--vscode-editorLineNumber-foreground: #37474f;
|
||||
--vscode-editorLink-activeForeground: #eeffff;
|
||||
--vscode-editorMarkerNavigation-background: rgba(238, 255, 255, 0.02);
|
||||
--vscode-editorMarkerNavigationError-background: rgba(255, 83, 112, 0.44);
|
||||
--vscode-editorMarkerNavigationInfo-background: #008000;
|
||||
--vscode-editorMarkerNavigationWarning-background: rgba(
|
||||
195,
|
||||
232,
|
||||
141,
|
||||
0.44
|
||||
);
|
||||
--vscode-editorOverviewRuler-addedForeground: rgba(0, 122, 204, 0.6);
|
||||
--vscode-editorOverviewRuler-border: #263238;
|
||||
--vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0;
|
||||
--vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, 0.4);
|
||||
--vscode-editorOverviewRuler-currentContentForeground: rgba(
|
||||
64,
|
||||
200,
|
||||
174,
|
||||
0.5
|
||||
);
|
||||
--vscode-editorOverviewRuler-deletedForeground: rgba(0, 122, 204, 0.6);
|
||||
--vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, 0.7);
|
||||
--vscode-editorOverviewRuler-findMatchForeground: rgba(246, 185, 77, 0.7);
|
||||
--vscode-editorOverviewRuler-incomingContentForeground: rgba(
|
||||
64,
|
||||
166,
|
||||
255,
|
||||
0.5
|
||||
);
|
||||
--vscode-editorOverviewRuler-infoForeground: rgba(18, 18, 136, 0.7);
|
||||
--vscode-editorOverviewRuler-modifiedForeground: rgba(0, 122, 204, 0.6);
|
||||
--vscode-editorOverviewRuler-rangeHighlightForeground: rgba(
|
||||
0,
|
||||
122,
|
||||
204,
|
||||
0.6
|
||||
);
|
||||
--vscode-editorOverviewRuler-selectionHighlightForeground: rgba(
|
||||
160,
|
||||
160,
|
||||
160,
|
||||
0.8
|
||||
);
|
||||
--vscode-editorOverviewRuler-warningForeground: rgba(18, 136, 18, 0.7);
|
||||
--vscode-editorOverviewRuler-wordHighlightForeground: rgba(
|
||||
160,
|
||||
160,
|
||||
160,
|
||||
0.8
|
||||
);
|
||||
--vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(
|
||||
192,
|
||||
160,
|
||||
192,
|
||||
0.8
|
||||
);
|
||||
--vscode-editorPane-background: #263238;
|
||||
--vscode-editorRuler-foreground: #37474f;
|
||||
--vscode-editorSuggestWidget-background: #263238;
|
||||
--vscode-editorSuggestWidget-border: rgba(255, 255, 255, 0.06);
|
||||
--vscode-editorSuggestWidget-foreground: #eeffff;
|
||||
--vscode-editorSuggestWidget-highlightForeground: #80cbc4;
|
||||
--vscode-editorSuggestWidget-selectedBackground: rgba(0, 0, 0, 0.31);
|
||||
--vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.67);
|
||||
--vscode-editorWarning-foreground: rgba(195, 232, 141, 0.44);
|
||||
--vscode-editorWhitespace-foreground: rgba(238, 255, 255, 0.25);
|
||||
--vscode-editorWidget-background: #192227;
|
||||
--vscode-editorWidget-border: #ff0000;
|
||||
--vscode-editorWidget-resizeBorder: #80cbc4;
|
||||
--vscode-errorForeground: #f48771;
|
||||
--vscode-extensionButton-prominentBackground: rgba(195, 232, 141, 0.56);
|
||||
--vscode-extensionButton-prominentForeground: #ffffff;
|
||||
--vscode-extensionButton-prominentHoverBackground: #c3e88d;
|
||||
--vscode-focusBorder: rgba(255, 255, 255, 0);
|
||||
--vscode-foreground: #cccccc;
|
||||
--vscode-gitDecoration-addedResourceForeground: #81b88b;
|
||||
--vscode-gitDecoration-conflictingResourceForeground: rgba(
|
||||
255,
|
||||
203,
|
||||
107,
|
||||
0.56
|
||||
);
|
||||
--vscode-gitDecoration-deletedResourceForeground: rgba(255, 83, 112, 0.56);
|
||||
--vscode-gitDecoration-ignoredResourceForeground: rgba(95, 122, 135, 0.56);
|
||||
--vscode-gitDecoration-modifiedResourceForeground: rgba(
|
||||
130,
|
||||
170,
|
||||
255,
|
||||
0.56
|
||||
);
|
||||
--vscode-gitDecoration-submoduleResourceForeground: #8db9e2;
|
||||
--vscode-gitDecoration-untrackedResourceForeground: rgba(
|
||||
195,
|
||||
232,
|
||||
141,
|
||||
0.56
|
||||
);
|
||||
--vscode-gitlens-gutterBackgroundColor: rgba(255, 255, 255, 0.07);
|
||||
--vscode-gitlens-gutterForegroundColor: #bebebe;
|
||||
--vscode-gitlens-gutterUncommittedForegroundColor: rgba(0, 188, 242, 0.6);
|
||||
--vscode-gitlens-lineHighlightBackgroundColor: rgba(0, 188, 242, 0.2);
|
||||
--vscode-gitlens-lineHighlightOverviewRulerColor: rgba(0, 188, 242, 0.6);
|
||||
--vscode-gitlens-trailingLineBackgroundColor: rgba(0, 0, 0, 0);
|
||||
--vscode-gitlens-trailingLineForegroundColor: rgba(153, 153, 153, 0.35);
|
||||
--vscode-input-background: #303c41;
|
||||
--vscode-input-border: rgba(255, 255, 255, 0.06);
|
||||
--vscode-input-foreground: #eeffff;
|
||||
--vscode-input-placeholderForeground: rgba(238, 255, 255, 0.38);
|
||||
--vscode-inputOption-activeBorder: #007acc;
|
||||
--vscode-inputValidation-errorBackground: #5a1d1d;
|
||||
--vscode-inputValidation-errorBorder: rgba(255, 83, 112, 0.31);
|
||||
--vscode-inputValidation-infoBackground: #063b49;
|
||||
--vscode-inputValidation-infoBorder: rgba(130, 170, 255, 0.31);
|
||||
--vscode-inputValidation-warningBackground: #352a05;
|
||||
--vscode-inputValidation-warningBorder: rgba(255, 203, 107, 0.31);
|
||||
--vscode-list-activeSelectionBackground: #192227;
|
||||
--vscode-list-activeSelectionForeground: #80cbc4;
|
||||
--vscode-list-dropBackground: rgba(238, 255, 255, 0.13);
|
||||
--vscode-list-errorForeground: #f88070;
|
||||
--vscode-list-focusBackground: rgba(238, 255, 255, 0.13);
|
||||
--vscode-list-focusForeground: #eeffff;
|
||||
--vscode-list-highlightForeground: #80cbc4;
|
||||
--vscode-list-hoverBackground: #192227;
|
||||
--vscode-list-hoverForeground: #ffffff;
|
||||
--vscode-list-inactiveFocusBackground: #313135;
|
||||
--vscode-list-inactiveSelectionBackground: rgba(0, 0, 0, 0.19);
|
||||
--vscode-list-inactiveSelectionForeground: #80cbc4;
|
||||
--vscode-list-invalidItemForeground: #b89500;
|
||||
--vscode-list-warningForeground: #4d9e4d;
|
||||
--vscode-menu-background: #263238;
|
||||
--vscode-menu-foreground: #eeffff;
|
||||
--vscode-menu-selectionBackground: rgba(0, 0, 0, 0.31);
|
||||
--vscode-menu-selectionBorder: rgba(0, 0, 0, 0.19);
|
||||
--vscode-menu-selectionForeground: #80cbc4;
|
||||
--vscode-menu-separatorBackground: #eeffff;
|
||||
--vscode-menubar-selectionBackground: rgba(0, 0, 0, 0.19);
|
||||
--vscode-menubar-selectionBorder: rgba(0, 0, 0, 0.19);
|
||||
--vscode-menubar-selectionForeground: #80cbc4;
|
||||
--vscode-merge-commonContentBackground: rgba(96, 96, 96, 0.16);
|
||||
--vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 0.4);
|
||||
--vscode-merge-currentContentBackground: rgba(64, 200, 174, 0.2);
|
||||
--vscode-merge-currentHeaderBackground: rgba(64, 200, 174, 0.5);
|
||||
--vscode-merge-incomingContentBackground: rgba(64, 166, 255, 0.2);
|
||||
--vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, 0.5);
|
||||
--vscode-notificationCenterHeader-background: #314149;
|
||||
--vscode-notificationLink-foreground: #80cbc4;
|
||||
--vscode-notifications-background: #263238;
|
||||
--vscode-notifications-border: #314149;
|
||||
--vscode-notifications-foreground: #eeffff;
|
||||
--vscode-panel-background: #192227;
|
||||
--vscode-panel-border: rgba(0, 0, 0, 0.38);
|
||||
--vscode-panel-dropBackground: #eeffff;
|
||||
--vscode-panelTitle-activeBorder: #80cbc4;
|
||||
--vscode-panelTitle-activeForeground: #ffffff;
|
||||
--vscode-panelTitle-inactiveForeground: #eeffff;
|
||||
--vscode-peekView-border: rgba(0, 0, 0, 0.19);
|
||||
--vscode-peekViewEditor-background: rgba(238, 255, 255, 0.02);
|
||||
--vscode-peekViewEditor-matchHighlightBackground: rgba(128, 203, 196, 0.13);
|
||||
--vscode-peekViewEditorGutter-background: rgba(238, 255, 255, 0.02);
|
||||
--vscode-peekViewResult-background: rgba(238, 255, 255, 0.02);
|
||||
--vscode-peekViewResult-fileForeground: #ffffff;
|
||||
--vscode-peekViewResult-lineForeground: #bbbbbb;
|
||||
--vscode-peekViewResult-matchHighlightBackground: rgba(128, 203, 196, 0.13);
|
||||
--vscode-peekViewResult-selectionBackground: rgba(95, 122, 135, 0.44);
|
||||
--vscode-peekViewResult-selectionForeground: #ffffff;
|
||||
--vscode-peekViewTitle-background: rgba(238, 255, 255, 0.02);
|
||||
--vscode-peekViewTitleDescription-foreground: rgba(238, 255, 255, 0.38);
|
||||
--vscode-peekViewTitleLabel-foreground: #ffffff;
|
||||
--vscode-pickerGroup-border: #3f3f46;
|
||||
--vscode-pickerGroup-foreground: #80cbc4;
|
||||
--vscode-progressBar-background: #80cbc4;
|
||||
--vscode-scrollbar-shadow: rgba(38, 50, 56, 0);
|
||||
--vscode-scrollbarSlider-activeBackground: #80cbc4;
|
||||
--vscode-scrollbarSlider-background: rgba(238, 255, 255, 0.13);
|
||||
--vscode-scrollbarSlider-hoverBackground: rgba(238, 255, 255, 0.06);
|
||||
--vscode-selection-background: #eeffff;
|
||||
--vscode-settings-checkboxBackground: #192227;
|
||||
--vscode-settings-checkboxBorder: rgba(255, 255, 255, 0.06);
|
||||
--vscode-settings-checkboxForeground: #eeffff;
|
||||
--vscode-settings-dropdownBackground: #192227;
|
||||
--vscode-settings-dropdownBorder: rgba(255, 255, 255, 0.06);
|
||||
--vscode-settings-dropdownForeground: #eeffff;
|
||||
--vscode-settings-dropdownListBorder: #ff0000;
|
||||
--vscode-settings-headerForeground: #80cbc4;
|
||||
--vscode-settings-modifiedItemIndicator: #80cbc4;
|
||||
--vscode-settings-numberInputBackground: #192227;
|
||||
--vscode-settings-numberInputBorder: rgba(255, 255, 255, 0.06);
|
||||
--vscode-settings-numberInputForeground: #eeffff;
|
||||
--vscode-settings-textInputBackground: #192227;
|
||||
--vscode-settings-textInputBorder: rgba(255, 255, 255, 0.06);
|
||||
--vscode-settings-textInputForeground: #eeffff;
|
||||
--vscode-sideBar-background: #192227;
|
||||
--vscode-sideBar-border: rgba(0, 0, 0, 0.38);
|
||||
--vscode-sideBar-dropBackground: rgba(255, 255, 255, 0.12);
|
||||
--vscode-sideBar-foreground: #5f7a87;
|
||||
--vscode-sideBarSectionHeader-background: #192227;
|
||||
--vscode-sideBarSectionHeader-border: rgba(0, 0, 0, 0.38);
|
||||
--vscode-sideBarSectionHeader-foreground: #5f7a87;
|
||||
--vscode-sideBarTitle-foreground: #eeffff;
|
||||
--vscode-statusBar-background: #192227;
|
||||
--vscode-statusBar-border: rgba(0, 0, 0, 0.38);
|
||||
--vscode-statusBar-debuggingBackground: #c792ea;
|
||||
--vscode-statusBar-debuggingBorder: rgba(0, 0, 0, 0.38);
|
||||
--vscode-statusBar-debuggingForeground: #ffffff;
|
||||
--vscode-statusBar-foreground: #546e7a;
|
||||
--vscode-statusBar-noFolderBackground: #263238;
|
||||
--vscode-statusBar-noFolderBorder: rgba(0, 0, 0, 0.38);
|
||||
--vscode-statusBar-noFolderForeground: #546e7a;
|
||||
--vscode-statusBarItem-activeBackground: rgba(255, 255, 255, 0.18);
|
||||
--vscode-statusBarItem-hoverBackground: rgba(84, 110, 122, 0.13);
|
||||
--vscode-statusBarItem-prominentBackground: #388a34;
|
||||
--vscode-statusBarItem-prominentHoverBackground: #369432;
|
||||
--vscode-tab-activeBackground: #263238;
|
||||
--vscode-tab-activeBorder: #80cbc4;
|
||||
--vscode-tab-activeForeground: #ffffff;
|
||||
--vscode-tab-activeModifiedBorder: #5f7a87;
|
||||
--vscode-tab-border: #263238;
|
||||
--vscode-tab-inactiveBackground: #263238;
|
||||
--vscode-tab-inactiveForeground: #5f7a87;
|
||||
--vscode-tab-inactiveModifiedBorder: rgba(95, 122, 135, 0.5);
|
||||
--vscode-tab-unfocusedActiveBorder: #546e7a;
|
||||
--vscode-tab-unfocusedActiveForeground: #eeffff;
|
||||
--vscode-tab-unfocusedActiveModifiedBorder: rgba(95, 122, 135, 0.5);
|
||||
--vscode-tab-unfocusedInactiveForeground: rgba(95, 122, 135, 0.5);
|
||||
--vscode-tab-unfocusedInactiveModifiedBorder: rgba(95, 122, 135, 0.25);
|
||||
--vscode-terminal-ansiBlack: #000000;
|
||||
--vscode-terminal-ansiBlue: #82aaff;
|
||||
--vscode-terminal-ansiBrightBlack: #546e7a;
|
||||
--vscode-terminal-ansiBrightBlue: #82aaff;
|
||||
--vscode-terminal-ansiBrightCyan: #89ddff;
|
||||
--vscode-terminal-ansiBrightGreen: #c3e88d;
|
||||
--vscode-terminal-ansiBrightMagenta: #c792ea;
|
||||
--vscode-terminal-ansiBrightRed: #ff5370;
|
||||
--vscode-terminal-ansiBrightWhite: #ffffff;
|
||||
--vscode-terminal-ansiBrightYellow: #ffcb6b;
|
||||
--vscode-terminal-ansiCyan: #89ddff;
|
||||
--vscode-terminal-ansiGreen: #c3e88d;
|
||||
--vscode-terminal-ansiMagenta: #c792ea;
|
||||
--vscode-terminal-ansiRed: #ff5370;
|
||||
--vscode-terminal-ansiWhite: #ffffff;
|
||||
--vscode-terminal-ansiYellow: #ffcb6b;
|
||||
--vscode-terminal-background: #192227;
|
||||
--vscode-terminal-border: rgba(0, 0, 0, 0.38);
|
||||
--vscode-terminal-foreground: #cccccc;
|
||||
--vscode-terminal-selectionBackground: rgba(255, 255, 255, 0.25);
|
||||
--vscode-textBlockQuote-background: rgba(127, 127, 127, 0.1);
|
||||
--vscode-textBlockQuote-border: rgba(0, 122, 204, 0.5);
|
||||
--vscode-textCodeBlock-background: rgba(10, 10, 10, 0.4);
|
||||
--vscode-textLink-activeForeground: #eeffff;
|
||||
--vscode-textLink-foreground: #80cbc4;
|
||||
--vscode-textPreformat-foreground: #d7ba7d;
|
||||
--vscode-textSeparator-foreground: rgba(255, 255, 255, 0.18);
|
||||
--vscode-titleBar-activeBackground: #192227;
|
||||
--vscode-titleBar-activeForeground: #eeffff;
|
||||
--vscode-titleBar-border: rgba(0, 0, 0, 0.38);
|
||||
--vscode-titleBar-inactiveBackground: #192227;
|
||||
--vscode-titleBar-inactiveForeground: #5f7a87;
|
||||
--vscode-widget-shadow: rgba(0, 0, 0, 0.19);
|
||||
}
|
||||
307
playground/src/vscode-light.scss
Normal file
307
playground/src/vscode-light.scss
Normal file
@@ -0,0 +1,307 @@
|
||||
.theme-light {
|
||||
--vscode-activityBar-background: #2c2c2c;
|
||||
--vscode-activityBar-dropBackground: rgba(255, 255, 255, 0.12);
|
||||
--vscode-activityBar-foreground: #ffffff;
|
||||
--vscode-activityBar-inactiveForeground: rgba(255, 255, 255, 0.6);
|
||||
--vscode-activityBarBadge-background: #007acc;
|
||||
--vscode-activityBarBadge-foreground: #ffffff;
|
||||
--vscode-badge-background: #c4c4c4;
|
||||
--vscode-badge-foreground: #333333;
|
||||
--vscode-breadcrumb-activeSelectionForeground: #4e4e4e;
|
||||
--vscode-breadcrumb-background: #ffffff;
|
||||
--vscode-breadcrumb-focusForeground: #4e4e4e;
|
||||
--vscode-breadcrumb-foreground: rgba(97, 97, 97, 0.8);
|
||||
--vscode-breadcrumbPicker-background: #f3f3f3;
|
||||
--vscode-button-background: #007acc;
|
||||
--vscode-button-foreground: #ffffff;
|
||||
--vscode-button-hoverBackground: #0062a3;
|
||||
--vscode-debugExceptionWidget-background: #f1dfde;
|
||||
--vscode-debugExceptionWidget-border: #a31515;
|
||||
--vscode-debugToolBar-background: #f3f3f3;
|
||||
--vscode-descriptionForeground: #717171;
|
||||
--vscode-diffEditor-insertedTextBackground: rgba(155, 185, 85, 0.2);
|
||||
--vscode-diffEditor-removedTextBackground: rgba(255, 0, 0, 0.2);
|
||||
--vscode-dropdown-background: #ffffff;
|
||||
--vscode-dropdown-border: #cecece;
|
||||
--vscode-editor-background: #ffffff;
|
||||
--vscode-editor-findMatchBackground: #a8ac94;
|
||||
--vscode-editor-findMatchHighlightBackground: rgba(234, 92, 0, 0.33);
|
||||
--vscode-editor-findRangeHighlightBackground: rgba(180, 180, 180, 0.3);
|
||||
--vscode-editor-focusedStackFrameHighlightBackground: rgba(
|
||||
206,
|
||||
231,
|
||||
206,
|
||||
0.45
|
||||
);
|
||||
--vscode-editor-font-family: Consolas, "Courier New", monospace;
|
||||
--vscode-editor-font-size: 14;
|
||||
--vscode-editor-font-weight: normal;
|
||||
--vscode-editor-foreground: #000000;
|
||||
--vscode-editor-hoverHighlightBackground: rgba(173, 214, 255, 0.15);
|
||||
--vscode-editor-inactiveSelectionBackground: #e5ebf1;
|
||||
--vscode-editor-lineHighlightBorder: #eeeeee;
|
||||
--vscode-editor-rangeHighlightBackground: rgba(253, 255, 0, 0.2);
|
||||
--vscode-editor-selectionBackground: #add6ff;
|
||||
--vscode-editor-selectionHighlightBackground: rgba(173, 214, 255, 0.5);
|
||||
--vscode-editor-snippetFinalTabstopHighlightBorder: rgba(10, 50, 100, 0.5);
|
||||
--vscode-editor-snippetTabstopHighlightBackground: rgba(10, 50, 100, 0.2);
|
||||
--vscode-editor-stackFrameHighlightBackground: rgba(255, 255, 102, 0.45);
|
||||
--vscode-editor-wordHighlightBackground: rgba(87, 87, 87, 0.25);
|
||||
--vscode-editor-wordHighlightStrongBackground: rgba(14, 99, 156, 0.25);
|
||||
--vscode-editorActiveLineNumber-foreground: #0b216f;
|
||||
--vscode-editorBracketMatch-background: rgba(0, 100, 0, 0.1);
|
||||
--vscode-editorBracketMatch-border: #b9b9b9;
|
||||
--vscode-editorCodeLens-foreground: #999999;
|
||||
--vscode-editorCursor-foreground: #000000;
|
||||
--vscode-editorError-foreground: #e51400;
|
||||
--vscode-editorGroup-border: #e7e7e7;
|
||||
--vscode-editorGroup-dropBackground: rgba(38, 119, 203, 0.18);
|
||||
--vscode-editorGroupHeader-noTabsBackground: #ffffff;
|
||||
--vscode-editorGroupHeader-tabsBackground: #f3f3f3;
|
||||
--vscode-editorGutter-addedBackground: #81b88b;
|
||||
--vscode-editorGutter-background: #ffffff;
|
||||
--vscode-editorGutter-commentRangeForeground: #c5c5c5;
|
||||
--vscode-editorGutter-deletedBackground: #ca4b51;
|
||||
--vscode-editorGutter-modifiedBackground: #66afe0;
|
||||
--vscode-editorHint-foreground: #6c6c6c;
|
||||
--vscode-editorHoverWidget-background: #f3f3f3;
|
||||
--vscode-editorHoverWidget-border: #c8c8c8;
|
||||
--vscode-editorHoverWidget-statusBarBackground: #e7e7e7;
|
||||
--vscode-editorIndentGuide-activeBackground: #939393;
|
||||
--vscode-editorIndentGuide-background: #d3d3d3;
|
||||
--vscode-editorInfo-foreground: #008000;
|
||||
--vscode-editorLineNumber-activeForeground: #0b216f;
|
||||
--vscode-editorLineNumber-foreground: #237893;
|
||||
--vscode-editorLink-activeForeground: #0000ff;
|
||||
--vscode-editorMarkerNavigation-background: #ffffff;
|
||||
--vscode-editorMarkerNavigationError-background: #e51400;
|
||||
--vscode-editorMarkerNavigationInfo-background: #008000;
|
||||
--vscode-editorMarkerNavigationWarning-background: #e9a700;
|
||||
--vscode-editorOverviewRuler-addedForeground: rgba(0, 122, 204, 0.6);
|
||||
--vscode-editorOverviewRuler-border: rgba(127, 127, 127, 0.3);
|
||||
--vscode-editorOverviewRuler-bracketMatchForeground: #a0a0a0;
|
||||
--vscode-editorOverviewRuler-commonContentForeground: rgba(96, 96, 96, 0.4);
|
||||
--vscode-editorOverviewRuler-currentContentForeground: rgba(
|
||||
64,
|
||||
200,
|
||||
174,
|
||||
0.5
|
||||
);
|
||||
--vscode-editorOverviewRuler-deletedForeground: rgba(0, 122, 204, 0.6);
|
||||
--vscode-editorOverviewRuler-errorForeground: rgba(255, 18, 18, 0.7);
|
||||
--vscode-editorOverviewRuler-findMatchForeground: rgba(209, 134, 22, 0.49);
|
||||
--vscode-editorOverviewRuler-incomingContentForeground: rgba(
|
||||
64,
|
||||
166,
|
||||
255,
|
||||
0.5
|
||||
);
|
||||
--vscode-editorOverviewRuler-infoForeground: #008000;
|
||||
--vscode-editorOverviewRuler-modifiedForeground: rgba(0, 122, 204, 0.6);
|
||||
--vscode-editorOverviewRuler-rangeHighlightForeground: rgba(
|
||||
0,
|
||||
122,
|
||||
204,
|
||||
0.6
|
||||
);
|
||||
--vscode-editorOverviewRuler-selectionHighlightForeground: rgba(
|
||||
160,
|
||||
160,
|
||||
160,
|
||||
0.8
|
||||
);
|
||||
--vscode-editorOverviewRuler-warningForeground: #e9a700;
|
||||
--vscode-editorOverviewRuler-wordHighlightForeground: rgba(
|
||||
160,
|
||||
160,
|
||||
160,
|
||||
0.8
|
||||
);
|
||||
--vscode-editorOverviewRuler-wordHighlightStrongForeground: rgba(
|
||||
192,
|
||||
160,
|
||||
192,
|
||||
0.8
|
||||
);
|
||||
--vscode-editorPane-background: #ffffff;
|
||||
--vscode-editorRuler-foreground: #d3d3d3;
|
||||
--vscode-editorSuggestWidget-background: #f3f3f3;
|
||||
--vscode-editorSuggestWidget-border: #c8c8c8;
|
||||
--vscode-editorSuggestWidget-foreground: #000000;
|
||||
--vscode-editorSuggestWidget-highlightForeground: #0066bf;
|
||||
--vscode-editorSuggestWidget-selectedBackground: #d6ebff;
|
||||
--vscode-editorUnnecessaryCode-opacity: rgba(0, 0, 0, 0.47);
|
||||
--vscode-editorWarning-foreground: #e9a700;
|
||||
--vscode-editorWhitespace-foreground: rgba(51, 51, 51, 0.2);
|
||||
--vscode-editorWidget-background: #f3f3f3;
|
||||
--vscode-editorWidget-border: #c8c8c8;
|
||||
--vscode-errorForeground: #a1260d;
|
||||
--vscode-extensionBadge-remoteBackground: #007acc;
|
||||
--vscode-extensionBadge-remoteForeground: #ffffff;
|
||||
--vscode-extensionButton-prominentBackground: #327e36;
|
||||
--vscode-extensionButton-prominentForeground: #ffffff;
|
||||
--vscode-extensionButton-prominentHoverBackground: #28632b;
|
||||
--vscode-focusBorder: rgba(0, 122, 204, 0.4);
|
||||
--vscode-font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC",
|
||||
"Segoe UI", "Ubuntu", "Droid Sans", ans-serif;
|
||||
--vscode-font-size: 13px;
|
||||
--vscode-font-weight: normal;
|
||||
--vscode-foreground: #616161;
|
||||
--vscode-gitDecoration-addedResourceForeground: #587c0c;
|
||||
--vscode-gitDecoration-conflictingResourceForeground: #6c6cc4;
|
||||
--vscode-gitDecoration-deletedResourceForeground: #ad0707;
|
||||
--vscode-gitDecoration-ignoredResourceForeground: #8e8e90;
|
||||
--vscode-gitDecoration-modifiedResourceForeground: #895503;
|
||||
--vscode-gitDecoration-submoduleResourceForeground: #1258a7;
|
||||
--vscode-gitDecoration-untrackedResourceForeground: #007100;
|
||||
--vscode-gitlens-gutterBackgroundColor: rgba(0, 0, 0, 0.05);
|
||||
--vscode-gitlens-gutterForegroundColor: #747474;
|
||||
--vscode-gitlens-gutterUncommittedForegroundColor: rgba(0, 188, 242, 0.6);
|
||||
--vscode-gitlens-lineHighlightBackgroundColor: rgba(0, 188, 242, 0.2);
|
||||
--vscode-gitlens-lineHighlightOverviewRulerColor: rgba(0, 188, 242, 0.6);
|
||||
--vscode-gitlens-trailingLineBackgroundColor: rgba(0, 0, 0, 0);
|
||||
--vscode-gitlens-trailingLineForegroundColor: rgba(153, 153, 153, 0.35);
|
||||
--vscode-input-background: #ffffff;
|
||||
--vscode-input-foreground: #616161;
|
||||
--vscode-input-placeholderForeground: #767676;
|
||||
--vscode-inputOption-activeBorder: #007acc;
|
||||
--vscode-inputValidation-errorBackground: #f2dede;
|
||||
--vscode-inputValidation-errorBorder: #be1100;
|
||||
--vscode-inputValidation-infoBackground: #d6ecf2;
|
||||
--vscode-inputValidation-infoBorder: #007acc;
|
||||
--vscode-inputValidation-warningBackground: #f6f5d2;
|
||||
--vscode-inputValidation-warningBorder: #b89500;
|
||||
--vscode-list-activeSelectionBackground: #0074e8;
|
||||
--vscode-list-activeSelectionForeground: #ffffff;
|
||||
--vscode-list-dropBackground: #d6ebff;
|
||||
--vscode-list-errorForeground: #b01011;
|
||||
--vscode-list-focusBackground: #d6ebff;
|
||||
--vscode-list-highlightForeground: #0066bf;
|
||||
--vscode-list-hoverBackground: #e8e8e8;
|
||||
--vscode-list-inactiveSelectionBackground: #e4e6f1;
|
||||
--vscode-list-invalidItemForeground: #b89500;
|
||||
--vscode-list-warningForeground: #855f00;
|
||||
--vscode-listFilterWidget-background: #efc1ad;
|
||||
--vscode-listFilterWidget-noMatchesOutline: #be1100;
|
||||
--vscode-listFilterWidget-outline: rgba(0, 0, 0, 0);
|
||||
--vscode-menu-background: #ffffff;
|
||||
--vscode-menu-foreground: #616161;
|
||||
--vscode-menu-selectionBackground: #0074e8;
|
||||
--vscode-menu-selectionForeground: #ffffff;
|
||||
--vscode-menu-separatorBackground: #888888;
|
||||
--vscode-menubar-selectionBackground: rgba(0, 0, 0, 0.1);
|
||||
--vscode-menubar-selectionForeground: #333333;
|
||||
--vscode-merge-commonContentBackground: rgba(96, 96, 96, 0.16);
|
||||
--vscode-merge-commonHeaderBackground: rgba(96, 96, 96, 0.4);
|
||||
--vscode-merge-currentContentBackground: rgba(64, 200, 174, 0.2);
|
||||
--vscode-merge-currentHeaderBackground: rgba(64, 200, 174, 0.5);
|
||||
--vscode-merge-incomingContentBackground: rgba(64, 166, 255, 0.2);
|
||||
--vscode-merge-incomingHeaderBackground: rgba(64, 166, 255, 0.5);
|
||||
--vscode-notificationCenterHeader-background: #e7e7e7;
|
||||
--vscode-notificationLink-foreground: #006ab1;
|
||||
--vscode-notifications-background: #f3f3f3;
|
||||
--vscode-notifications-border: #e7e7e7;
|
||||
--vscode-panel-background: #ffffff;
|
||||
--vscode-panel-border: rgba(128, 128, 128, 0.35);
|
||||
--vscode-panel-dropBackground: rgba(38, 119, 203, 0.18);
|
||||
--vscode-panelInput-border: #dddddd;
|
||||
--vscode-panelTitle-activeBorder: rgba(128, 128, 128, 0.35);
|
||||
--vscode-panelTitle-activeForeground: #424242;
|
||||
--vscode-panelTitle-inactiveForeground: rgba(66, 66, 66, 0.75);
|
||||
--vscode-peekView-border: #007acc;
|
||||
--vscode-peekViewEditor-background: #f2f8fc;
|
||||
--vscode-peekViewEditor-matchHighlightBackground: rgba(245, 216, 2, 0.87);
|
||||
--vscode-peekViewEditorGutter-background: #f2f8fc;
|
||||
--vscode-peekViewResult-background: #f3f3f3;
|
||||
--vscode-peekViewResult-fileForeground: #1e1e1e;
|
||||
--vscode-peekViewResult-lineForeground: #646465;
|
||||
--vscode-peekViewResult-matchHighlightBackground: rgba(234, 92, 0, 0.3);
|
||||
--vscode-peekViewResult-selectionBackground: rgba(51, 153, 255, 0.2);
|
||||
--vscode-peekViewResult-selectionForeground: #6c6c6c;
|
||||
--vscode-peekViewTitle-background: #ffffff;
|
||||
--vscode-peekViewTitleDescription-foreground: rgba(108, 108, 108, 0.7);
|
||||
--vscode-peekViewTitleLabel-foreground: #333333;
|
||||
--vscode-pickerGroup-border: #cccedb;
|
||||
--vscode-pickerGroup-foreground: #0066bf;
|
||||
--vscode-progressBar-background: #0e70c0;
|
||||
--vscode-quickInput-background: #f3f3f3;
|
||||
--vscode-scrollbar-shadow: #dddddd;
|
||||
--vscode-scrollbarSlider-activeBackground: rgba(0, 0, 0, 0.6);
|
||||
--vscode-scrollbarSlider-background: rgba(100, 100, 100, 0.4);
|
||||
--vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7);
|
||||
--vscode-settings-checkboxBackground: #ffffff;
|
||||
--vscode-settings-checkboxBorder: #cecece;
|
||||
--vscode-settings-dropdownBackground: #ffffff;
|
||||
--vscode-settings-dropdownBorder: #cecece;
|
||||
--vscode-settings-dropdownListBorder: #c8c8c8;
|
||||
--vscode-settings-headerForeground: #444444;
|
||||
--vscode-settings-modifiedItemIndicator: #66afe0;
|
||||
--vscode-settings-numberInputBackground: #ffffff;
|
||||
--vscode-settings-numberInputBorder: #cecece;
|
||||
--vscode-settings-numberInputForeground: #616161;
|
||||
--vscode-settings-textInputBackground: #ffffff;
|
||||
--vscode-settings-textInputBorder: #cecece;
|
||||
--vscode-settings-textInputForeground: #616161;
|
||||
--vscode-sideBar-background: #f3f3f3;
|
||||
--vscode-sideBar-dropBackground: rgba(0, 0, 0, 0.1);
|
||||
--vscode-sideBarSectionHeader-background: rgba(128, 128, 128, 0.2);
|
||||
--vscode-sideBarTitle-foreground: #6f6f6f;
|
||||
--vscode-statusBar-background: #007acc;
|
||||
--vscode-statusBar-debuggingBackground: #cc6633;
|
||||
--vscode-statusBar-debuggingForeground: #ffffff;
|
||||
--vscode-statusBar-foreground: #ffffff;
|
||||
--vscode-statusBar-noFolderBackground: #68217a;
|
||||
--vscode-statusBar-noFolderForeground: #ffffff;
|
||||
--vscode-statusBarItem-activeBackground: rgba(255, 255, 255, 0.18);
|
||||
--vscode-statusBarItem-hoverBackground: rgba(255, 255, 255, 0.12);
|
||||
--vscode-statusBarItem-prominentBackground: rgba(0, 0, 0, 0.5);
|
||||
--vscode-statusBarItem-prominentForeground: #ffffff;
|
||||
--vscode-statusBarItem-prominentHoverBackground: rgba(0, 0, 0, 0.3);
|
||||
--vscode-statusBarItem-remoteBackground: #16825d;
|
||||
--vscode-statusBarItem-remoteForeground: #ffffff;
|
||||
--vscode-tab-activeBackground: #ffffff;
|
||||
--vscode-tab-activeForeground: #333333;
|
||||
--vscode-tab-activeModifiedBorder: #33aaee;
|
||||
--vscode-tab-border: #f3f3f3;
|
||||
--vscode-tab-inactiveBackground: #ececec;
|
||||
--vscode-tab-inactiveForeground: rgba(51, 51, 51, 0.7);
|
||||
--vscode-tab-inactiveModifiedBorder: rgba(51, 170, 238, 0.5);
|
||||
--vscode-tab-unfocusedActiveBackground: #ffffff;
|
||||
--vscode-tab-unfocusedActiveForeground: rgba(51, 51, 51, 0.7);
|
||||
--vscode-tab-unfocusedActiveModifiedBorder: rgba(51, 170, 238, 0.7);
|
||||
--vscode-tab-unfocusedInactiveForeground: rgba(51, 51, 51, 0.35);
|
||||
--vscode-tab-unfocusedInactiveModifiedBorder: rgba(51, 170, 238, 0.25);
|
||||
--vscode-terminal-ansiBlack: #000000;
|
||||
--vscode-terminal-ansiBlue: #0451a5;
|
||||
--vscode-terminal-ansiBrightBlack: #666666;
|
||||
--vscode-terminal-ansiBrightBlue: #0451a5;
|
||||
--vscode-terminal-ansiBrightCyan: #0598bc;
|
||||
--vscode-terminal-ansiBrightGreen: #14ce14;
|
||||
--vscode-terminal-ansiBrightMagenta: #bc05bc;
|
||||
--vscode-terminal-ansiBrightRed: #cd3131;
|
||||
--vscode-terminal-ansiBrightWhite: #a5a5a5;
|
||||
--vscode-terminal-ansiBrightYellow: #b5ba00;
|
||||
--vscode-terminal-ansiCyan: #0598bc;
|
||||
--vscode-terminal-ansiGreen: #00bc00;
|
||||
--vscode-terminal-ansiMagenta: #bc05bc;
|
||||
--vscode-terminal-ansiRed: #cd3131;
|
||||
--vscode-terminal-ansiWhite: #555555;
|
||||
--vscode-terminal-ansiYellow: #949800;
|
||||
--vscode-terminal-background: #ffffff;
|
||||
--vscode-terminal-border: rgba(128, 128, 128, 0.35);
|
||||
--vscode-terminal-foreground: #333333;
|
||||
--vscode-terminal-selectionBackground: rgba(0, 0, 0, 0.25);
|
||||
--vscode-textBlockQuote-background: rgba(127, 127, 127, 0.1);
|
||||
--vscode-textBlockQuote-border: rgba(0, 122, 204, 0.5);
|
||||
--vscode-textCodeBlock-background: rgba(220, 220, 220, 0.4);
|
||||
--vscode-textLink-activeForeground: #006ab1;
|
||||
--vscode-textLink-foreground: #006ab1;
|
||||
--vscode-textPreformat-foreground: #a31515;
|
||||
--vscode-textSeparator-foreground: rgba(0, 0, 0, 0.18);
|
||||
--vscode-titleBar-activeBackground: #dddddd;
|
||||
--vscode-titleBar-activeForeground: #333333;
|
||||
--vscode-titleBar-inactiveBackground: rgba(221, 221, 221, 0.6);
|
||||
--vscode-titleBar-inactiveForeground: rgba(51, 51, 51, 0.6);
|
||||
--vscode-tree-indentGuidesStroke: #a9a9a9;
|
||||
--vscode-widget-shadow: #a8a8a8;
|
||||
}
|
||||
16
playground/tsconfig.json
Normal file
16
playground/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"outDir": "dist",
|
||||
"skipLibCheck": true,
|
||||
"rootDir": "./src",
|
||||
"resolveJsonModule": true,
|
||||
"newLine": "LF",
|
||||
"sourceMap": true,
|
||||
"jsx": "react",
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
74
playground/webpack.config.ts
Normal file
74
playground/webpack.config.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import * as webpack from "webpack";
|
||||
import path = require("path");
|
||||
import HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
import MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
|
||||
import ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
|
||||
import { CleanWebpackPlugin } from "clean-webpack-plugin";
|
||||
|
||||
const r = (file: string) => path.resolve(__dirname, file);
|
||||
|
||||
module.exports = {
|
||||
entry: [r("src/index.tsx")],
|
||||
output: {
|
||||
path: r("dist"),
|
||||
filename: "[name].js",
|
||||
chunkFilename: "[name]-[hash].js",
|
||||
devtoolModuleFilenameTemplate: info => {
|
||||
let result = info.absoluteResourcePath.replace(/\\/g, "/");
|
||||
if (!result.startsWith("file:")) {
|
||||
// Some paths already start with the file scheme.
|
||||
result = "file:///" + result;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"],
|
||||
},
|
||||
devtool: "source-map",
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.less$/,
|
||||
loaders: ["style-loader", "css-loader", "less-loader"],
|
||||
},
|
||||
{ test: /\.css$/, loader: "style-loader!css-loader" },
|
||||
{ test: /\.scss$/, loader: "style-loader!css-loader!sass-loader" },
|
||||
{
|
||||
test: /\.(jpe?g|png|gif|eot|ttf|svg|woff|woff2|md)$/i,
|
||||
loader: "file-loader",
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: "ts-loader",
|
||||
options: { transpileOnly: true },
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
enforce: "pre",
|
||||
use: ["source-map-loader"],
|
||||
},
|
||||
],
|
||||
},
|
||||
node: {
|
||||
fs: "empty",
|
||||
},
|
||||
plugins: (() => {
|
||||
const plugins: any[] = [
|
||||
new HtmlWebpackPlugin({
|
||||
title: "Debug Visualizer",
|
||||
}),
|
||||
new ForkTsCheckerWebpackPlugin(),
|
||||
new CleanWebpackPlugin(),
|
||||
];
|
||||
|
||||
plugins.push(
|
||||
new MonacoWebpackPlugin({
|
||||
// Add more languages here once webworker issues are solved.
|
||||
languages: ["typescript", "json"],
|
||||
})
|
||||
);
|
||||
|
||||
return plugins;
|
||||
})(),
|
||||
} as webpack.Configuration;
|
||||
38
simple-demo/package.json
Normal file
38
simple-demo/package.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "simple-demo",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server --hot --port 8090",
|
||||
"build": "webpack"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hediet/visualization-core": "^0.1.0",
|
||||
"@hediet/visualization-bundle": "^0.1.0",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/html-webpack-plugin": "^3.2.2",
|
||||
"@types/webpack": "^4.41.6",
|
||||
"@types/react": "^16.9.22",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"clean-webpack-plugin": "^3.0.0",
|
||||
"css-loader": "^3.4.2",
|
||||
"file-loader": "^5.1.0",
|
||||
"fork-ts-checker-webpack-plugin": "^4.0.4",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"monaco-editor-webpack-plugin": "^1.9.0",
|
||||
"raw-loader": "^4.0.0",
|
||||
"sass-loader": "^8.0.2",
|
||||
"style-loader": "^1.1.3",
|
||||
"ts-loader": "^6.2.1",
|
||||
"ts-node": "^8.6.2",
|
||||
"typescript": "^3.8.2",
|
||||
"webpack": "^4.41.6",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-dev-server": "^3.10.3",
|
||||
"source-map-loader": "^1.0.1",
|
||||
"less-loader": "^6.2.0"
|
||||
}
|
||||
}
|
||||
47
simple-demo/src/index.tsx
Normal file
47
simple-demo/src/index.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
VisualizationView,
|
||||
Theme,
|
||||
globalVisualizationFactory,
|
||||
VisualizationData,
|
||||
} from "@hediet/visualization-core";
|
||||
|
||||
// This registers all visualizations
|
||||
import "@hediet/visualization-bundle";
|
||||
|
||||
import * as React from "react";
|
||||
import * as ReactDOM from "react-dom";
|
||||
import "./style.scss";
|
||||
|
||||
class App extends React.Component {
|
||||
render() {
|
||||
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,
|
||||
undefined
|
||||
);
|
||||
|
||||
return (
|
||||
<VisualizationView
|
||||
theme={Theme.light}
|
||||
visualization={visualizations.bestVisualization!}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const elem = document.createElement("div");
|
||||
elem.className = "react-root";
|
||||
document.body.append(elem);
|
||||
ReactDOM.render(<App />, elem);
|
||||
7
simple-demo/src/style.scss
Normal file
7
simple-demo/src/style.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
@import "~@hediet/visualization-bundle/style.scss";
|
||||
|
||||
html,
|
||||
body,
|
||||
.react-root {
|
||||
height: 100%;
|
||||
}
|
||||
16
simple-demo/tsconfig.json
Normal file
16
simple-demo/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"outDir": "dist",
|
||||
"skipLibCheck": true,
|
||||
"rootDir": "./src",
|
||||
"resolveJsonModule": true,
|
||||
"newLine": "LF",
|
||||
"sourceMap": true,
|
||||
"jsx": "react",
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
74
simple-demo/webpack.config.ts
Normal file
74
simple-demo/webpack.config.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
import * as webpack from "webpack";
|
||||
import path = require("path");
|
||||
import HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
import MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
|
||||
import ForkTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin");
|
||||
import { CleanWebpackPlugin } from "clean-webpack-plugin";
|
||||
|
||||
const r = (file: string) => path.resolve(__dirname, file);
|
||||
|
||||
module.exports = {
|
||||
entry: [r("src/index.tsx")],
|
||||
output: {
|
||||
path: r("dist"),
|
||||
filename: "[name].js",
|
||||
chunkFilename: "[name]-[hash].js",
|
||||
devtoolModuleFilenameTemplate: info => {
|
||||
let result = info.absoluteResourcePath.replace(/\\/g, "/");
|
||||
if (!result.startsWith("file:")) {
|
||||
// Some paths already start with the file scheme.
|
||||
result = "file:///" + result;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"],
|
||||
},
|
||||
devtool: "source-map",
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.less$/,
|
||||
loaders: ["style-loader", "css-loader", "less-loader"],
|
||||
},
|
||||
{ test: /\.css$/, loader: "style-loader!css-loader" },
|
||||
{ test: /\.scss$/, loader: "style-loader!css-loader!sass-loader" },
|
||||
{
|
||||
test: /\.(jpe?g|png|gif|eot|ttf|svg|woff|woff2|md)$/i,
|
||||
loader: "file-loader",
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: "ts-loader",
|
||||
options: { transpileOnly: true },
|
||||
},
|
||||
{
|
||||
test: /\.js$/,
|
||||
enforce: "pre",
|
||||
use: ["source-map-loader"],
|
||||
},
|
||||
],
|
||||
},
|
||||
node: {
|
||||
fs: "empty",
|
||||
},
|
||||
plugins: (() => {
|
||||
const plugins: any[] = [
|
||||
new HtmlWebpackPlugin({
|
||||
title: "Debug Visualizer",
|
||||
}),
|
||||
new ForkTsCheckerWebpackPlugin(),
|
||||
new CleanWebpackPlugin(),
|
||||
];
|
||||
|
||||
plugins.push(
|
||||
new MonacoWebpackPlugin({
|
||||
// Add more languages here once webworker issues are solved.
|
||||
languages: ["typescript", "json"],
|
||||
})
|
||||
);
|
||||
|
||||
return plugins;
|
||||
})(),
|
||||
} as webpack.Configuration;
|
||||
6
tslint.json
Normal file
6
tslint.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "recommended",
|
||||
"rules": {
|
||||
"await-promise": true
|
||||
}
|
||||
}
|
||||
45
visualization-bundle/README.md
Normal file
45
visualization-bundle/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Visualization Bundle
|
||||
|
||||
You can use yarn to install this library:
|
||||
|
||||
```
|
||||
yarn add @hediet/visualization-bundle
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Import `@hediet/visualization-bundle` to register all default visualizations to `globalVisualizationFactory` of `@hediet/visualization-core`:
|
||||
|
||||
```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";
|
||||
```
|
||||
|
||||
See docs of `@hediet/visualization-core` for how to find and render visualizations!
|
||||
|
||||
## Supported Visualizations
|
||||
|
||||
### Origin Visualizations
|
||||
|
||||
- Tree Visualizer
|
||||
- AST Visualizer (+ Monaco Source Code View)
|
||||
- Grid Visualizer
|
||||
- SVG Visualizer
|
||||
- Image Visualizer
|
||||
- Simple Text Visualizer
|
||||
|
||||
### Integrated Visualizations
|
||||
|
||||
- Plotly Visualizer
|
||||
- Perspective JS Visualizer
|
||||
- VisJS Visualizer
|
||||
- Graphviz Graph Visualizer
|
||||
- Graphviz Dot Visualizer
|
||||
- Vis.js Visualizer
|
||||
- Monaco Editor Source Code Visualizer
|
||||
65
visualization-bundle/package.json
Normal file
65
visualization-bundle/package.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "@hediet/visualization-bundle",
|
||||
"description": "Bundles several visualizers",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "tsc --watch",
|
||||
"build": "tsc"
|
||||
},
|
||||
"homepage": "https://github.com/hediet/visualization",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/hediet/visualization.git"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"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-d3fc": "^0.5.2",
|
||||
"@finos/perspective-viewer-datagrid": "^0.5.2",
|
||||
"@hediet/monaco-editor-react": "^0.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@hediet/visualization-core": "^0.1.0"
|
||||
},
|
||||
"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/react": "^16.9.22",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"@types/line-column": "^1.0.0",
|
||||
"sass": "^1.25.0",
|
||||
"ts-loader": "^6.2.1",
|
||||
"ts-node": "^8.6.2",
|
||||
"typescript": "^3.9.7"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
}
|
||||
}
|
||||
3
visualization-bundle/src/consts.ts
Normal file
3
visualization-bundle/src/consts.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { namespace } from "@hediet/semantic-json/dist/src/NamespacedNamed";
|
||||
|
||||
export const visualizationNs = namespace("hediet.de/visualization");
|
||||
1
visualization-bundle/src/index.ts
Normal file
1
visualization-bundle/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./visualizers";
|
||||
30
visualization-bundle/src/utils/LazyLoadable.tsx
Normal file
30
visualization-bundle/src/utils/LazyLoadable.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import * as React from "react";
|
||||
import { observable, runInAction } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
export function makeLazyLoadable<TProps>(
|
||||
selector: () => Promise<React.ComponentClass<TProps>>
|
||||
): React.ComponentClass<TProps> {
|
||||
@observer
|
||||
class MyComponent extends React.Component<TProps> {
|
||||
@observable
|
||||
component: React.ComponentClass<TProps> | undefined;
|
||||
|
||||
async componentDidMount() {
|
||||
const result = await selector();
|
||||
runInAction("Set Lazily Loaded Component", () => {
|
||||
this.component = result;
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.component) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
const C = this.component;
|
||||
return <C {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
return MyComponent;
|
||||
}
|
||||
13
visualization-bundle/src/utils/index.ts
Normal file
13
visualization-bundle/src/utils/index.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Refinement } from "@hediet/semantic-json/dist/src/serialization/BaseSerializer";
|
||||
|
||||
export function withDeserializer<T, TIntermediate>(
|
||||
fromIntermediate: (val: TIntermediate) => T
|
||||
): Refinement<T, TIntermediate> {
|
||||
return {
|
||||
canSerialize: (val: unknown): val is T => false,
|
||||
fromIntermediate,
|
||||
toIntermediate: () => {
|
||||
throw new Error("not supported");
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
import * as React from "react";
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { observable, autorun, trace, computed } from "mobx";
|
||||
import * as monacoTypes from "monaco-editor";
|
||||
import { getLanguageId } from "../monaco-text-visualizer/MonacoEditor";
|
||||
import {
|
||||
TreeWithPathView,
|
||||
TreeViewModel,
|
||||
TreeNodeViewModel,
|
||||
} from "../tree-visualizer/Views";
|
||||
import { sAstTree } from ".";
|
||||
import Measure from "react-measure";
|
||||
import { MonacoEditor, getLoadedMonaco } from "@hediet/monaco-editor-react";
|
||||
import { Theme } from "@hediet/visualization-core";
|
||||
|
||||
export interface NodeInfo {
|
||||
start: number;
|
||||
length: number;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class AstTree extends React.Component<{
|
||||
model: TreeViewModel<NodeInfo>;
|
||||
data: typeof sAstTree.T;
|
||||
nodeInfoToRange: (info: NodeInfo) => monacoTypes.IRange;
|
||||
posToIndex: (pos: monacoTypes.IPosition) => number;
|
||||
theme: Theme;
|
||||
}> {
|
||||
private readonly editorRef = React.createRef<SourceCodeView>();
|
||||
|
||||
render() {
|
||||
const { model, data, theme } = this.props;
|
||||
|
||||
let languageId = "text";
|
||||
if (data.fileName) {
|
||||
languageId = getLanguageId(data.fileName);
|
||||
}
|
||||
return (
|
||||
<Measure client>
|
||||
{({ measureRef, contentRect }) => {
|
||||
return (
|
||||
<div
|
||||
ref={measureRef}
|
||||
className="component-AstTree"
|
||||
style={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection:
|
||||
contentRect.client &&
|
||||
contentRect.client.width > 1100
|
||||
? "row"
|
||||
: "column",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="part-tree2"
|
||||
style={{ flex: 1, minHeight: 0, minWidth: 0 }}
|
||||
>
|
||||
<TreeWithPathView model={model} />
|
||||
</div>
|
||||
<div
|
||||
className="part-editor"
|
||||
style={{
|
||||
flex: 1,
|
||||
minHeight: 0,
|
||||
minWidth: 0,
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<SourceCodeView
|
||||
theme={theme}
|
||||
ref={this.editorRef}
|
||||
nodeInfoToRange={this.props.nodeInfoToRange}
|
||||
posToIndex={this.props.posToIndex}
|
||||
model={model}
|
||||
languageId={languageId}
|
||||
text={data.text}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Measure>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class SourceCodeView extends React.Component<{
|
||||
text: string;
|
||||
languageId: string;
|
||||
model: TreeViewModel<NodeInfo>;
|
||||
theme: Theme;
|
||||
nodeInfoToRange: (info: NodeInfo) => monacoTypes.IRange;
|
||||
posToIndex: (pos: monacoTypes.IPosition) => number;
|
||||
}> {
|
||||
@observable private editor:
|
||||
| monacoTypes.editor.IStandaloneCodeEditor
|
||||
| undefined;
|
||||
|
||||
private markedDecorations: string[] = [];
|
||||
private selectedDecorations: string[] = [];
|
||||
|
||||
constructor(props: any) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
layout() {
|
||||
if (this.editor) {
|
||||
this.editor.layout();
|
||||
}
|
||||
}
|
||||
|
||||
@computed.struct
|
||||
private get markedRanges(): monacoTypes.IRange[] {
|
||||
if (!this.editor || !this.model) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const editorModel = this.model;
|
||||
const ranges = this.props.model.marked.map(s =>
|
||||
this.props.nodeInfoToRange(s.data)
|
||||
);
|
||||
return ranges;
|
||||
}
|
||||
|
||||
@disposeOnUnmount
|
||||
private _updateMarkedDecorations = autorun(
|
||||
() => {
|
||||
if (!this.editor) {
|
||||
return;
|
||||
}
|
||||
const ranges = this.markedRanges;
|
||||
this.markedDecorations = this.editor.deltaDecorations(
|
||||
this.markedDecorations,
|
||||
ranges.map(range => ({
|
||||
range,
|
||||
options: { className: "marked" },
|
||||
}))
|
||||
);
|
||||
if (ranges.length > 0) {
|
||||
this.editor.revealRange(
|
||||
ranges[0],
|
||||
getLoadedMonaco().editor.ScrollType.Smooth
|
||||
);
|
||||
}
|
||||
},
|
||||
{ name: "updateMarkedDecorations" }
|
||||
);
|
||||
|
||||
@computed.struct
|
||||
private get selected(): monacoTypes.IRange | undefined {
|
||||
if (this.editor && this.model) {
|
||||
const selected = this.props.model.selected;
|
||||
if (selected) {
|
||||
const range = this.props.nodeInfoToRange(selected.data);
|
||||
return range;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@disposeOnUnmount
|
||||
private _updateSelectedDecoration = autorun(
|
||||
() => {
|
||||
console.log("_updateSelectedDecoration");
|
||||
if (!this.editor) {
|
||||
return;
|
||||
}
|
||||
const range = this.selected;
|
||||
if (range) {
|
||||
this.selectedDecorations = this.editor.deltaDecorations(
|
||||
this.selectedDecorations,
|
||||
[
|
||||
{
|
||||
range,
|
||||
options: { className: "selected" },
|
||||
},
|
||||
]
|
||||
);
|
||||
this.editor.revealRange(
|
||||
range,
|
||||
getLoadedMonaco().editor.ScrollType.Smooth
|
||||
);
|
||||
} else {
|
||||
this.selectedDecorations = this.editor.deltaDecorations(
|
||||
this.selectedDecorations,
|
||||
[]
|
||||
);
|
||||
}
|
||||
},
|
||||
{ name: "updateDecorations" }
|
||||
);
|
||||
|
||||
@computed.struct
|
||||
private get modelData(): { text: string; languageId: string } {
|
||||
const { text, languageId } = this.props;
|
||||
return { text, languageId };
|
||||
}
|
||||
|
||||
private lastModel: monacoTypes.editor.ITextModel | undefined;
|
||||
@computed get model(): monacoTypes.editor.ITextModel {
|
||||
const last = this.lastModel;
|
||||
this.lastModel = getLoadedMonaco().editor.createModel(
|
||||
this.modelData.text,
|
||||
this.modelData.languageId,
|
||||
undefined
|
||||
);
|
||||
if (last) {
|
||||
setTimeout(() => {
|
||||
// we don't want to dispose the current model
|
||||
last.dispose();
|
||||
});
|
||||
}
|
||||
return this.lastModel;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<MonacoEditor
|
||||
model={this.model}
|
||||
theme={this.props.theme.kind === "dark" ? "vs-dark" : "vs"}
|
||||
onEditorLoaded={e => {
|
||||
this.editor = e;
|
||||
e.onDidChangeCursorSelection(e => {
|
||||
const selectionStart = this.props.posToIndex(
|
||||
e.selection.getStartPosition()
|
||||
);
|
||||
const selectionEnd = this.props.posToIndex(
|
||||
e.selection.getEndPosition()
|
||||
);
|
||||
|
||||
const findNode = (
|
||||
m: TreeNodeViewModel<NodeInfo>
|
||||
): TreeNodeViewModel<NodeInfo> | undefined => {
|
||||
const nodeStart = m.data.start;
|
||||
const nodeEnd = nodeStart + m.data.length;
|
||||
|
||||
if (
|
||||
!(
|
||||
nodeStart <= selectionStart &&
|
||||
selectionEnd <= nodeEnd
|
||||
)
|
||||
) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const c of m.children) {
|
||||
const r = findNode(c);
|
||||
if (r) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return m;
|
||||
};
|
||||
|
||||
if (this.props.model.root) {
|
||||
const n = findNode(this.props.model.root);
|
||||
if (n) {
|
||||
this.props.model.select(n);
|
||||
}
|
||||
}
|
||||
});
|
||||
}}
|
||||
readOnly={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
126
visualization-bundle/src/visualizers/ast-visualizer/index.tsx
Normal file
126
visualization-bundle/src/visualizers/ast-visualizer/index.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import {
|
||||
Serializer,
|
||||
sLazy,
|
||||
sOpenObject,
|
||||
sArrayOf,
|
||||
sString,
|
||||
sOptionalProp,
|
||||
sUnion,
|
||||
sLiteral,
|
||||
sBoolean,
|
||||
sProp,
|
||||
sNumber,
|
||||
} from "@hediet/semantic-json";
|
||||
import { visualizationNs } from "../../consts";
|
||||
import {
|
||||
createVisualizer,
|
||||
ReactVisualization,
|
||||
globalVisualizationFactory,
|
||||
} from "@hediet/visualization-core";
|
||||
import { createTreeViewModelFromTreeNodeData } from "../tree-visualizer";
|
||||
import { AstTree, NodeInfo } from "./AstVisualizer";
|
||||
import React from "react";
|
||||
import LineColumn from "line-column";
|
||||
import * as monacoTypes from "monaco-editor";
|
||||
import { getLoadedMonaco } from "@hediet/monaco-editor-react";
|
||||
|
||||
interface AstTreeNode {
|
||||
children: AstTreeNode[];
|
||||
items: Item[];
|
||||
segment?: string; // like "root", ".name" or "[0]"
|
||||
isMarked?: boolean;
|
||||
span: NodeInfo;
|
||||
}
|
||||
|
||||
interface Item {
|
||||
text: string;
|
||||
emphasis?: "style1" | "style2" | "style3" | string;
|
||||
}
|
||||
|
||||
export const sAstTreeNode: Serializer<AstTreeNode> = sLazy(() =>
|
||||
sOpenObject({
|
||||
children: sArrayOf(sAstTreeNode),
|
||||
items: sArrayOf(
|
||||
sOpenObject({
|
||||
text: sString(),
|
||||
emphasis: sOptionalProp(
|
||||
sUnion(
|
||||
[
|
||||
sLiteral("style1"),
|
||||
sLiteral("style2"),
|
||||
sLiteral("style3"),
|
||||
sString(),
|
||||
],
|
||||
{ inclusive: true }
|
||||
)
|
||||
),
|
||||
}).defineAs(visualizationNs("TreeNodeItem"))
|
||||
),
|
||||
segment: sOptionalProp(sString()),
|
||||
isMarked: sOptionalProp(sBoolean()),
|
||||
span: sOpenObject({
|
||||
start: sNumber(),
|
||||
length: sNumber(),
|
||||
}),
|
||||
}).defineAs(visualizationNs("TreeNode"))
|
||||
);
|
||||
|
||||
export const sAstTree = sOpenObject({
|
||||
kind: sOpenObject({
|
||||
tree: sLiteral(true),
|
||||
text: sLiteral(true),
|
||||
}),
|
||||
root: sAstTreeNode,
|
||||
text: sString(),
|
||||
fileName: sOptionalProp(sString()),
|
||||
});
|
||||
|
||||
export const astVisualizer = createVisualizer({
|
||||
id: "ast",
|
||||
name: "AST",
|
||||
serializer: sAstTree,
|
||||
getVisualization: (data, self) =>
|
||||
new ReactVisualization(self, { priority: 1500 }, theme => {
|
||||
const m = createTreeViewModelFromTreeNodeData(
|
||||
data.root,
|
||||
node => node.span
|
||||
);
|
||||
const l = LineColumn(data.text);
|
||||
function translatePosition(pos: number): monacoTypes.IPosition {
|
||||
let r = l.fromIndex(pos);
|
||||
if (!r) {
|
||||
r = l.fromIndex(data.text.length - 1);
|
||||
r.col++;
|
||||
}
|
||||
return {
|
||||
column: r.col,
|
||||
lineNumber: r.line,
|
||||
};
|
||||
}
|
||||
const nodeInfoToRange = (info: NodeInfo): monacoTypes.IRange => {
|
||||
const start = translatePosition(info.start);
|
||||
const end = translatePosition(info.start + info.length);
|
||||
const range = getLoadedMonaco().Range.fromPositions(start, end);
|
||||
return range;
|
||||
};
|
||||
function posToIndex(pos: monacoTypes.IPosition): number {
|
||||
const i = l.toIndex(pos.lineNumber, pos.column);
|
||||
if (i == -1) {
|
||||
return data.text.length;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
return (
|
||||
<AstTree
|
||||
model={m}
|
||||
theme={theme}
|
||||
data={data}
|
||||
nodeInfoToRange={nodeInfoToRange}
|
||||
posToIndex={posToIndex}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
});
|
||||
|
||||
globalVisualizationFactory.addVisualizer(astVisualizer);
|
||||
@@ -0,0 +1,23 @@
|
||||
.theme-dark {
|
||||
--visualizer-ast-marked-background: rgba(234, 92, 0, 0.33);
|
||||
--visualizer-ast-selected-background: #515c6a;
|
||||
}
|
||||
.theme-light {
|
||||
--visualizer-ast-marked-background: orange;
|
||||
--visualizer-ast-selected-background: yellow;
|
||||
}
|
||||
|
||||
.component-AstTree {
|
||||
.part-editor {
|
||||
//border-top: solid gray 1px;
|
||||
|
||||
.marked {
|
||||
background: var(--visualizer-ast-marked-background);
|
||||
}
|
||||
|
||||
.selected {
|
||||
background: var(--visualizer-ast-selected-background);
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
visualization-bundle/src/visualizers/ast-visualizer/types.d.ts
vendored
Normal file
13
visualization-bundle/src/visualizers/ast-visualizer/types.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
declare module "line-column" {
|
||||
export = function(
|
||||
text: string
|
||||
): {
|
||||
fromIndex(
|
||||
idx: number
|
||||
): {
|
||||
line: number;
|
||||
col: number;
|
||||
};
|
||||
toIndex(line: number, column: number): number;
|
||||
} {};
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { observable, autorun, runInAction } from "mobx";
|
||||
import { SvgViewer } from "../../svg-visualizer/SvgViewer";
|
||||
|
||||
class VisLoader {
|
||||
private result: any | undefined;
|
||||
|
||||
async getViz(): Promise<any> {
|
||||
if (!this.result) {
|
||||
const Viz = await import("viz.js");
|
||||
const { Module, render } = await import("viz.js/full.render.js");
|
||||
|
||||
const viz = new Viz.default({
|
||||
Module: () => Module({ TOTAL_MEMORY: 1 << 30 }),
|
||||
render,
|
||||
});
|
||||
|
||||
this.result = viz;
|
||||
}
|
||||
return this.result;
|
||||
}
|
||||
}
|
||||
|
||||
const vizLoader = new VisLoader();
|
||||
|
||||
@observer
|
||||
export class GraphvizDotViewer extends React.Component<{
|
||||
dotCode: string;
|
||||
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() {
|
||||
if (!this.svg) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
return <SvgViewer svgRef={this.props.svgRef} svgContent={this.svg} />;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
import { sOpenObject, sLiteral, sString } from "@hediet/semantic-json";
|
||||
import { GraphvizDotViewer } from "./GraphvizDotVisualizer";
|
||||
import {
|
||||
ReactVisualization,
|
||||
globalVisualizationFactory,
|
||||
createVisualizer,
|
||||
} from "@hediet/visualization-core";
|
||||
|
||||
export const graphvizDotVisualizer = createVisualizer({
|
||||
id: "graphviz-dot",
|
||||
name: "Graphviz (Dot Data)",
|
||||
serializer: sOpenObject({
|
||||
kind: sOpenObject({
|
||||
dotGraph: sLiteral(true),
|
||||
}),
|
||||
text: sString(),
|
||||
}),
|
||||
getVisualization: (data, self) =>
|
||||
new ReactVisualization(self, { priority: 1500 }, () => (
|
||||
<GraphvizDotViewer dotCode={data.text} />
|
||||
)),
|
||||
});
|
||||
|
||||
globalVisualizationFactory.addVisualizer(graphvizDotVisualizer);
|
||||
9
visualization-bundle/src/visualizers/graph/dot-graphviz-visualizer/types.d.ts
vendored
Normal file
9
visualization-bundle/src/visualizers/graph/dot-graphviz-visualizer/types.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
declare module "viz.js" {
|
||||
const x: any;
|
||||
export default x;
|
||||
}
|
||||
|
||||
declare module "viz.js/full.render.js" {
|
||||
export const render: any;
|
||||
export const Module: any;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
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,20 @@
|
||||
import React from "react";
|
||||
import { GraphvizGraphViewer } from "./GraphvizGraphVisualizer";
|
||||
import { sGraph } from "../sGraph";
|
||||
import {
|
||||
createVisualizer,
|
||||
ReactVisualization,
|
||||
globalVisualizationFactory,
|
||||
} from "@hediet/visualization-core";
|
||||
|
||||
export const graphvizGraphVisualizer = createVisualizer({
|
||||
id: "graphviz-graph",
|
||||
name: "Graphviz",
|
||||
serializer: sGraph,
|
||||
getVisualization: (data, self) =>
|
||||
new ReactVisualization(self, { priority: 1000 }, () => (
|
||||
<GraphvizGraphViewer edges={data.edges} nodes={data.nodes} />
|
||||
)),
|
||||
});
|
||||
|
||||
globalVisualizationFactory.addVisualizer(graphvizGraphVisualizer);
|
||||
@@ -0,0 +1,96 @@
|
||||
import { Network, Options, DataSet } from "vis-network/standalone";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { NodeGraphData, EdgeGraphData } from "../sGraph";
|
||||
|
||||
@observer
|
||||
export class VisJsGraphViewer extends React.Component<{
|
||||
nodes: NodeGraphData[];
|
||||
edges: EdgeGraphData[];
|
||||
}> {
|
||||
private readonly divRef = React.createRef<HTMLDivElement>();
|
||||
private readonly nodes = new DataSet<{
|
||||
id: string;
|
||||
label?: string;
|
||||
color?: string;
|
||||
shape?: string;
|
||||
}>();
|
||||
private readonly edges = new DataSet<{
|
||||
id: string;
|
||||
from: string;
|
||||
to: string;
|
||||
label?: string;
|
||||
color?: string;
|
||||
dashes?: boolean;
|
||||
shape?: boolean;
|
||||
}>();
|
||||
|
||||
render() {
|
||||
return <div style={{ height: "100%" }} ref={this.divRef} />;
|
||||
}
|
||||
|
||||
synchronizeData() {
|
||||
const newNodes = new Set<string>();
|
||||
for (const n of this.props.nodes) {
|
||||
newNodes.add(n.id);
|
||||
this.nodes.update({
|
||||
id: n.id,
|
||||
label: n.label !== undefined ? n.label : n.id,
|
||||
color: n.color,
|
||||
shape: n.shape,
|
||||
});
|
||||
}
|
||||
this.nodes.forEach(item => {
|
||||
if (!newNodes.has(item.id)) {
|
||||
this.nodes.remove(item);
|
||||
}
|
||||
});
|
||||
|
||||
function getIdOfEdge(e: EdgeGraphData): string {
|
||||
if (e.id) {
|
||||
return e.id;
|
||||
}
|
||||
return e.from + "####" + e.to + "|" + e.label;
|
||||
}
|
||||
|
||||
const newEdges = new Set<string>();
|
||||
for (const n of this.props.edges) {
|
||||
const id = getIdOfEdge(n);
|
||||
newEdges.add(id);
|
||||
this.edges.update({
|
||||
id: id,
|
||||
label: n.label !== undefined ? n.label : "",
|
||||
from: n.from,
|
||||
to: n.to,
|
||||
color: n.color,
|
||||
dashes: n.dashes,
|
||||
});
|
||||
}
|
||||
this.edges.forEach(item => {
|
||||
if (!newEdges.has(item.id)) {
|
||||
this.edges.remove(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.synchronizeData();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.synchronizeData();
|
||||
|
||||
const data = {
|
||||
nodes: this.nodes,
|
||||
edges: this.edges,
|
||||
};
|
||||
const options: Options = {
|
||||
edges: {
|
||||
arrows: {
|
||||
to: { enabled: true, scaleFactor: 1, type: "arrow" },
|
||||
},
|
||||
},
|
||||
};
|
||||
const network = new Network(this.divRef.current!, data, options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import * as React from "react";
|
||||
import { makeLazyLoadable } from "../../../utils/LazyLoadable";
|
||||
import {
|
||||
createVisualizer,
|
||||
ReactVisualization,
|
||||
globalVisualizationFactory,
|
||||
} from "@hediet/visualization-core";
|
||||
import { sGraph } from "../sGraph";
|
||||
|
||||
const VisJsGraphViewerLazyLoadable = makeLazyLoadable(
|
||||
async () => (await import("./VisJsGraphViewer")).VisJsGraphViewer
|
||||
);
|
||||
|
||||
export const visJsGraphVisualizer = createVisualizer({
|
||||
id: "vis.js-graph",
|
||||
name: "vis.js",
|
||||
serializer: sGraph,
|
||||
getVisualization: (data, self) =>
|
||||
new ReactVisualization(self, { priority: 1500 }, () => (
|
||||
<VisJsGraphViewerLazyLoadable
|
||||
edges={data.edges}
|
||||
nodes={data.nodes}
|
||||
/>
|
||||
)),
|
||||
});
|
||||
|
||||
globalVisualizationFactory.addVisualizer(visJsGraphVisualizer);
|
||||
3
visualization-bundle/src/visualizers/graph/index.ts
Normal file
3
visualization-bundle/src/visualizers/graph/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from "./dot-graphviz-visualizer";
|
||||
export * from "./graph-graphviz-visualizer";
|
||||
export * from "./graph-visjs-visualizer";
|
||||
35
visualization-bundle/src/visualizers/graph/sGraph.ts
Normal file
35
visualization-bundle/src/visualizers/graph/sGraph.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
sOpenObject,
|
||||
sLiteral,
|
||||
sArrayOf,
|
||||
sString,
|
||||
sOptionalProp,
|
||||
sUnion,
|
||||
sBoolean,
|
||||
} from "@hediet/semantic-json";
|
||||
import { visualizationNs } from "../../consts";
|
||||
|
||||
export type NodeGraphData = typeof sGraphNode.T;
|
||||
export type EdgeGraphData = typeof sGraphEdge.T;
|
||||
|
||||
export const sGraphNode = sOpenObject({
|
||||
id: sString(),
|
||||
label: sOptionalProp(sString(), {}),
|
||||
color: sOptionalProp(sString(), {}),
|
||||
shape: sOptionalProp(sUnion([sLiteral("ellipse"), sLiteral("box")]), {}),
|
||||
}).defineAs(visualizationNs("GraphNode"));
|
||||
|
||||
export const sGraphEdge = sOpenObject({
|
||||
from: sString(),
|
||||
to: sString(),
|
||||
label: sOptionalProp(sString(), {}),
|
||||
id: sOptionalProp(sString(), {}),
|
||||
color: sOptionalProp(sString(), {}),
|
||||
dashes: sOptionalProp(sBoolean(), {}),
|
||||
}).defineAs(visualizationNs("GraphEdge"));
|
||||
|
||||
export const sGraph = sOpenObject({
|
||||
kind: sOpenObject({ graph: sLiteral(true) }),
|
||||
nodes: sArrayOf(sGraphNode),
|
||||
edges: sArrayOf(sGraphEdge),
|
||||
}).defineAs(visualizationNs("Graph"));
|
||||
@@ -0,0 +1,377 @@
|
||||
import * as React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { computed, action, observable } from "mobx";
|
||||
import { Icon } from "@blueprintjs/core";
|
||||
import { sGrid } from ".";
|
||||
|
||||
export class DecoratedGridComponent extends React.Component<{
|
||||
data: typeof sGrid.T;
|
||||
}> {
|
||||
render() {
|
||||
const d = this.props.data;
|
||||
|
||||
const map = new Set<string>();
|
||||
function getUniqueId(tag: string): string {
|
||||
let n = 0;
|
||||
while (true) {
|
||||
const id = tag + (n === 0 ? "" : `-${n}`);
|
||||
if (!map.has(id)) {
|
||||
map.add(id);
|
||||
return id;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
}
|
||||
|
||||
const rows: GridComponent["props"]["rows"] = [];
|
||||
rows.push();
|
||||
let rowIdx = 0;
|
||||
let columnCount = 0;
|
||||
for (const row of d.rows) {
|
||||
columnCount = Math.max(columnCount, row.columns.length);
|
||||
rows.push({
|
||||
columns: row.columns.map((c, colIdx) => ({
|
||||
content: (
|
||||
<Cell>
|
||||
{c.content !== undefined ? c.content : c.tag || ""}
|
||||
</Cell>
|
||||
),
|
||||
id:
|
||||
c.tag !== undefined
|
||||
? getUniqueId(c.tag)
|
||||
: `${rowIdx}-${colIdx}`,
|
||||
color: c.color,
|
||||
kind: "data",
|
||||
})),
|
||||
});
|
||||
rowIdx++;
|
||||
}
|
||||
|
||||
rows.unshift({
|
||||
columns: [...new Array(columnCount)].map((v, idx) => ({
|
||||
id: `column-header-${idx}`,
|
||||
content: <Cell>{idx}</Cell>,
|
||||
kind: "header",
|
||||
})),
|
||||
});
|
||||
|
||||
if (d.markers) {
|
||||
for (const m of d.markers) {
|
||||
rows.push({
|
||||
columns: [],
|
||||
});
|
||||
const r = rows[rows.length - 1];
|
||||
for (let i = 0; i < m.column; i++) {
|
||||
r.columns.push({
|
||||
content: "",
|
||||
kind: "empty",
|
||||
id: `marker-spacer-${i}`,
|
||||
});
|
||||
}
|
||||
|
||||
r.columns.push({
|
||||
id: m.id,
|
||||
kind: "empty",
|
||||
content: (
|
||||
<Cell>
|
||||
<Icon
|
||||
icon="arrow-up"
|
||||
color="var(--vscode-editor-foreground)"
|
||||
/>
|
||||
{m.label || m.id}
|
||||
</Cell>
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
height: "100%",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: 10,
|
||||
display: "flex",
|
||||
}}
|
||||
>
|
||||
<GridComponent rows={rows} markers={[]} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Cell(props: { children: React.ReactNode }) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: 10,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface CellData {
|
||||
content: React.ReactNode;
|
||||
id: string;
|
||||
color?: string;
|
||||
kind: "data" | "header" | "empty";
|
||||
}
|
||||
|
||||
@observer
|
||||
class GridComponent extends React.Component<{
|
||||
rows: {
|
||||
columns: CellData[];
|
||||
}[];
|
||||
markers: {
|
||||
id: string;
|
||||
|
||||
row: number;
|
||||
column: number;
|
||||
rows?: number;
|
||||
columns?: number;
|
||||
|
||||
label?: string;
|
||||
color?: string;
|
||||
}[];
|
||||
}> {
|
||||
private readonly cells = new Map<string, CellInfo>();
|
||||
|
||||
@computed get cellsGrid(): { maxWidth: number; grid: CellInfo[][] } {
|
||||
const unusedIds = new Set(this.cells.keys());
|
||||
|
||||
const rows = this.props.rows;
|
||||
|
||||
const grid = new Array<Array<CellInfo>>();
|
||||
|
||||
let maxWidth = 0;
|
||||
for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
|
||||
const row = rows[rowIdx];
|
||||
maxWidth = Math.max(maxWidth, row.columns.length);
|
||||
const cellRow = new Array<CellInfo>();
|
||||
grid.push(cellRow);
|
||||
|
||||
for (let colIdx = 0; colIdx < row.columns.length; colIdx++) {
|
||||
const col = row.columns[colIdx];
|
||||
const key = col.id;
|
||||
unusedIds.delete(key);
|
||||
let c = this.cells.get(key);
|
||||
if (!c) {
|
||||
// New cell
|
||||
c = new CellInfo(col);
|
||||
this.cells.set(key, c);
|
||||
} else {
|
||||
c.updateCellData(col);
|
||||
}
|
||||
cellRow.push(c);
|
||||
}
|
||||
}
|
||||
|
||||
for (const id in unusedIds.values()) {
|
||||
this.cells.delete(id);
|
||||
}
|
||||
|
||||
return { maxWidth, grid };
|
||||
}
|
||||
|
||||
@computed
|
||||
private get layout(): {
|
||||
cells: {
|
||||
cell: CellInfo;
|
||||
top: number;
|
||||
left: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}[];
|
||||
width: number;
|
||||
height: number;
|
||||
} {
|
||||
const { maxWidth, grid } = this.cellsGrid;
|
||||
|
||||
const maxWidths = new Array<number>();
|
||||
for (let colIdx = 0; colIdx < maxWidth; colIdx++) {
|
||||
let maxWidth = 30;
|
||||
for (const row of grid) {
|
||||
const col = row[colIdx];
|
||||
if (col) {
|
||||
maxWidth = Math.max(maxWidth, col.contentWidth || 0);
|
||||
}
|
||||
}
|
||||
maxWidths.push(maxWidth);
|
||||
}
|
||||
|
||||
const result = new Array<{
|
||||
cell: CellInfo;
|
||||
top: number;
|
||||
left: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}>();
|
||||
|
||||
let width = 0;
|
||||
let height = 0;
|
||||
let top = 0;
|
||||
let left = 0;
|
||||
|
||||
for (const row of grid) {
|
||||
let maxHeight = 0;
|
||||
for (const col of row) {
|
||||
maxHeight = Math.max(maxHeight, col.contentHeight || 0);
|
||||
}
|
||||
|
||||
for (let colIdx = 0; colIdx < row.length; colIdx++) {
|
||||
const maxWidth = maxWidths[colIdx];
|
||||
const col = row[colIdx];
|
||||
result.push({
|
||||
cell: col,
|
||||
top,
|
||||
left,
|
||||
height: maxHeight,
|
||||
width: maxWidth,
|
||||
});
|
||||
height = Math.max(height, top + maxHeight);
|
||||
width = Math.max(width, left + maxWidth);
|
||||
col.lastTop = col.top;
|
||||
col.lastLeft = col.left;
|
||||
col.top = top;
|
||||
col.left = left;
|
||||
|
||||
col.lastContentArea = col.contentArea;
|
||||
col.contentArea = maxHeight * maxWidth;
|
||||
left += maxWidth;
|
||||
}
|
||||
|
||||
left = 0;
|
||||
top += maxHeight;
|
||||
}
|
||||
|
||||
result.sort((a, b) => a.cell.id.localeCompare(b.cell.id));
|
||||
return { cells: result, height, width };
|
||||
}
|
||||
|
||||
render() {
|
||||
const l = this.layout;
|
||||
return (
|
||||
<div
|
||||
className="component-Grid"
|
||||
style={{
|
||||
position: "relative",
|
||||
height: l.height,
|
||||
width: l.width,
|
||||
}}
|
||||
>
|
||||
{l.cells.map(i => (
|
||||
<div
|
||||
key={i.cell.id}
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: i.top,
|
||||
left: i.left,
|
||||
height: i.height,
|
||||
width: i.width,
|
||||
zIndex: i.cell.distance,
|
||||
transition:
|
||||
i.cell.lastContentArea !== 0 ? "all 1s" : "",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={`part-${i.cell.kind}`}
|
||||
style={
|
||||
{
|
||||
empty: {},
|
||||
data: {
|
||||
display: "flex",
|
||||
overflow: "hidden",
|
||||
justifyContent: "center",
|
||||
margin: 2,
|
||||
},
|
||||
header: {
|
||||
overflow: "hidden",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
margin: 2,
|
||||
},
|
||||
}[i.cell.kind]
|
||||
}
|
||||
>
|
||||
<div
|
||||
ref={i.cell.handleRef}
|
||||
style={{
|
||||
width: "fit-content",
|
||||
height: "fit-content",
|
||||
}}
|
||||
>
|
||||
{i.cell.content}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.updateContentSize();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.updateContentSize();
|
||||
}
|
||||
|
||||
@action
|
||||
updateContentSize() {
|
||||
for (const c of this.cells.values()) {
|
||||
if (c.ref) {
|
||||
const r = c.ref.getBoundingClientRect();
|
||||
c.contentHeight = r.height + 6;
|
||||
c.contentWidth = r.width + 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CellInfo {
|
||||
@observable public contentWidth: number | undefined = undefined;
|
||||
@observable public contentHeight: number | undefined = undefined;
|
||||
|
||||
public contentArea = 0;
|
||||
public lastContentArea = 0;
|
||||
|
||||
public lastTop = 0;
|
||||
public top = 0;
|
||||
public lastLeft = 0;
|
||||
public left = 0;
|
||||
|
||||
public get distance(): number {
|
||||
return Math.floor(
|
||||
Math.pow(this.top - this.lastTop, 2) +
|
||||
Math.pow(this.left - this.lastLeft, 2)
|
||||
);
|
||||
}
|
||||
|
||||
content!: React.ReactNode;
|
||||
|
||||
ref: HTMLDivElement | null = null;
|
||||
|
||||
public readonly id: string;
|
||||
public readonly kind: CellData["kind"];
|
||||
|
||||
constructor(cellData: CellData) {
|
||||
this.id = cellData.id;
|
||||
this.kind = cellData.kind;
|
||||
this.updateCellData(cellData);
|
||||
}
|
||||
|
||||
public updateCellData(cellData: CellData) {
|
||||
this.content = cellData.content;
|
||||
}
|
||||
|
||||
public readonly handleRef = (ref: HTMLDivElement | null) => {
|
||||
this.ref = ref;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import React from "react";
|
||||
import {
|
||||
sOpenObject,
|
||||
sLiteral,
|
||||
sString,
|
||||
sArrayOf,
|
||||
sObject,
|
||||
sOptionalProp,
|
||||
sNumber,
|
||||
} from "@hediet/semantic-json";
|
||||
import { DecoratedGridComponent } from "./GridVisualizer";
|
||||
import {
|
||||
createVisualizer,
|
||||
ReactVisualization,
|
||||
globalVisualizationFactory,
|
||||
} from "@hediet/visualization-core";
|
||||
|
||||
export const sGrid = sOpenObject({
|
||||
kind: sOpenObject({
|
||||
array: sLiteral(true),
|
||||
}),
|
||||
columnLabels: sArrayOf(
|
||||
sOpenObject({
|
||||
label: sOptionalProp(sString(), {}),
|
||||
})
|
||||
),
|
||||
rows: sArrayOf(
|
||||
sOpenObject({
|
||||
label: sOptionalProp(sString(), {}),
|
||||
columns: sArrayOf(
|
||||
sOpenObject({
|
||||
content: sOptionalProp(sString(), {}),
|
||||
tag: sOptionalProp(sString(), {
|
||||
description:
|
||||
"A value to identify this cell. Should be unique.",
|
||||
}),
|
||||
color: sOptionalProp(sString(), {}),
|
||||
})
|
||||
),
|
||||
})
|
||||
),
|
||||
markers: sOptionalProp(
|
||||
sArrayOf(
|
||||
sOpenObject({
|
||||
id: sString(),
|
||||
|
||||
row: sNumber(),
|
||||
column: sNumber(),
|
||||
rows: sOptionalProp(sNumber(), {}),
|
||||
columns: sOptionalProp(sNumber(), {}),
|
||||
|
||||
label: sOptionalProp(sString(), {}),
|
||||
color: sOptionalProp(sString(), {}),
|
||||
})
|
||||
),
|
||||
{}
|
||||
),
|
||||
});
|
||||
|
||||
export const gridVisualizer = createVisualizer({
|
||||
id: "grid",
|
||||
name: "Grid",
|
||||
serializer: sGrid,
|
||||
getVisualization: (data, self) =>
|
||||
new ReactVisualization(self, { priority: 1000 }, () => (
|
||||
<DecoratedGridComponent data={data} />
|
||||
)),
|
||||
});
|
||||
|
||||
globalVisualizationFactory.addVisualizer(gridVisualizer);
|
||||
@@ -0,0 +1,14 @@
|
||||
.component-Grid {
|
||||
.part-data {
|
||||
border: 1px solid lightgray;
|
||||
background: #eeeeee;
|
||||
}
|
||||
|
||||
.part-header {
|
||||
background: transparentize(white, 0.5);
|
||||
}
|
||||
|
||||
.part-empty {
|
||||
color: var(--vscode-editor-foreground);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import { sOpenObject, sLiteral, sString } from "@hediet/semantic-json";
|
||||
import React from "react";
|
||||
import {
|
||||
createVisualizer,
|
||||
ReactVisualization,
|
||||
globalVisualizationFactory,
|
||||
} from "@hediet/visualization-core";
|
||||
import { SvgViewer } from "../svg-visualizer/SvgViewer";
|
||||
import { Observer, observer, disposeOnUnmount } from "mobx-react";
|
||||
import { observable, autorun } from "mobx";
|
||||
|
||||
export const imageVisualizer = createVisualizer({
|
||||
id: "image",
|
||||
name: "Image",
|
||||
serializer: sOpenObject({
|
||||
kind: sOpenObject({
|
||||
imagePng: sLiteral(true),
|
||||
}),
|
||||
base64Data: sString(),
|
||||
}),
|
||||
getVisualization: (data, self) =>
|
||||
new ReactVisualization(self, { priority: 1000 }, () => {
|
||||
return <ImageViewer base64Data={data.base64Data} />;
|
||||
}),
|
||||
});
|
||||
|
||||
@observer
|
||||
class ImageViewer extends React.Component<{ base64Data: string }> {
|
||||
@observable private svg: string | undefined = undefined;
|
||||
|
||||
@disposeOnUnmount
|
||||
private readonly updateSvg = autorun(async () => {
|
||||
const src = `data:image/png;base64,${this.props.base64Data}`;
|
||||
|
||||
const { width, height } = await new Promise<{
|
||||
width: number;
|
||||
height: number;
|
||||
}>(res => {
|
||||
const img = new Image();
|
||||
img.onload = function() {
|
||||
res({ width: img.width, height: img.height });
|
||||
};
|
||||
img.src = src;
|
||||
});
|
||||
|
||||
this.svg = `
|
||||
<svg viewBox="0 0 ${width} ${height}">
|
||||
<defs>
|
||||
<pattern id="Pattern" x="0" y="0" width="14" height="14" patternUnits="userSpaceOnUse">
|
||||
<rect x="0" y="0" width="7" height="7" style="fill: lightgray; stroke-width: 0" />
|
||||
<rect x="7" y="7" width="7" height="7" style="fill: lightgray; stroke-width: 0" />
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect fill="url(#Pattern)" style="stroke-width: 0" width="${width}" height="${height}"/>
|
||||
|
||||
<image style="image-rendering: pixelated" xlink:href="${src}" />
|
||||
</svg>`;
|
||||
});
|
||||
|
||||
render() {
|
||||
if (!this.svg) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <SvgViewer svgContent={this.svg} />;
|
||||
}
|
||||
}
|
||||
|
||||
globalVisualizationFactory.addVisualizer(imageVisualizer);
|
||||
11
visualization-bundle/src/visualizers/index.ts
Normal file
11
visualization-bundle/src/visualizers/index.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export * from "./ast-visualizer";
|
||||
export * from "./graph";
|
||||
export * from "./grid-visualizer";
|
||||
export * from "./image-visualizer";
|
||||
export * from "./monaco-text-visualizer";
|
||||
export * from "./perspective-table-visualizer";
|
||||
export * from "./plotly-visualizer";
|
||||
export * from "./simple-text-visualizer";
|
||||
export * from "./source-visualizer";
|
||||
export * from "./svg-visualizer";
|
||||
export * from "./tree-visualizer";
|
||||
@@ -0,0 +1,103 @@
|
||||
import 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 MonacoEditor extends React.Component<{
|
||||
text: string;
|
||||
languageId: string;
|
||||
theme: Theme;
|
||||
}> {
|
||||
@observable private editor:
|
||||
| monacoTypes.editor.IStandaloneCodeEditor
|
||||
| undefined;
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.editor) {
|
||||
this.editor.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private model: monacoTypes.editor.ITextModel | undefined = undefined;
|
||||
|
||||
@disposeOnUnmount
|
||||
private _updateText = autorun(() => {
|
||||
if (this.editor) {
|
||||
const model = getLoadedMonaco().editor.createModel(
|
||||
this.props.text,
|
||||
this.props.languageId,
|
||||
undefined
|
||||
);
|
||||
|
||||
this.editor.setModel(model);
|
||||
|
||||
/* this.editor!.updateOptions({ readOnly: false });
|
||||
|
||||
setTimeout(() => {
|
||||
const a = this.editor!.getAction(
|
||||
"editor.action.formatDocument"
|
||||
);
|
||||
a.run().then(() => {
|
||||
this.editor!.updateOptions({ readOnly: true });
|
||||
});
|
||||
}, 200);*/
|
||||
if (this.model) {
|
||||
this.model.dispose();
|
||||
}
|
||||
this.model = model;
|
||||
}
|
||||
});
|
||||
|
||||
private readonly setEditorDiv = (editorDiv: HTMLDivElement) => {
|
||||
if (!editorDiv) {
|
||||
return;
|
||||
}
|
||||
this.editor = getLoadedMonaco().editor.create(editorDiv, {
|
||||
model: null,
|
||||
automaticLayout: true,
|
||||
scrollBeyondLastLine: false,
|
||||
minimap: { enabled: false },
|
||||
fixedOverflowWidgets: true,
|
||||
readOnly: true,
|
||||
theme: this.props.theme.kind === "dark" ? "vs-dark" : "vs",
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="component-monaco-editor">
|
||||
<div className="part-editor" ref={this.setEditorDiv} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import {
|
||||
sOpenObject,
|
||||
sLiteral,
|
||||
sString,
|
||||
sOptionalProp,
|
||||
} from "@hediet/semantic-json";
|
||||
import React from "react";
|
||||
import { getLanguageId, MonacoEditor } from "./MonacoEditor";
|
||||
import {
|
||||
createVisualizer,
|
||||
ReactVisualization,
|
||||
globalVisualizationFactory,
|
||||
} from "@hediet/visualization-core";
|
||||
|
||||
export const monacoTextVisualizer = createVisualizer({
|
||||
id: "monaco-text",
|
||||
name: "Text",
|
||||
serializer: sOpenObject({
|
||||
kind: sOpenObject({
|
||||
text: sLiteral(true),
|
||||
}),
|
||||
text: sString(),
|
||||
fileName: sOptionalProp(sString(), {
|
||||
description:
|
||||
"An optional filename that might be used for chosing a syntax highlighter",
|
||||
}),
|
||||
}),
|
||||
getVisualization: (data, self) =>
|
||||
new ReactVisualization(self, { priority: 500 }, theme => {
|
||||
let id = "text";
|
||||
if (data.fileName) {
|
||||
id = getLanguageId(data.fileName);
|
||||
}
|
||||
return (
|
||||
<MonacoEditor text={data.text} languageId={id} theme={theme} />
|
||||
);
|
||||
}),
|
||||
});
|
||||
|
||||
globalVisualizationFactory.addVisualizer(monacoTextVisualizer);
|
||||
@@ -0,0 +1,22 @@
|
||||
.component-monaco-editor {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import React from "react";
|
||||
import { sOpenObject, sLiteral, sArrayOf, sAny } from "@hediet/semantic-json";
|
||||
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 {
|
||||
createVisualizer,
|
||||
ReactVisualization,
|
||||
globalVisualizationFactory,
|
||||
} from "@hediet/visualization-core";
|
||||
|
||||
export const sTable = sOpenObject({
|
||||
kind: sOpenObject({
|
||||
table: sLiteral(true),
|
||||
}),
|
||||
rows: sArrayOf(
|
||||
sAny() // object
|
||||
),
|
||||
});
|
||||
|
||||
export const perspectiveTableVisualizer = createVisualizer({
|
||||
id: "perspective-table",
|
||||
name: "Perspective Table",
|
||||
serializer: sTable,
|
||||
getVisualization: (data, self) =>
|
||||
new ReactVisualization(self, { priority: 1000 }, theme => (
|
||||
<PerspectiveDataViewer
|
||||
className={
|
||||
{
|
||||
light: "perspective-viewer-material",
|
||||
dark: "perspective-viewer-material-dark",
|
||||
}[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);
|
||||
@@ -0,0 +1,187 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import Plot from "react-plotly.js";
|
||||
|
||||
@observer
|
||||
export class PlotlyViewer extends React.Component<{
|
||||
data: Plotly.Data[];
|
||||
layout?: Partial<Plotly.Layout>;
|
||||
theme: "light" | "dark";
|
||||
}> {
|
||||
render() {
|
||||
const { theme, data, layout } = this.props;
|
||||
return (
|
||||
<Plot
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
data={data}
|
||||
layout={Object.assign(
|
||||
{},
|
||||
theme === "dark" ? darkLayout : {},
|
||||
layout || {}
|
||||
)}
|
||||
config={{ responsive: true }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// extracted from the plotly website
|
||||
const darkLayout: Partial<Plotly.Layout> = {
|
||||
colorway: [
|
||||
"#636efa",
|
||||
"#EF553B",
|
||||
"#00cc96",
|
||||
"#ab63fa",
|
||||
"#19d3f3",
|
||||
"#e763fa",
|
||||
"#fecb52",
|
||||
"#ffa15a",
|
||||
"#ff6692",
|
||||
"#b6e880",
|
||||
],
|
||||
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" },
|
||||
title: { x: 0.05 },
|
||||
paper_bgcolor: "rgb(17,17,17)",
|
||||
plot_bgcolor: "rgb(17,17,17)",
|
||||
hovermode: "closest",
|
||||
polar: {
|
||||
bgcolor: "rgb(17,17,17)",
|
||||
angularaxis: {
|
||||
gridcolor: "#506784",
|
||||
linecolor: "#506784",
|
||||
ticks: "",
|
||||
},
|
||||
radialaxis: {
|
||||
gridcolor: "#506784",
|
||||
linecolor: "#506784",
|
||||
ticks: "",
|
||||
},
|
||||
},
|
||||
ternary: {
|
||||
bgcolor: "rgb(17,17,17)",
|
||||
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: {
|
||||
backgroundcolor: "rgb(17,17,17)",
|
||||
gridcolor: "#506784",
|
||||
linecolor: "#506784",
|
||||
showbackground: true,
|
||||
ticks: "",
|
||||
zerolinecolor: "#C8D4E3",
|
||||
gridwidth: 2,
|
||||
},
|
||||
yaxis: {
|
||||
backgroundcolor: "rgb(17,17,17)",
|
||||
gridcolor: "#506784",
|
||||
linecolor: "#506784",
|
||||
showbackground: true,
|
||||
ticks: "",
|
||||
zerolinecolor: "#C8D4E3",
|
||||
gridwidth: 2,
|
||||
},
|
||||
zaxis: {
|
||||
backgroundcolor: "rgb(17,17,17)",
|
||||
gridcolor: "#506784",
|
||||
linecolor: "#506784",
|
||||
showbackground: true,
|
||||
ticks: "",
|
||||
zerolinecolor: "#C8D4E3",
|
||||
gridwidth: 2,
|
||||
},
|
||||
},
|
||||
/*
|
||||
shapedefaults: {
|
||||
fillcolor: "#f2f5fa",
|
||||
line: { width: 0 },
|
||||
opacity: 0.4,
|
||||
},
|
||||
annotationdefaults: {
|
||||
arrowcolor: "#f2f5fa",
|
||||
arrowhead: 0,
|
||||
arrowwidth: 1,
|
||||
},*/
|
||||
geo: {
|
||||
bgcolor: "rgb(17,17,17)",
|
||||
landcolor: "rgb(17,17,17)",
|
||||
subunitcolor: "#506784",
|
||||
showland: true,
|
||||
showlakes: true,
|
||||
lakecolor: "rgb(17,17,17)",
|
||||
},
|
||||
/*
|
||||
updatemenudefaults: { bgcolor: "#506784", borderwidth: 0 },
|
||||
sliderdefaults: {
|
||||
bgcolor: "#C8D4E3",
|
||||
borderwidth: 1,
|
||||
bordercolor: "rgb(17,17,17)",
|
||||
tickwidth: 0,
|
||||
},
|
||||
*/
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
import * as React from "react";
|
||||
import {
|
||||
sOpenObject,
|
||||
sLiteral,
|
||||
sArrayOf,
|
||||
sOptionalProp,
|
||||
sAny,
|
||||
sProp,
|
||||
} from "@hediet/semantic-json";
|
||||
import { makeLazyLoadable } from "../../utils/LazyLoadable";
|
||||
import {
|
||||
createVisualizer,
|
||||
ReactVisualization,
|
||||
globalVisualizationFactory,
|
||||
} from "@hediet/visualization-core";
|
||||
|
||||
const PlotlyViewerLazyLoadable = makeLazyLoadable(
|
||||
async () => (await import("./PlotlyViewer")).PlotlyViewer
|
||||
);
|
||||
|
||||
export const plotlyVisualizer = createVisualizer({
|
||||
id: "plotly",
|
||||
name: "plotly",
|
||||
serializer: sOpenObject({
|
||||
kind: sOpenObject({
|
||||
plotly: sLiteral(true),
|
||||
}),
|
||||
data: sProp(sArrayOf(sAny()), {
|
||||
description:
|
||||
"Expecting Plotly.Data[] (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/795ce172038dbafcb9cba030d637d733a7eea19c/types/plotly.js/index.d.ts#L1036)",
|
||||
}),
|
||||
layout: sOptionalProp(sAny(), {
|
||||
description:
|
||||
"Expecting Partial<Plotly.Layout> (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/795ce172038dbafcb9cba030d637d733a7eea19c/types/plotly.js/index.d.ts#L329)",
|
||||
}),
|
||||
}),
|
||||
getVisualization: (data, self) =>
|
||||
new ReactVisualization(self, { priority: 1000 }, theme => (
|
||||
<PlotlyViewerLazyLoadable
|
||||
data={data.data}
|
||||
layout={data.layout}
|
||||
theme={theme.kind}
|
||||
/>
|
||||
)),
|
||||
});
|
||||
|
||||
globalVisualizationFactory.addVisualizer(plotlyVisualizer);
|
||||
@@ -0,0 +1,33 @@
|
||||
import { sOpenObject, sLiteral, sString } from "@hediet/semantic-json";
|
||||
import React from "react";
|
||||
import {
|
||||
createVisualizer,
|
||||
ReactVisualization,
|
||||
globalVisualizationFactory,
|
||||
} from "@hediet/visualization-core";
|
||||
|
||||
export const simpleTextVisualizer = createVisualizer({
|
||||
id: "simple-text",
|
||||
name: "Simple Text",
|
||||
serializer: sOpenObject({
|
||||
kind: sOpenObject({
|
||||
text: sLiteral(true),
|
||||
}),
|
||||
text: sString(),
|
||||
}),
|
||||
getVisualization: (data, self) =>
|
||||
new ReactVisualization(self, { priority: 100 }, () => (
|
||||
<pre
|
||||
className="visualizer-simple-text"
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: 10,
|
||||
color: "var(--visualizer-simple-text-color)",
|
||||
}}
|
||||
>
|
||||
{data.text}
|
||||
</pre>
|
||||
)),
|
||||
});
|
||||
|
||||
globalVisualizationFactory.addVisualizer(simpleTextVisualizer);
|
||||
@@ -0,0 +1,6 @@
|
||||
.theme-dark {
|
||||
--visualizer-simple-text-color: white;
|
||||
}
|
||||
.theme-light {
|
||||
--visualizer-simple-text-color: black;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { sAny } from "@hediet/semantic-json";
|
||||
import React from "react";
|
||||
import {
|
||||
createVisualizer,
|
||||
ReactVisualization,
|
||||
globalVisualizationFactory,
|
||||
} from "@hediet/visualization-core";
|
||||
import { MonacoEditor } from "../monaco-text-visualizer/MonacoEditor";
|
||||
|
||||
export const sourceVisualizer = createVisualizer({
|
||||
id: "source",
|
||||
name: "JSON Source",
|
||||
serializer: sAny(),
|
||||
getVisualization: (data, self) =>
|
||||
new ReactVisualization(self, { priority: -100 }, theme => (
|
||||
<MonacoEditor
|
||||
text={JSON.stringify(data, undefined, 4)}
|
||||
languageId={"json"}
|
||||
theme={theme}
|
||||
/>
|
||||
)),
|
||||
});
|
||||
|
||||
globalVisualizationFactory.addHiddenVisualizer(sourceVisualizer);
|
||||
@@ -0,0 +1,6 @@
|
||||
.theme-dark {
|
||||
--visualizer-simple-text-color: white;
|
||||
}
|
||||
.theme-light {
|
||||
--visualizer-simple-text-color: black;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import Measure, { ContentRect } from "react-measure";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { observable, action } from "mobx";
|
||||
import { Tool, ReactSVGPanZoom, Value } from "react-svg-pan-zoom";
|
||||
|
||||
function widthOrDefault(r: ContentRect): number {
|
||||
if (r.bounds && r.bounds.width) {
|
||||
return r.bounds.width;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
function heightOrDefault(r: ContentRect): number {
|
||||
if (r.bounds && r.bounds.height) {
|
||||
return r.bounds.height;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class SvgViewer extends React.Component<{
|
||||
svgContent: string;
|
||||
svgRef?: (element: SVGSVGElement | null) => void;
|
||||
}> {
|
||||
@observable tool: Tool = "pan";
|
||||
|
||||
@action.bound
|
||||
private setTool(tool: Tool): void {
|
||||
this.tool = tool;
|
||||
}
|
||||
|
||||
private readonly svgPanZoomRef = React.createRef<ReactSVGPanZoom>();
|
||||
|
||||
componentDidMount() {
|
||||
const element = this.svgPanZoomRef.current;
|
||||
if (this.props.svgRef) {
|
||||
if (!element) {
|
||||
this.props.svgRef(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const svg = (element as any).ViewerDOM as SVGSVGElement;
|
||||
this.props.svgRef(svg);
|
||||
}
|
||||
}
|
||||
|
||||
@observable value: Value | {} = {};
|
||||
|
||||
render() {
|
||||
let { svgContent } = this.props;
|
||||
let width: number = 0;
|
||||
let height: number = 0;
|
||||
svgContent = svgContent.replace(
|
||||
/viewBox="[0-9\.]+ [0-9\.]+ ([0-9\.]+) ([0-9\.]+)"/,
|
||||
(r, w, h) => {
|
||||
width = parseFloat(w);
|
||||
height = parseFloat(h);
|
||||
return "";
|
||||
}
|
||||
);
|
||||
const tool = this.tool;
|
||||
const val = this.value;
|
||||
|
||||
return (
|
||||
<Measure
|
||||
bounds
|
||||
onResize={() => {
|
||||
if (this.svgPanZoomRef.current) {
|
||||
(this.svgPanZoomRef.current.fitToViewer as any)(
|
||||
"center",
|
||||
"center"
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{({ measureRef, contentRect }) => (
|
||||
<div
|
||||
ref={measureRef}
|
||||
className="svgViewer"
|
||||
style={{ width: "100%", height: "100%" }}
|
||||
>
|
||||
<ReactSVGPanZoom
|
||||
width={widthOrDefault(contentRect)}
|
||||
height={heightOrDefault(contentRect)}
|
||||
tool={tool}
|
||||
value={val as any}
|
||||
onChangeValue={v => (this.value = v)}
|
||||
onChangeTool={this.setTool}
|
||||
ref={this.svgPanZoomRef}
|
||||
toolbarProps={{
|
||||
SVGAlignX: "center",
|
||||
SVGAlignY: "center",
|
||||
}}
|
||||
miniatureProps={{
|
||||
position: "none",
|
||||
height: 0,
|
||||
width: 0,
|
||||
background: "black",
|
||||
}}
|
||||
>
|
||||
<svg width={width} height={height}>
|
||||
<g
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: svgContent,
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
</ReactSVGPanZoom>
|
||||
</div>
|
||||
)}
|
||||
</Measure>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
import { sOpenObject, sLiteral, sString } from "@hediet/semantic-json";
|
||||
import { SvgViewer } from "./SvgViewer";
|
||||
import {
|
||||
createVisualizer,
|
||||
ReactVisualization,
|
||||
globalVisualizationFactory,
|
||||
} from "@hediet/visualization-core";
|
||||
|
||||
export const svgVisualizer = createVisualizer({
|
||||
id: "svg",
|
||||
name: "Svg",
|
||||
serializer: sOpenObject({
|
||||
kind: sOpenObject({
|
||||
svg: sLiteral(true),
|
||||
}),
|
||||
text: sString(),
|
||||
}),
|
||||
getVisualization: (data, self) =>
|
||||
new ReactVisualization(self, { priority: 1500 }, () => (
|
||||
<SvgViewer svgContent={data.text} />
|
||||
)),
|
||||
});
|
||||
|
||||
globalVisualizationFactory.addVisualizer(svgVisualizer);
|
||||
@@ -0,0 +1,5 @@
|
||||
.svgViewer {
|
||||
svg {
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
256
visualization-bundle/src/visualizers/tree-visualizer/Point.ts
Normal file
256
visualization-bundle/src/visualizers/tree-visualizer/Point.ts
Normal file
@@ -0,0 +1,256 @@
|
||||
function sqr(a: number) {
|
||||
return a * a;
|
||||
}
|
||||
|
||||
export type PointLike =
|
||||
| Point
|
||||
| { x?: number; y: number }
|
||||
| { x: number; y?: number };
|
||||
|
||||
export function point(data: PointLike) {
|
||||
if (data instanceof Point) {
|
||||
return data;
|
||||
}
|
||||
return new Point(data.x || 0, data.y || 0);
|
||||
}
|
||||
|
||||
export class Point {
|
||||
public static readonly Zero = new Point(0, 0);
|
||||
|
||||
constructor(public readonly x: number, public readonly y: number) {}
|
||||
|
||||
public mapXY(fn: (arg: number) => number): Point {
|
||||
return new Point(fn(this.x), fn(this.y));
|
||||
}
|
||||
|
||||
public distance(other: PointLike = Point.Zero): number {
|
||||
const d = this.sub(other);
|
||||
return Math.sqrt(sqr(d.x) + sqr(d.y));
|
||||
}
|
||||
|
||||
public sub(other: PointLike): Point {
|
||||
const o = point(other);
|
||||
return new Point(this.x - o.x, this.y - o.y);
|
||||
}
|
||||
|
||||
public add(other: PointLike): Point {
|
||||
const o = point(other);
|
||||
return new Point(this.x + o.x, this.y + o.y);
|
||||
}
|
||||
|
||||
public mul(scalar: number): Point {
|
||||
return new Point(this.x * scalar, this.y * scalar);
|
||||
}
|
||||
|
||||
public div(scalar: number): Point {
|
||||
return new Point(this.x / scalar, this.y / scalar);
|
||||
}
|
||||
|
||||
public equals(other: PointLike) {
|
||||
const o = point(other);
|
||||
return this.x === o.x && this.y === o.y;
|
||||
}
|
||||
|
||||
public angle(): number {
|
||||
const angle = Math.atan2(this.x, this.y);
|
||||
return angle;
|
||||
}
|
||||
|
||||
public getPointCloserTo(dest: PointLike, dist: number): Point {
|
||||
if (this.equals(dest)) return this;
|
||||
|
||||
const angle = point(dest)
|
||||
.sub(this)
|
||||
.angle();
|
||||
|
||||
const result = new Point(
|
||||
this.x + Math.sin(angle) * dist,
|
||||
this.y + Math.cos(angle) * dist
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
public toJson(): { x: number; y: number } {
|
||||
return { x: this.x, y: this.y };
|
||||
}
|
||||
}
|
||||
|
||||
function turn(p1: Point, p2: Point, p3: Point): number {
|
||||
const A = (p3.y - p1.y) * (p2.x - p1.x);
|
||||
const B = (p2.y - p1.y) * (p3.x - p1.x);
|
||||
return A > B + Number.MIN_VALUE ? 1 : A + Number.MIN_VALUE < B ? -1 : 0;
|
||||
}
|
||||
|
||||
export function isIntersect(
|
||||
aStart: Point,
|
||||
aEnd: Point,
|
||||
bStart: Point,
|
||||
bEnd: Point
|
||||
): boolean {
|
||||
return (
|
||||
turn(aStart, bStart, bEnd) != turn(aEnd, bStart, bEnd) &&
|
||||
turn(aStart, aEnd, bStart) != turn(aStart, aEnd, bEnd)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Liang-Barsky function by Daniel White
|
||||
*
|
||||
* @link http://www.skytopia.com/project/articles/compsci/clipping.html
|
||||
*
|
||||
* @param {number} x0
|
||||
* @param {number} y0
|
||||
* @param {number} x1
|
||||
* @param {number} y1
|
||||
* @param {array<number>} bbox
|
||||
* @return {array<array<number>>|null}
|
||||
*/
|
||||
function liangBarsky(
|
||||
x0: number,
|
||||
y0: number,
|
||||
x1: number,
|
||||
y1: number,
|
||||
bbox: [number, number, number, number]
|
||||
): [[number, number], [number, number]] | null {
|
||||
let [xmin, xmax, ymin, ymax] = bbox;
|
||||
let t0 = 0,
|
||||
t1 = 1;
|
||||
let dx = x1 - x0,
|
||||
dy = y1 - y0;
|
||||
let p = 0,
|
||||
q = 0,
|
||||
r = 0;
|
||||
|
||||
for (let edge: 0 | 1 | 2 | 3 = 0; edge < 4; edge++) {
|
||||
// Traverse through left, right, bottom, top edges.
|
||||
if (edge === 0) {
|
||||
p = -dx;
|
||||
q = -(xmin - x0);
|
||||
} else if (edge === 1) {
|
||||
p = dx;
|
||||
q = xmax - x0;
|
||||
} else if (edge === 2) {
|
||||
p = -dy;
|
||||
q = -(ymin - y0);
|
||||
} else if (edge === 3) {
|
||||
p = dy;
|
||||
q = ymax - y0;
|
||||
}
|
||||
|
||||
r = q / p;
|
||||
|
||||
if (p === 0 && q < 0) return null; // Don't draw line at all. (parallel line outside)
|
||||
|
||||
if (p < 0) {
|
||||
if (r > t1) return null;
|
||||
// Don't draw line at all.
|
||||
else if (r > t0) t0 = r; // Line is clipped!
|
||||
} else if (p > 0) {
|
||||
if (r < t0) return null;
|
||||
// Don't draw line at all.
|
||||
else if (r < t1) t1 = r; // Line is clipped!
|
||||
}
|
||||
}
|
||||
|
||||
return [[x0 + t0 * dx, y0 + t0 * dy], [x0 + t1 * dx, y0 + t1 * dy]];
|
||||
}
|
||||
|
||||
export function intersectRectWithLine(
|
||||
start: Point,
|
||||
end: Point,
|
||||
rect: Rectangle
|
||||
): { first: Point; second: Point } | undefined {
|
||||
const r = liangBarsky(start.x, start.y, end.x, end.y, [
|
||||
rect.topLeft.x,
|
||||
rect.bottomRight.x,
|
||||
rect.topLeft.y,
|
||||
rect.bottomRight.y,
|
||||
]);
|
||||
if (!r) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
first: new Point(r[0][0], r[0][1]),
|
||||
second: new Point(r[1][0], r[1][1]),
|
||||
};
|
||||
}
|
||||
|
||||
// first zoom then translate
|
||||
export function scale(
|
||||
clientOffset: Point,
|
||||
clientSize: Point,
|
||||
viewSize: Point
|
||||
): { clientZoom: number; clientOffset: Point } {
|
||||
const clientRatio = clientSize.x / clientSize.y;
|
||||
const viewRatio = viewSize.x / viewSize.y;
|
||||
|
||||
let zoom = 1;
|
||||
|
||||
if (clientRatio < viewRatio) zoom = viewSize.y / clientSize.y;
|
||||
else zoom = viewSize.x / clientSize.x;
|
||||
|
||||
const clientMid = clientOffset.mul(zoom).add(clientSize.mul(zoom / 2));
|
||||
const viewMid = viewSize.div(2);
|
||||
|
||||
const clientOffset2 = viewMid.sub(clientMid);
|
||||
|
||||
return { clientOffset: clientOffset2, clientZoom: zoom };
|
||||
}
|
||||
|
||||
export class Rectangle {
|
||||
public static ofSize(position: PointLike, size: PointLike): Rectangle {
|
||||
const pos = point(position);
|
||||
return new Rectangle(pos, pos.add(size));
|
||||
}
|
||||
|
||||
public static spanning(first: Point, ...rest: Point[]): Rectangle {
|
||||
let min = first.toJson();
|
||||
let max = first.toJson();
|
||||
for (const p of rest) {
|
||||
min.x = Math.min(min.x, p.x);
|
||||
min.y = Math.min(min.y, p.y);
|
||||
max.x = Math.max(max.x, p.x);
|
||||
max.y = Math.max(max.y, p.y);
|
||||
}
|
||||
|
||||
return new Rectangle(point(min), point(max));
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly topLeft: Point,
|
||||
public readonly bottomRight: Point
|
||||
) {}
|
||||
|
||||
get center(): Point {
|
||||
return this.bottomRight.add(this.topLeft).div(2);
|
||||
}
|
||||
|
||||
get size(): Point {
|
||||
return this.bottomRight.sub(this.topLeft);
|
||||
}
|
||||
|
||||
get topRight(): Point {
|
||||
return new Point(this.bottomRight.x, this.topLeft.y);
|
||||
}
|
||||
|
||||
get bottomLeft(): Point {
|
||||
return new Point(this.topLeft.x, this.bottomRight.y);
|
||||
}
|
||||
|
||||
public intersects(selectionRect: Rectangle): boolean {
|
||||
return !(
|
||||
selectionRect.topLeft.x > this.bottomRight.x ||
|
||||
selectionRect.bottomRight.x < this.topLeft.x ||
|
||||
selectionRect.topLeft.y > this.bottomRight.y ||
|
||||
selectionRect.bottomRight.y < this.topLeft.y
|
||||
);
|
||||
}
|
||||
|
||||
equals(other: Rectangle) {
|
||||
return (
|
||||
this.topLeft.equals(other.topLeft) &&
|
||||
this.bottomRight.equals(other.bottomRight)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Point, Rectangle } from "./Point";
|
||||
import * as React from "react";
|
||||
import { Properties } from "csstype";
|
||||
|
||||
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> {
|
||||
const newObj: Omit<T, K> = {} as any;
|
||||
for (const [key, val] of Object.entries(obj)) {
|
||||
if (!keys.includes(key as K)) {
|
||||
(newObj as any)[key] = val;
|
||||
}
|
||||
}
|
||||
return newObj;
|
||||
}
|
||||
|
||||
export interface SvgAttributes<T> extends React.DOMAttributes<T> {
|
||||
stroke?: string;
|
||||
className?: string;
|
||||
style?: Properties;
|
||||
pointerEvents?: "none" | "auto" | "initial";
|
||||
}
|
||||
|
||||
export function SvgText(
|
||||
props: {
|
||||
position: Point;
|
||||
children: any;
|
||||
childRef?: React.Ref<SVGTextElement>;
|
||||
textAnchor?: "middle" | "end" | "start";
|
||||
dominantBaseline?: "central" | "middle";
|
||||
} & SvgAttributes<SVGTextElement>
|
||||
) {
|
||||
return (
|
||||
<text
|
||||
x={props.position.x}
|
||||
y={props.position.y}
|
||||
ref={props.childRef}
|
||||
{...omit(props, ["position", "childRef"])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SvgCircle(
|
||||
props: {
|
||||
center: Point;
|
||||
radius: number;
|
||||
childRef?: React.Ref<SVGCircleElement>;
|
||||
} & SvgAttributes<SVGCircleElement>
|
||||
) {
|
||||
return (
|
||||
<circle
|
||||
cx={props.center.x}
|
||||
cy={props.center.y}
|
||||
r={props.radius}
|
||||
ref={props.childRef}
|
||||
{...omit(props, ["center", "radius", "childRef"])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SvgLine(
|
||||
props: { start: Point; end: Point } & SvgAttributes<SVGLineElement>
|
||||
) {
|
||||
return (
|
||||
<line
|
||||
x1={props.start.x}
|
||||
y1={props.start.y}
|
||||
x2={props.end.x}
|
||||
y2={props.end.y}
|
||||
{...omit(props, ["end", "start"])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function SvgRect(
|
||||
props: { rectangle: Rectangle; fill?: string } & SvgAttributes<
|
||||
SVGRectElement
|
||||
>
|
||||
) {
|
||||
const r = props.rectangle;
|
||||
return (
|
||||
<rect
|
||||
x={r.topLeft.x}
|
||||
y={r.topLeft.y}
|
||||
width={r.size.x}
|
||||
height={r.size.y}
|
||||
{...omit(props, ["rectangle"])}
|
||||
/>
|
||||
);
|
||||
}
|
||||
545
visualization-bundle/src/visualizers/tree-visualizer/Views.tsx
Normal file
545
visualization-bundle/src/visualizers/tree-visualizer/Views.tsx
Normal file
@@ -0,0 +1,545 @@
|
||||
import { observer, disposeOnUnmount } from "mobx-react";
|
||||
import { observable, autorun, computed, action, runInAction } from "mobx";
|
||||
import * as React from "react";
|
||||
import * as classNames from "classnames";
|
||||
import { Rectangle, Point } from "./Point";
|
||||
import { SvgLine } from "./SvgElements";
|
||||
import { Item } from ".";
|
||||
|
||||
// import "./style.scss";
|
||||
|
||||
export class TreeViewModel<TData = unknown> {
|
||||
private started = true;
|
||||
start() {
|
||||
this.started = true;
|
||||
this.updateOffsets();
|
||||
}
|
||||
stop() {
|
||||
this.started = false;
|
||||
}
|
||||
|
||||
private version = 0;
|
||||
private lastUpdateVersion = -1;
|
||||
|
||||
@observable public root: TreeNodeViewModel<TData> | undefined = undefined;
|
||||
|
||||
public invalidateOffsets() {
|
||||
this.version++;
|
||||
}
|
||||
|
||||
@action
|
||||
public updateOffsets() {
|
||||
if (!this.started) {
|
||||
return;
|
||||
}
|
||||
if (!this.root) {
|
||||
return;
|
||||
}
|
||||
if (this.lastUpdateVersion === this.version) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastUpdateVersion = this.version;
|
||||
console.log("update");
|
||||
this.root.updateOffsets();
|
||||
}
|
||||
|
||||
public get marked(): TreeNodeViewModel<TData>[] {
|
||||
const result = new Array<TreeNodeViewModel<TData>>();
|
||||
function recurse(node: TreeNodeViewModel<TData>) {
|
||||
if (node.isMarked) {
|
||||
result.push(node);
|
||||
}
|
||||
for (const c of node.children) {
|
||||
recurse(c);
|
||||
}
|
||||
}
|
||||
if (this.root) {
|
||||
recurse(this.root);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@observable selected: TreeNodeViewModel<TData> | undefined = undefined;
|
||||
|
||||
@action public select(current: TreeNodeViewModel<TData> | undefined) {
|
||||
if (this.selected === current) {
|
||||
return;
|
||||
}
|
||||
if (this.selected) {
|
||||
this.selected.isSelected = false;
|
||||
}
|
||||
if (current) {
|
||||
current.isSelected = true;
|
||||
}
|
||||
this.selected = current;
|
||||
}
|
||||
|
||||
@action public toggleSelect(current: TreeNodeViewModel<TData>) {
|
||||
if (this.selected === current) {
|
||||
this.select(undefined);
|
||||
} else {
|
||||
this.select(current);
|
||||
}
|
||||
}
|
||||
|
||||
private selectOnHoverEnabled = false;
|
||||
private selectedBeforeHover:
|
||||
| TreeNodeViewModel<TData>
|
||||
| undefined = undefined;
|
||||
|
||||
@action public handleHoverEvent(
|
||||
current: TreeNodeViewModel<TData> | undefined
|
||||
) {
|
||||
if (this.selectOnHoverEnabled) {
|
||||
if (current) {
|
||||
this.select(current);
|
||||
} else {
|
||||
this.select(this.selectedBeforeHover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setSelectOnHover(enable: boolean) {
|
||||
if (this.selectOnHoverEnabled === enable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
this.selectedBeforeHover = this.selected;
|
||||
} else if (!this.selected) {
|
||||
this.select(this.selectedBeforeHover);
|
||||
}
|
||||
|
||||
this.selectOnHoverEnabled = enable;
|
||||
}
|
||||
}
|
||||
|
||||
export class TreeNodeViewModel<TData = unknown> {
|
||||
public parent: TreeNodeViewModel<TData> | null = null;
|
||||
|
||||
constructor(
|
||||
public readonly treeViewModel: TreeViewModel<TData>,
|
||||
public readonly children: TreeNodeViewModel<TData>[],
|
||||
public readonly items: Item[],
|
||||
public readonly segment: string,
|
||||
public readonly data: TData
|
||||
) {}
|
||||
|
||||
@observable expanderOffsetRect: Rectangle | undefined = undefined;
|
||||
private expanderDiv: HTMLDivElement | null = null;
|
||||
|
||||
@action.bound
|
||||
setExpanderDiv(div: HTMLDivElement | null) {
|
||||
this.expanderDiv = div;
|
||||
this.treeViewModel.invalidateOffsets();
|
||||
}
|
||||
|
||||
@observable public expanded: boolean = false;
|
||||
@observable public isMarked: boolean = false;
|
||||
@observable public isSelected: boolean = false;
|
||||
|
||||
@action
|
||||
public toggleExpanded() {
|
||||
this.expanded = !this.expanded;
|
||||
this.treeViewModel.invalidateOffsets();
|
||||
}
|
||||
|
||||
@computed public get isChildOrThisMarked(): boolean {
|
||||
if (this.isMarked) return true;
|
||||
return this.children.some(c => c.isChildOrThisMarked);
|
||||
}
|
||||
|
||||
@computed public get isChildOrThisSelected(): boolean {
|
||||
if (this.isSelected) return true;
|
||||
return this.children.some(c => c.isChildOrThisSelected);
|
||||
}
|
||||
|
||||
@computed public get isParentOrThisSelected(): boolean {
|
||||
if (this.isSelected) return true;
|
||||
if (this.parent) return this.parent.isParentOrThisSelected;
|
||||
return false;
|
||||
}
|
||||
|
||||
get path(): string[] {
|
||||
const result = new Array<string>();
|
||||
let c: TreeNodeViewModel = this;
|
||||
while (c.parent) {
|
||||
result.unshift(c.segment);
|
||||
c = c.parent;
|
||||
}
|
||||
if (result.length === 0) {
|
||||
result.push("root");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
updateOffsets() {
|
||||
for (const c of this.children) {
|
||||
c.updateOffsets();
|
||||
}
|
||||
|
||||
if (!this.expanderDiv) {
|
||||
return;
|
||||
}
|
||||
const newRect = Rectangle.ofSize(
|
||||
new Point(this.expanderDiv.offsetLeft, this.expanderDiv.offsetTop),
|
||||
new Point(
|
||||
this.expanderDiv.offsetWidth,
|
||||
this.expanderDiv.offsetHeight
|
||||
)
|
||||
);
|
||||
if (
|
||||
!this.expanderOffsetRect ||
|
||||
!newRect.equals(this.expanderOffsetRect)
|
||||
) {
|
||||
this.expanderOffsetRect = newRect;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isValidFunctionName = (function() {
|
||||
var validName = /^[$A-Z_][0-9A-Z_$]*$/i;
|
||||
var reserved = {
|
||||
abstract: true,
|
||||
boolean: true,
|
||||
// ...
|
||||
with: true,
|
||||
} as any;
|
||||
return function(s: string) {
|
||||
// Ensure a valid name and not reserved.
|
||||
return validName.test(s) && !reserved[s];
|
||||
};
|
||||
})();
|
||||
|
||||
@observer
|
||||
export class TreeWithPathView extends React.Component<{
|
||||
model: TreeViewModel;
|
||||
}> {
|
||||
render() {
|
||||
const model = this.props.model;
|
||||
return (
|
||||
<div className="component-TreeWithPathView">
|
||||
<div className="part-path">
|
||||
<span>
|
||||
{model.selected ? (
|
||||
<span>
|
||||
{model.selected.path.reduce((acc, v) => {
|
||||
acc = acc.slice();
|
||||
function add() {
|
||||
acc.push(
|
||||
<span
|
||||
className="part-path-item"
|
||||
key={acc.length}
|
||||
>
|
||||
{v}
|
||||
</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;
|
||||
}, new Array<React.ReactElement>())}
|
||||
</span>
|
||||
) : (
|
||||
"(Nothing Selected)"
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="part-tree">
|
||||
<TreeView model={model} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class TreeView extends React.Component<{ model: TreeViewModel }> {
|
||||
componentWillUpdate(newProps: this["props"]) {
|
||||
newProps.model.stop();
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this.props.model.stop();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.props.model.start();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.model.start();
|
||||
}
|
||||
|
||||
render() {
|
||||
const model = this.props.model;
|
||||
return (
|
||||
<div
|
||||
tabIndex={0}
|
||||
className="component-TreeView"
|
||||
onKeyDown={e => {
|
||||
if (e.keyCode === 18) {
|
||||
// alt
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
model.setSelectOnHover(true);
|
||||
}
|
||||
}}
|
||||
onKeyUp={e => {
|
||||
if (e.keyCode === 18) {
|
||||
// alt
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
model.setSelectOnHover(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{model.root && (
|
||||
<>
|
||||
<svg className="part-svg">
|
||||
<TreeNodeSvgView model={model.root} />
|
||||
</svg>
|
||||
<div className="part-node">
|
||||
<TreeNodeView model={model.root} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class TreeNodeSvgView extends React.Component<{
|
||||
model: TreeNodeViewModel;
|
||||
}> {
|
||||
render() {
|
||||
const model = this.props.model;
|
||||
if (
|
||||
!model.expanderOffsetRect ||
|
||||
model.children.length === 0 ||
|
||||
model.expanded
|
||||
) {
|
||||
return <g />;
|
||||
}
|
||||
|
||||
function findLastExpanderOffset(
|
||||
predicate: (v: TreeNodeViewModel) => boolean
|
||||
): Rectangle | undefined {
|
||||
const all = model.children
|
||||
.filter(predicate)
|
||||
.map(c => c.expanderOffsetRect);
|
||||
const last = all[all.length - 1];
|
||||
return last;
|
||||
}
|
||||
|
||||
const lastOffset = findLastExpanderOffset(() => true);
|
||||
const selectedOffset = findLastExpanderOffset(
|
||||
v => v.isChildOrThisSelected
|
||||
);
|
||||
const markedOffset = findLastExpanderOffset(v => v.isChildOrThisMarked);
|
||||
|
||||
if (!lastOffset) {
|
||||
return <g />;
|
||||
}
|
||||
|
||||
const start = model.expanderOffsetRect.center.add({ y: 10 });
|
||||
|
||||
return (
|
||||
<g className="component-TreeNodeSvgView">
|
||||
{markedOffset && (
|
||||
<SvgLine
|
||||
className="path childOrThisMarked"
|
||||
start={start}
|
||||
end={new Point(start.x, markedOffset.center.y)}
|
||||
/>
|
||||
)}
|
||||
{selectedOffset && (
|
||||
<SvgLine
|
||||
className="path childOrThisSelected"
|
||||
start={start}
|
||||
end={new Point(start.x, selectedOffset.center.y)}
|
||||
/>
|
||||
)}
|
||||
<SvgLine
|
||||
className="path"
|
||||
start={start}
|
||||
end={new Point(start.x, lastOffset.center.y)}
|
||||
/>
|
||||
{model.children.map((c, idx) => {
|
||||
const end = c.expanderOffsetRect!.center.sub({ x: 5 });
|
||||
|
||||
return (
|
||||
<g key={idx}>
|
||||
{c.isChildOrThisMarked && (
|
||||
<SvgLine
|
||||
className={classNames(
|
||||
"path",
|
||||
"childOrThisMarked"
|
||||
)}
|
||||
start={new Point(start.x, end.y)}
|
||||
end={end}
|
||||
/>
|
||||
)}
|
||||
<SvgLine
|
||||
className={classNames(
|
||||
"path",
|
||||
c.isChildOrThisSelected &&
|
||||
"childOrThisSelected"
|
||||
)}
|
||||
start={new Point(start.x, end.y)}
|
||||
end={end}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
})}
|
||||
{model.children.map((c, idx) => (
|
||||
<TreeNodeSvgView key={idx} model={c} />
|
||||
))}
|
||||
</g>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@observer
|
||||
export class TreeNodeView extends React.Component<{
|
||||
model: TreeNodeViewModel;
|
||||
}> {
|
||||
@action.bound
|
||||
private clickHandler() {
|
||||
this.props.model.treeViewModel.toggleSelect(this.props.model);
|
||||
}
|
||||
|
||||
private handleMouseEnterOrLeave(enter: boolean) {
|
||||
this.props.model.treeViewModel.handleHoverEvent(
|
||||
enter ? this.props.model : undefined
|
||||
);
|
||||
}
|
||||
|
||||
@observable
|
||||
private rootDiv: HTMLElement | null = null;
|
||||
@action.bound
|
||||
private setRootDiv(div: HTMLElement | null) {
|
||||
this.rootDiv = div;
|
||||
}
|
||||
|
||||
@disposeOnUnmount
|
||||
private readonly _autorun = autorun(() => {
|
||||
if (this.props.model.isMarked && this.rootDiv) {
|
||||
this.rootDiv.scrollIntoView({ block: "nearest" });
|
||||
}
|
||||
});
|
||||
|
||||
public componentDidMount() {
|
||||
this.props.model.treeViewModel.updateOffsets();
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
this.props.model.treeViewModel.updateOffsets();
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
const model = this.props.model;
|
||||
const collapsed = model.expanded;
|
||||
console.log("render");
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"component-TreeNodeView",
|
||||
model.expanded && "collapsed",
|
||||
model.isMarked && "selected",
|
||||
model.isSelected && "hovered",
|
||||
model.isChildOrThisSelected &&
|
||||
!model.isSelected &&
|
||||
"childHovered",
|
||||
model.isParentOrThisSelected &&
|
||||
!model.isSelected &&
|
||||
"parentHovered"
|
||||
)}
|
||||
ref={this.setRootDiv}
|
||||
>
|
||||
<div
|
||||
className="part-header"
|
||||
onMouseOver={e => {
|
||||
this.handleMouseEnterOrLeave(true);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onMouseOut={e => {
|
||||
this.handleMouseEnterOrLeave(false);
|
||||
e.stopPropagation();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={model.setExpanderDiv}
|
||||
onClick={() => model.toggleExpanded()}
|
||||
className={classNames(
|
||||
"part-collapser",
|
||||
model.children.length > 0 &&
|
||||
"decorator-collapse-icon",
|
||||
!collapsed && "expanded"
|
||||
)}
|
||||
/>
|
||||
|
||||
<span className="part-text" onClick={this.clickHandler}>
|
||||
{renderItems(model.items)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{model.children.length > 0 && !model.expanded && (
|
||||
<div className="part-children">
|
||||
{model.children.map((c, idx) => (
|
||||
<TreeNodeView model={c} key={idx} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function renderItems(items: Item[]): React.ReactNode {
|
||||
return items.map((i, idx) => {
|
||||
switch (i.emphasis) {
|
||||
case "style1":
|
||||
return (
|
||||
<span key={idx} className="style1">
|
||||
{i.text}
|
||||
</span>
|
||||
);
|
||||
case "style2":
|
||||
return (
|
||||
<span key={idx} className="style2">
|
||||
{i.text}
|
||||
</span>
|
||||
);
|
||||
case "style3":
|
||||
return (
|
||||
<span key={idx} className="style3">
|
||||
{i.text}
|
||||
</span>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<span key={idx} className="unstyled">
|
||||
{i.text}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
121
visualization-bundle/src/visualizers/tree-visualizer/index.tsx
Normal file
121
visualization-bundle/src/visualizers/tree-visualizer/index.tsx
Normal file
@@ -0,0 +1,121 @@
|
||||
import {
|
||||
TreeViewModel,
|
||||
TreeNodeViewModel,
|
||||
TreeView,
|
||||
TreeWithPathView,
|
||||
} from "./Views";
|
||||
import * as React from "react";
|
||||
import {
|
||||
sOpenObject,
|
||||
sLiteral,
|
||||
sLazy,
|
||||
sString,
|
||||
sBoolean,
|
||||
sArrayOf,
|
||||
Serializer,
|
||||
sOptionalProp,
|
||||
sUnion,
|
||||
} from "@hediet/semantic-json";
|
||||
import {
|
||||
createVisualizer,
|
||||
ReactVisualization,
|
||||
globalVisualizationFactory,
|
||||
} from "@hediet/visualization-core";
|
||||
import { visualizationNs } from "../../consts";
|
||||
|
||||
type TreeNode<T = {}> = {
|
||||
children: TreeNode<T>[];
|
||||
items: Item[];
|
||||
segment?: string; // like "root", ".name" or "[0]"
|
||||
isMarked?: boolean;
|
||||
} & T;
|
||||
|
||||
export interface Item {
|
||||
text: string;
|
||||
emphasis?: "style1" | "style2" | "style3" | string;
|
||||
}
|
||||
|
||||
const sTreeNode: Serializer<TreeNode> = sLazy(() =>
|
||||
sOpenObject({
|
||||
children: sArrayOf(sTreeNode),
|
||||
items: sArrayOf(
|
||||
sOpenObject({
|
||||
text: sString(),
|
||||
emphasis: sOptionalProp(
|
||||
sUnion(
|
||||
[
|
||||
sLiteral("style1"),
|
||||
sLiteral("style2"),
|
||||
sLiteral("style3"),
|
||||
sString(),
|
||||
],
|
||||
{ inclusive: true }
|
||||
)
|
||||
),
|
||||
}).defineAs(visualizationNs("TreeNodeItem"))
|
||||
),
|
||||
segment: sOptionalProp(sString()),
|
||||
isMarked: sOptionalProp(sBoolean()),
|
||||
}).defineAs(visualizationNs("TreeNode"))
|
||||
);
|
||||
|
||||
const sTree = sOpenObject({
|
||||
kind: sOpenObject({
|
||||
tree: sLiteral(true),
|
||||
}),
|
||||
root: sTreeNode,
|
||||
});
|
||||
|
||||
export const treeVisualizer = createVisualizer({
|
||||
id: "tree",
|
||||
name: "Tree",
|
||||
serializer: sTree,
|
||||
getVisualization: (data, self) =>
|
||||
new ReactVisualization(self, { priority: 1200 }, () => {
|
||||
const m = createTreeViewModelFromTreeNodeData(
|
||||
data.root,
|
||||
() => undefined
|
||||
);
|
||||
return <TreeWithPathView model={m} />;
|
||||
}),
|
||||
});
|
||||
|
||||
globalVisualizationFactory.addVisualizer(treeVisualizer);
|
||||
|
||||
export function createTreeViewModelFromTreeNodeData<TData, TNodeData>(
|
||||
root: TreeNode<TNodeData>,
|
||||
dataSelector: (node: TreeNode<TNodeData>) => TData
|
||||
): TreeViewModel<TData> {
|
||||
const m = new TreeViewModel<TData>();
|
||||
m.root = recurse(root, m);
|
||||
return m;
|
||||
|
||||
function recurse(
|
||||
node: TreeNode<TNodeData>,
|
||||
viewModel: TreeViewModel<TData>
|
||||
): TreeNodeViewModel<TData> {
|
||||
const children: TreeNodeViewModel<TData>[] = node.children.map(c =>
|
||||
recurse(c, viewModel)
|
||||
);
|
||||
|
||||
let segment = node.segment;
|
||||
if (!segment && node.items.length > 0) {
|
||||
segment = node.items[0].text;
|
||||
} else {
|
||||
segment = "";
|
||||
}
|
||||
|
||||
const model = new TreeNodeViewModel<TData>(
|
||||
viewModel,
|
||||
children,
|
||||
node.items,
|
||||
segment,
|
||||
dataSelector(node)
|
||||
);
|
||||
model.isMarked = !!node.isMarked;
|
||||
for (const c of children) {
|
||||
c.parent = model;
|
||||
}
|
||||
return model;
|
||||
}
|
||||
}
|
||||
162
visualization-bundle/src/visualizers/tree-visualizer/style.scss
Normal file
162
visualization-bundle/src/visualizers/tree-visualizer/style.scss
Normal file
@@ -0,0 +1,162 @@
|
||||
.theme-dark {
|
||||
--visualizer-tree-childOrThisMarked-background: orange;
|
||||
--visualizer-tree-childOrThisSelected-background: white;
|
||||
--visualizer-tree-pathColor: gray;
|
||||
|
||||
--visualizer-tree-style1-foreground: #5594ea;
|
||||
--visualizer-tree-style2-foreground: #c88544; //#38b968;
|
||||
--visualizer-tree-style3-foreground: darkred;
|
||||
--visualizer-tree-style3-border: black;
|
||||
--visualizer-tree-unstyled-foreground: lightgray;
|
||||
--visualizer-tree-path-foreground: white;
|
||||
}
|
||||
|
||||
.theme-light {
|
||||
--visualizer-tree-childOrThisMarked-background: orange;
|
||||
--visualizer-tree-childOrThisSelected-background: black;
|
||||
--visualizer-tree-pathColor: gray;
|
||||
|
||||
--visualizer-tree-style1-foreground: blue;
|
||||
--visualizer-tree-style2-foreground: darkgreen;
|
||||
--visualizer-tree-style3-foreground: darkred;
|
||||
--visualizer-tree-style3-border: black;
|
||||
--visualizer-tree-unstyled-foreground: black;
|
||||
--visualizer-tree-path-foreground: black;
|
||||
}
|
||||
|
||||
.component-TreeView {
|
||||
overflow: auto;
|
||||
scroll-behavior: smooth;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: grid;
|
||||
|
||||
& > .part-svg,
|
||||
& > .part-node {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.component-TreeNodeSvgView {
|
||||
.path {
|
||||
stroke: var(--visualizer-tree-pathColor);
|
||||
stroke-dasharray: 1;
|
||||
|
||||
&.childOrThisMarked {
|
||||
stroke: var(--visualizer-tree-childOrThisMarked-background);
|
||||
stroke-width: 5;
|
||||
stroke-dasharray: 0;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
&.childOrThisSelected {
|
||||
stroke: var(--visualizer-tree-childOrThisSelected-background);
|
||||
stroke-width: 2;
|
||||
stroke-dasharray: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.component-TreeNodeView {
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe WPC, Segoe UI,
|
||||
HelveticaNeue-Light, Ubuntu, Droid Sans, sans-serif;
|
||||
|
||||
.part-header {
|
||||
padding: 2px;
|
||||
padding-left: 5px;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.part-collapser {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.part-text {
|
||||
cursor: pointer;
|
||||
span.style1 {
|
||||
color: var(--visualizer-tree-style1-foreground);
|
||||
}
|
||||
span.style2 {
|
||||
color: var(--visualizer-tree-style2-foreground);
|
||||
text-decoration: underline;
|
||||
padding: 1px 3px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
span.style3 {
|
||||
color: var(--visualizer-tree-style3-foreground);
|
||||
border: var(--visualizer-tree-style3-border) solid 1px;
|
||||
padding: 1px 3px;
|
||||
margin-left: 5px;
|
||||
}
|
||||
span.unstyled {
|
||||
color: var(--visualizer-tree-unstyled-foreground);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.part-children {
|
||||
margin-left: 0px;
|
||||
margin-top: 0px;
|
||||
|
||||
padding: 5px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
& > .part-header {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
&.hovered {
|
||||
& > .part-header > span {
|
||||
font-weight: bold;
|
||||
}
|
||||
& > .part-children {
|
||||
background: transparentize(gray, 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.component-TreeWithPathView {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
|
||||
.part-path {
|
||||
overflow: hidden;
|
||||
white-space: pre;
|
||||
padding: 5px 10px;
|
||||
border-bottom: solid gray 1px;
|
||||
|
||||
color: lightgray;
|
||||
|
||||
.part-path-item {
|
||||
color: var(--visualizer-tree-path-foreground);
|
||||
margin-left: 1px;
|
||||
margin-right: 1px;
|
||||
}
|
||||
}
|
||||
.part-tree {
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.decorator-collapse-icon {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath fill='%23646465' d='M6 4v8l4-4-4-4zm1 2.414L8.586 8 7 9.586V6.414z'/%3E%3C/svg%3E");
|
||||
|
||||
background-position: 50%;
|
||||
background-position-x: 50%;
|
||||
background-position-y: center;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
&.expanded {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16'%3E%3Cpath fill='%23646465' d='M11 10.07H5.344L11 4.414v5.656z'/%3E%3C/svg%3E");
|
||||
}
|
||||
}
|
||||
6
visualization-bundle/style.scss
Normal file
6
visualization-bundle/style.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
@import "./src/visualizers/grid-visualizer/style.scss";
|
||||
@import "./src/visualizers/simple-text-visualizer/style.scss";
|
||||
@import "./src/visualizers/monaco-text-visualizer/style.scss";
|
||||
@import "./src/visualizers/tree-visualizer/style.scss";
|
||||
@import "./src/visualizers/ast-visualizer/style.scss";
|
||||
@import "./src/visualizers/svg-visualizer/style.scss";
|
||||
18
visualization-bundle/tsconfig.json
Normal file
18
visualization-bundle/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "es2020",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"outDir": "dist",
|
||||
"skipLibCheck": true,
|
||||
"newLine": "LF",
|
||||
"sourceMap": true,
|
||||
"jsx": "react",
|
||||
"experimentalDecorators": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
59
visualization-core/README.md
Normal file
59
visualization-core/README.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Visualization Core
|
||||
|
||||
## Installation
|
||||
|
||||
You can use yarn to install this library:
|
||||
|
||||
```
|
||||
yarn add @hediet/visualization-core
|
||||
```
|
||||
|
||||
You may want to install `@hediet/visualization-bundle` for the actual visualizations too!
|
||||
|
||||
## Usage
|
||||
|
||||
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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
```
|
||||
442
visualization-core/architecture.drawio.svg
Normal file
442
visualization-core/architecture.drawio.svg
Normal file
@@ -0,0 +1,442 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="801px" height="662px" viewBox="-0.5 -0.5 801 662" content="<mxfile host="db45393e-0ffa-4c0c-be8c-fc04b4d85e84" modified="2020-08-13T13:22:12.385Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Code/1.47.3 Chrome/78.0.3904.130 Electron/7.3.2 Safari/537.36" etag="KzlR1NopTHQEYcp3MlbJ" version="13.1.3"><diagram id="6hGFLwfOUW9BJ-s0fimq" name="Page-1">3Vtbk6I4FP41PnYXhIv62Nq3requ2pqenZ15jBCBGSQuxFbn128CiRCCCEgrPVZ1tRxy4+R837kQR8Z8tXuK4dp/xS4KR0BzdyPjfgSArtk6/cck+0xi2VYm8OLA5Y1ywVvwG4meXLoJXJRIDQnGIQnWstDBUYQcIslgHOOt3GyJQ3nWNfSQInhzYKhK/w1c4mfSCRjn8mcUeL6YWben2Z0VFI35kyQ+dPG2IDIeRsY8xphk31a7OQqZ8oResn6PR+4eFhajiDTpYICsxzsMN/zhXmFE158wnfpoxZdJ9uLZkUtVwS9xTHzs4QiGD7l0FuNN5CI2gUav8jYvGK+pUKfCn4iQPd9XuCGYinyyCvldtAvId9b91uJXPwp37nd85PRiLy4iEu+zTmNLXP8o3sz7pVeiowsTP10sG19VH9dogjexwzVgcIuDsYd4KzMTMd0UunGVPyG8QnRK2oAj4IY+mTHhNh+jEJLgXTYtyC3UO/Tlw93FMdwXGqxxEJGkMNvfTJBPZQlL41AztJJBlNobdm17+iVbQam3WA5eLhOqFLlHQQW5KDXEI0ZpXNXqCjaXW2C11fViPGDa0HoaW4qk+zpFmwr65zGCJEU/pH/fgmQDw+A3nRZHKVPGlVvzAheU4iV10m5eRL87VBsopoJ3FJOAcugdv7EKXDfbOZTQGRbpeEyp3KTp4NZsZN1XalaYCBsU7SSFcNLnA0q8WoVF7VbT7LFk8zd6F2QqUNJ1edSpPEA/ULGuCRW9BVSuS9BW3xDrRMb2WCZX3aon43J780T7MnmX2svk3cXa7Bq+2CRB5KUq9IKEQp7uWU4gKE4GQhxWX8RBnfhYn5iytzyPOPqlBmWvRsCQCP0ROgRzj1HcGJkftn5A0Nsapuja0oBe3q4ljoigm4rwUz+i7qNqNScljIgAZptH2rqwc78QZQvZOQ6xzr6JT/Xi+fTbyNZmMEFvKA4y2x4BG66YXkK6llnJafJbHrs1EosswiBy71hOwuh6jaKU/9zHIDyQbSQyIB2oqn+EqyBkqnpG4TtiOBF7wvuIhnMcUufNZqQhHftUQS0hMf6Fqpqe9iqNqNnsTs057IBkIFNtSKDTFQv6ixnPAmXkuI6xg5Ik5cbFPsuxmIoKpjSUfOvgwU9HvgXfLrn2w70jrr2ryTSOmGWCP6Q0vYR3N0Y5U/qA+E4N0CmhpCyTrCEjl4xW5mB0NwsYiJeMp+nlbCYa0imktly8iBVJLjByv10YptDgHI/Rg5MAZkn3l3QS4+tiFDQHaQGXoBUw28fclorSpkWR3tPaSVXkky22aNdDCEfHRyy/QxqrT0ppbD9ZrEhb9zL19spy+ljZsS807EmTBo2uBl8DcbKHO8Bv3Lwm2RU4ut06JLLlne/k4dpms+Vs05rUZ6emWdu+fWlRnaFU7LRBie4zxfNe55nsVDHZJ0RE3YxVzllct/VRlO4DM+Y0AhyM56g33Ubh3anKTWf7B+3sX7uVs/CbbvbfxOTaYgRM2mGkHNjUYqSD2YrqR8E11sWOzYJEkeiejhPbJq+gn+R1SbPpgnyZfj4mEj2ULy4RiQqgyJHO14x8hq36vstGeskZ2dol90GtGz1/fX25D94fQroX3NkMpsRWdtwX1VVFSuuIGluLypj2p1XGRKjSJYP6eL/XwdWIBzpauyhstf3fhp14SLfjZsst7o42MZlpiruNyh2qy1IKIEZmcDW54KAq4BctblRVN3IYLkLs/FLDwz8XlBXRae+vEhtvTUXw1ieg4ILqEzqkdfnQYG9DviDatRQRDgpV1wzUREmqHlUF6NgKrj45kGwVSFdEkhqv0WceugXbV7Vgtd5RzgDvIYHD0lg5OdOnF9SYUVPUvF4VqM2RnQ4nbyqC2KYoP6t2+VGvcyt4e+jnX6UKXV6w6+90VcUeN67Y9X+EUeWlf5L0vEZ6VvFE6fXyx4/Eu6k+jh/ppmBl8XJmch52xNBgcquZ08KndD7SBLdTII/aD9yqUkbZyXwL0HYgTuYabtisOF8SJQRGDnvdgJefha7a045ZQTvGtVjH/ODaBl78RC0Sse6zAcML8QKGn+BcoK3CDGj9wIxe5j+5yXgr/+GS8fA/</diagram></mxfile>">
|
||||
<defs/>
|
||||
<g>
|
||||
<path d="M 560 180 L 520.03 180.03 L 520.03 300.03 L 360.03 300.03 L 360.01 313.63" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/>
|
||||
<path d="M 360 318.88 L 356.51 311.88 L 360.01 313.63 L 363.51 311.89 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 219px; margin-left: 520px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
Manages them
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="520" y="222" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
Manages them
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 480 150 L 320.03 150.03 L 320.03 183.63" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/>
|
||||
<path d="M 320.03 188.88 L 316.53 181.88 L 320.03 183.63 L 323.53 181.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 140px; margin-left: 391px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
Creates a Visualization for
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="391" y="144" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
Creates a Visualization for
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 640 150 L 670.03 150.03 L 670.03 450.03 L 360.03 450.03 L 360.01 473.63" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/>
|
||||
<path d="M 360 478.88 L 356.51 471.88 L 360.01 473.63 L 363.51 471.89 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 215px; margin-left: 673px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
Creates using registered Visualizers
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="673" y="218" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
Creates using registered Visualizers
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="480" y="120" width="160" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 150px; margin-left: 481px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">
|
||||
#VisualizationFactory
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="560" y="154" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold">
|
||||
#VisualizationFactory
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 320 380 L 320 477.76" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 313.5 465.88 L 320 478.88 L 326.5 465.88" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 420px; margin-left: 410px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
Creates through `BaseSerializer<Visualization>`
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="410" y="423" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
Creates through `BaseSerializer<Visualization>`
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 320 320 L 320 256.37" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/>
|
||||
<path d="M 320 251.12 L 323.5 258.12 L 320 256.37 L 316.5 258.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 280px; margin-left: 260px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
Is being processed by the serializer
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="260" y="283" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
Is being processed by the serializer
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="240" y="320" width="160" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 350px; margin-left: 241px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; ">
|
||||
<span>
|
||||
«interface»
|
||||
</span>
|
||||
<br/>
|
||||
<b>
|
||||
#Visualizer
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="320" y="354" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
«interface»...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 280 480 L 280 386.37" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/>
|
||||
<path d="M 280 381.12 L 283.5 388.12 L 280 386.37 L 276.5 388.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 450px; margin-left: 270px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
#sourceVisualizer
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="270" y="453" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
#sourceVisualizer
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 360 540 L 360.03 580.03 L 440.03 580.03 L 440.03 593.63" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/>
|
||||
<path d="M 440.03 598.88 L 436.53 591.88 L 440.03 593.63 L 443.53 591.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 568px; margin-left: 370px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
Renders into
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="370" y="572" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
Renders into
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 280 540 L 280.03 580.03 L 240.03 580.03 L 240.01 593.63" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/>
|
||||
<path d="M 240 598.88 L 236.51 591.88 L 240.01 593.63 L 243.51 591.89 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 570px; margin-left: 250px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
Gets a theme when rendering
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="250" y="573" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
Gets a theme when rendering
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="240" y="480" width="160" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 510px; margin-left: 320px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: nowrap; ">
|
||||
«interface»
|
||||
<br/>
|
||||
<b>
|
||||
#Visualization
|
||||
</b>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="320" y="514" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle">
|
||||
«interface»...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="160" y="600" width="160" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 630px; margin-left: 240px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: nowrap; ">
|
||||
#Theme
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="240" y="634" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold">
|
||||
#Theme
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="360" y="600" width="160" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 630px; margin-left: 361px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">
|
||||
HTMLDivElement
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="440" y="634" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold">
|
||||
HTMLDivElement
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 480 350 L 402.24 350" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 414.12 343.5 L 401.12 350 L 414.12 356.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 340px; margin-left: 440px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
creates
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="440" y="343" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
creates
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="480" y="320" width="160" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 350px; margin-left: 481px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">
|
||||
<span style="font-weight: 400">
|
||||
«interface»
|
||||
<br/>
|
||||
</span>
|
||||
#createVisualizer
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="560" y="354" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold">
|
||||
«interface»...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 440 510 L 414.12 510" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/>
|
||||
<path d="M 401.12 510 L 414.12 503.5 L 414.12 516.5 Z" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<rect x="440" y="480" width="160" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 510px; margin-left: 441px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">
|
||||
<span style="font-weight: 400">
|
||||
«abstract»
|
||||
</span>
|
||||
<br/>
|
||||
#BaseReactVisualization
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="520" y="514" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold">
|
||||
«abstract»...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 640 510 L 618.12 510" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"/>
|
||||
<path d="M 601.12 510 L 618.12 501.5 L 618.12 518.5 Z" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<rect x="640" y="480" width="160" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 510px; margin-left: 641px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">
|
||||
#ReactVisualization
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="720" y="514" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold">
|
||||
#ReactVisualization
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="240" y="190" width="160" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 220px; margin-left: 241px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">
|
||||
#VisualizationData
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="320" y="224" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold">
|
||||
#VisualizationData
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 160 510 L 233.63 510" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/>
|
||||
<path d="M 238.88 510 L 231.88 513.5 L 233.63 510 L 231.88 506.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 500px; margin-left: 200px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
Renders
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="200" y="503" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
Renders
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 80 540 L 80.03 630.03 L 153.63 630" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/>
|
||||
<path d="M 158.88 630 L 151.88 633.5 L 153.63 630 L 151.88 626.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 571px; margin-left: 80px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
Uses for rendering
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="80" y="574" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
Uses for rendering
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="0" y="480" width="160" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 510px; margin-left: 1px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">
|
||||
#VisualizationView
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="80" y="514" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold">
|
||||
#VisualizationView
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<path d="M 560 60 L 560 113.63" fill="none" stroke="#000000" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/>
|
||||
<path d="M 560 118.88 L 556.5 111.88 L 560 113.63 L 563.5 111.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 90px; margin-left: 560px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; ">
|
||||
Instance of
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="560" y="94" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
|
||||
Instance of
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
<rect x="460" y="0" width="200" height="60" fill="#ffffff" stroke="#000000" pointer-events="all"/>
|
||||
<g transform="translate(-0.5 -0.5)">
|
||||
<switch>
|
||||
<foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
|
||||
<div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 30px; margin-left: 461px;">
|
||||
<div style="box-sizing: border-box; font-size: 0; text-align: center; ">
|
||||
<div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; ">
|
||||
<span style="font-weight: 400">
|
||||
«object»
|
||||
</span>
|
||||
<br style="font-weight: 400"/>
|
||||
#globalVisualizationFactory
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</foreignObject>
|
||||
<text x="560" y="34" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold">
|
||||
«object»...
|
||||
</text>
|
||||
</switch>
|
||||
</g>
|
||||
</g>
|
||||
<switch>
|
||||
<g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/>
|
||||
<a transform="translate(0,-5)" xlink:href="https://desk.draw.io/support/solutions/articles/16000042487" target="_blank">
|
||||
<text text-anchor="middle" font-size="10px" x="50%" y="100%">
|
||||
Viewer does not support full SVG 1.1
|
||||
</text>
|
||||
</a>
|
||||
</switch>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 36 KiB |
45
visualization-core/package.json
Normal file
45
visualization-core/package.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "@hediet/visualization-core",
|
||||
"description": "Provides infrastructure to find registered visualizations for given json data",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/hediet/visualization",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/hediet/visualization.git"
|
||||
},
|
||||
"author": {
|
||||
"name": "Henning Dieterichs",
|
||||
"email": "henning.dieterichs@live.de"
|
||||
},
|
||||
"keywords": [
|
||||
"visualization",
|
||||
"json"
|
||||
],
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "tsc --watch",
|
||||
"build": "tsc"
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"dependencies": {
|
||||
"@hediet/std": "^0.6.0",
|
||||
"@hediet/semantic-json": "^0.3.11",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-measure": "^2.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^16.9.22",
|
||||
"@types/react-dom": "^16.9.5",
|
||||
"typescript": "^3.9.7"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org/"
|
||||
}
|
||||
}
|
||||
56
visualization-core/src/ReactVisualization.tsx
Normal file
56
visualization-core/src/ReactVisualization.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import * as React from "react";
|
||||
import * as ReactDom from "react-dom";
|
||||
import { Visualization, Theme } from "./index";
|
||||
import { Visualizer, VisualizationId, asVisualizationId } from "./Visualizer";
|
||||
|
||||
interface Options {
|
||||
id?: string;
|
||||
name?: string;
|
||||
priority: number;
|
||||
}
|
||||
|
||||
export abstract class BaseReactVisualization implements Visualization<unknown> {
|
||||
public readonly id: VisualizationId;
|
||||
public readonly name: string;
|
||||
public readonly priority: number;
|
||||
|
||||
constructor(
|
||||
public readonly sourceVisualizer: Visualizer,
|
||||
options: Options
|
||||
) {
|
||||
this.id = asVisualizationId(
|
||||
options.id || sourceVisualizer.id.toString()
|
||||
);
|
||||
this.name = options.name || sourceVisualizer.name;
|
||||
this.priority = options.priority;
|
||||
}
|
||||
|
||||
public render(
|
||||
target: HTMLDivElement,
|
||||
theme: Theme,
|
||||
previous: unknown | undefined
|
||||
): unknown {
|
||||
const node = this.getReactNode(theme);
|
||||
// We could use this to get access to theme vars:
|
||||
// getComputedStyle(target).getPropertyValue("--background-color")
|
||||
|
||||
ReactDom.render(<>{node}</>, target);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
9
visualization-core/src/Theme.ts
Normal file
9
visualization-core/src/Theme.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export class Theme {
|
||||
public static readonly light = new Theme("light", "light");
|
||||
public static readonly dark = new Theme("dark", "dark");
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
public readonly kind: "light" | "dark"
|
||||
) {}
|
||||
}
|
||||
16
visualization-core/src/VisualizationData.tsx
Normal file
16
visualization-core/src/VisualizationData.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface VisualizationData {
|
||||
kind: Record<string, true>;
|
||||
}
|
||||
|
||||
export function isVisualizationData(val: unknown): val is VisualizationData {
|
||||
if (typeof val !== "object" || !val || !("kind" in val)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const obj = val as any;
|
||||
if (typeof obj.kind !== "object" || !obj.kind) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Object.values(obj.kind).every(val => val === true);
|
||||
}
|
||||
42
visualization-core/src/VisualizationView.tsx
Normal file
42
visualization-core/src/VisualizationView.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import * as React from "react";
|
||||
import { Visualization } from "./Visualizer";
|
||||
import { Theme } from "./Theme";
|
||||
|
||||
export class VisualizationView extends React.Component<{
|
||||
visualization: Visualization;
|
||||
theme: Theme;
|
||||
}> {
|
||||
private readonly ref = React.createRef<HTMLDivElement>();
|
||||
private visualizationRenderState: unknown = undefined;
|
||||
|
||||
componentDidMount() {
|
||||
this.renderVisualization();
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
this.renderVisualization();
|
||||
}
|
||||
|
||||
renderVisualization() {
|
||||
const { visualization, theme } = this.props;
|
||||
this.visualizationRenderState = visualization.render(
|
||||
this.ref.current!,
|
||||
theme,
|
||||
this.visualizationRenderState
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
className={`themeable theme-${this.props.theme.id} visualization`}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
ref={this.ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
37
visualization-core/src/Visualizer.tsx
Normal file
37
visualization-core/src/Visualizer.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
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;
|
||||
}
|
||||
82
visualization-core/src/VisualizerRegistry.ts
Normal file
82
visualization-core/src/VisualizerRegistry.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Visualizer, Visualization, VisualizationId } from "./Visualizer";
|
||||
import { VisualizationData } from "./VisualizationData";
|
||||
import { JSONValue, sUnionMany } from "@hediet/semantic-json";
|
||||
import { ObservableMap } from "mobx";
|
||||
|
||||
export class VisualizationFactory {
|
||||
private readonly visualizers = new ObservableMap<string, Visualizer>();
|
||||
private readonly hiddenVisualizers = new ObservableMap<
|
||||
string,
|
||||
Visualizer
|
||||
>();
|
||||
|
||||
public getSerializer() {
|
||||
return this._getSerializer(this.visualizers);
|
||||
}
|
||||
|
||||
private _getSerializer(map: ObservableMap<string, Visualizer>) {
|
||||
return sUnionMany(
|
||||
[...map.values()].map(v => v.serializer.asSerializer()),
|
||||
{ processingStrategy: "all" }
|
||||
);
|
||||
}
|
||||
|
||||
public addVisualizer(visualizer: Visualizer): void {
|
||||
this.visualizers.set(visualizer.id.toString(), visualizer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public addHiddenVisualizer(visualizer: Visualizer): void {
|
||||
this.hiddenVisualizers.set(visualizer.id.toString(), visualizer);
|
||||
}
|
||||
|
||||
public getVisualizations(
|
||||
data: VisualizationData,
|
||||
preferredVisualization: VisualizationId | undefined
|
||||
): Visualizations {
|
||||
const u = this.getSerializer();
|
||||
const result = u.deserialize((data as unknown) as JSONValue);
|
||||
const allVisualizations = result.value || [];
|
||||
|
||||
const u2 = this._getSerializer(this.hiddenVisualizers);
|
||||
const hiddenResult = u2.deserialize((data as unknown) as JSONValue);
|
||||
allVisualizations.push(...(hiddenResult.value || []));
|
||||
|
||||
allVisualizations.sort((a, b) => b.priority - a.priority);
|
||||
|
||||
let bestVisualization: Visualization | undefined = allVisualizations[0];
|
||||
if (bestVisualization && bestVisualization.priority < 0) {
|
||||
bestVisualization = undefined;
|
||||
}
|
||||
if (preferredVisualization) {
|
||||
const preferred = allVisualizations.find(
|
||||
vis => vis.id === preferredVisualization
|
||||
);
|
||||
if (preferred) {
|
||||
bestVisualization = preferred;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
bestVisualization,
|
||||
allVisualizations,
|
||||
visualizationDataErrors: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export interface Visualizations {
|
||||
bestVisualization: Visualization | undefined;
|
||||
allVisualizations: Visualization[];
|
||||
visualizationDataErrors: VisualizationDataError[];
|
||||
}
|
||||
|
||||
export interface VisualizationDataError {
|
||||
visualizer: Visualizer;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export const globalVisualizationFactory = new VisualizationFactory();
|
||||
29
visualization-core/src/createVisualizer.tsx
Normal file
29
visualization-core/src/createVisualizer.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { BaseSerializer } from "@hediet/semantic-json";
|
||||
import { Visualization, Visualizer, asVisualizerId } from "./Visualizer";
|
||||
|
||||
export function createVisualizer<TData>(options: {
|
||||
/**
|
||||
* A unique id for this visualizer.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* A human readable name of this visualizer.
|
||||
* Should be in English language.
|
||||
*/
|
||||
name: string;
|
||||
serializer: BaseSerializer<TData>;
|
||||
getVisualization: (data: TData, self: Visualizer) => Visualization;
|
||||
}): Visualizer {
|
||||
const result: Visualizer = {
|
||||
id: asVisualizerId(options.id),
|
||||
name: options.name,
|
||||
serializer: options.serializer.refine({
|
||||
fromIntermediate: data => options.getVisualization(data, result),
|
||||
canSerialize: (val): val is Visualization => false,
|
||||
toIntermediate: () => {
|
||||
throw new Error("not supported");
|
||||
},
|
||||
}),
|
||||
};
|
||||
return result;
|
||||
}
|
||||
7
visualization-core/src/index.tsx
Normal file
7
visualization-core/src/index.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
export * from "./VisualizationData";
|
||||
export * from "./ReactVisualization";
|
||||
export * from "./Visualizer";
|
||||
export * from "./VisualizerRegistry";
|
||||
export * from "./VisualizationView";
|
||||
export * from "./Theme";
|
||||
export * from "./createVisualizer";
|
||||
18
visualization-core/tsconfig.json
Normal file
18
visualization-core/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "es2020",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"outDir": "dist",
|
||||
"skipLibCheck": true,
|
||||
"newLine": "LF",
|
||||
"sourceMap": true,
|
||||
"jsx": "react",
|
||||
"experimentalDecorators": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
Reference in New Issue
Block a user