initial commit

This commit is contained in:
Henning Dieterichs
2020-08-12 23:31:12 +02:00
commit 7c4c9348f3
98 changed files with 15500 additions and 0 deletions

51
.gitignore vendored Normal file
View 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
View File

@@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"useTabs": true
}

12
.vscode/launch.json vendored Normal file
View 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
View File

@@ -0,0 +1,3 @@
{
"tasksStatusbar.taskLabelFilter": "dev"
}

21
LICENSE.md Normal file
View 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
View 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
View 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
View 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"
}
}

View 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;
};
}

View 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;
}
}
}

View 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} />;
}
}

View 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>
);
}
}

View 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>
);
}
}

View 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);
}
}

View 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} />;
}
}

View 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>
);
}
}

View 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);
}
}}
/>
);
}
}

View 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;
};

View 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}
/>
);
}
}

View File

@@ -0,0 +1 @@
export const modelCount = { cur: 0 };

File diff suppressed because one or more lines are too long

9
playground/src/index.tsx Normal file
View 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);

View 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}
/>
);
}
}

View 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;
}
}
}

View 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"
);
}),
});
}
}

View 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;
}

View 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 };

View 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
View 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
View 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);
}

View 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"; // */

View 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);
}

View 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
View 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/**/*"]
}

View 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
View 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
View 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);

View File

@@ -0,0 +1,7 @@
@import "~@hediet/visualization-bundle/style.scss";
html,
body,
.react-root {
height: 100%;
}

16
simple-demo/tsconfig.json Normal file
View 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/**/*"]
}

View 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
View File

@@ -0,0 +1,6 @@
{
"extends": "recommended",
"rules": {
"await-promise": true
}
}

View 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

View 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/"
}
}

View File

@@ -0,0 +1,3 @@
import { namespace } from "@hediet/semantic-json/dist/src/NamespacedNamed";
export const visualizationNs = namespace("hediet.de/visualization");

View File

@@ -0,0 +1 @@
export * from "./visualizers";

View 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;
}

View 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");
},
};
}

View File

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

View 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);

View File

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

View 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;
} {};
}

View File

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

View File

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

View 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;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
export * from "./dot-graphviz-visualizer";
export * from "./graph-graphviz-visualizer";
export * from "./graph-visjs-visualizer";

View 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"));

View File

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

View File

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

View File

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

View File

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

View 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";

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,
},
*/
};

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
.theme-dark {
--visualizer-simple-text-color: white;
}
.theme-light {
--visualizer-simple-text-color: black;
}

View File

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

View File

@@ -0,0 +1,6 @@
.theme-dark {
--visualizer-simple-text-color: white;
}
.theme-light {
--visualizer-simple-text-color: black;
}

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
.svgViewer {
svg {
overflow: auto;
}
}

View 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)
);
}
}

View File

@@ -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"])}
/>
);
}

View 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>
);
}
});
}

View 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;
}
}

View 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");
}
}

View 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";

View 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/**/*"]
}

View 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}
/>
);
}
```

View 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="&lt;mxfile host=&quot;db45393e-0ffa-4c0c-be8c-fc04b4d85e84&quot; modified=&quot;2020-08-13T13:22:12.385Z&quot; agent=&quot;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&quot; etag=&quot;KzlR1NopTHQEYcp3MlbJ&quot; version=&quot;13.1.3&quot;&gt;&lt;diagram id=&quot;6hGFLwfOUW9BJ-s0fimq&quot; name=&quot;Page-1&quot;&gt;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/&lt;/diagram&gt;&lt;/mxfile&gt;">
<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&lt;Visualization&gt;`
</div>
</div>
</div>
</foreignObject>
<text x="410" y="423" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle">
Creates through `BaseSerializer&lt;Visualization&gt;`
</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

View 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/"
}
}

View 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);
}
}

View 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"
) {}
}

View 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);
}

View 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}
/>
);
}
}

View 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;
}

View 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();

View 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;
}

View 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";

View 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/**/*"]
}

8792
yarn.lock Normal file

File diff suppressed because it is too large Load Diff