Improves lazy loading.

This commit is contained in:
Henning Dieterichs
2020-10-08 10:50:19 +02:00
parent 5512c0b244
commit cf382c71c9
33 changed files with 498 additions and 280 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "@hediet/visualization-bundle",
"description": "Bundles several visualizers",
"version": "0.1.8",
"version": "0.1.9",
"license": "MIT",
"scripts": {
"dev": "tsc --watch",
@@ -24,7 +24,7 @@
"@finos/perspective-viewer-d3fc": "^0.5.2",
"@finos/perspective-viewer-datagrid": "^0.5.2",
"@hediet/monaco-editor-react": "^0.1.1",
"@hediet/semantic-json": "^0.3.14",
"@hediet/semantic-json": "^0.3.15",
"@hediet/std": "^0.6.0",
"classnames": "^2.2.6",
"line-column": "^1.0.2",

View File

@@ -1,29 +1,30 @@
import * as React from "react";
import { observable, runInAction } from "mobx";
import { observer } from "mobx-react";
import { Loadable } from "./Loadable";
export function makeLazyLoadable<TProps>(
selector: () => Promise<React.ComponentClass<TProps>>
): React.ComponentClass<TProps> {
): React.ComponentClass<TProps> & { preload(): Promise<void> } {
const loader = new Loadable(selector);
@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;
});
constructor(props: any) {
super(props);
loader.load();
}
render() {
if (!this.component) {
if (!loader.result) {
return <div>Loading...</div>;
}
const C = this.component;
const C = loader.result;
return <C {...this.props} />;
}
static async preload(): Promise<void> {
await loader.load();
}
}
return MyComponent;

View File

@@ -0,0 +1,22 @@
import { observable, runInAction } from "mobx";
export class Loadable<T> {
@observable.ref
public result: T | undefined;
private operation?: Promise<void> = undefined;
constructor(private readonly _load: () => Promise<T>) {}
public load(): Promise<void> {
if (!this.operation) {
this.operation = (async () => {
const r = await this._load();
runInAction(() => {
this.result = r;
});
})();
}
return this.operation;
}
}

View File

@@ -2,7 +2,7 @@ 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 { getLanguageId } from "../monaco-text-visualizer/getLanguageId";
import {
TreeWithPathView,
TreeViewModel,

View File

@@ -14,8 +14,8 @@ import {
import { visualizationNs } from "../../consts";
import {
createVisualizer,
ReactVisualization,
globalVisualizationFactory,
createReactVisualization,
} from "@hediet/visualization-core";
import { createTreeViewModelFromTreeNodeData } from "../tree-visualizer";
import { AstTree, NodeInfo } from "./AstVisualizer";
@@ -81,7 +81,7 @@ export const astVisualizer = createVisualizer({
name: "AST",
serializer: sAstTree,
getVisualization: (data, self) =>
new ReactVisualization(self, { priority: 1500 }, theme => {
createReactVisualization(self, { priority: 1500 }, ({ theme }) => {
const m = createTreeViewModelFromTreeNodeData(
data.root,
node => node.span

View File

@@ -1,7 +1,7 @@
import { observer, disposeOnUnmount } from "mobx-react";
import { observer } from "mobx-react";
import * as React from "react";
import { observable, autorun, runInAction } from "mobx";
import { SvgViewer } from "../../svg-visualizer/SvgViewer";
import { Loadable } from "../../../utils/Loadable";
class VisLoader {
private result: any | undefined;
@@ -24,26 +24,29 @@ class VisLoader {
const vizLoader = new VisLoader();
export function getSvgFromDotCode(dotCode: string): Loadable<string> {
return new Loadable(async () => {
const viz = await vizLoader.getViz();
return await viz.renderString(dotCode);
});
}
@observer
export class GraphvizDotViewer extends React.Component<{
dotCode: string;
svgSource: Loadable<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) {
const { svgSource } = this.props;
svgSource.load();
if (!svgSource.result) {
return <div>Loading...</div>;
}
return <SvgViewer svgRef={this.props.svgRef} svgContent={this.svg} />;
return (
<SvgViewer
svgRef={this.props.svgRef}
svgContent={svgSource.result}
/>
);
}
}

View File

@@ -1,10 +1,10 @@
import * as React from "react";
import { sOpenObject, sLiteral, sString } from "@hediet/semantic-json";
import { GraphvizDotViewer } from "./GraphvizDotVisualizer";
import { getSvgFromDotCode, GraphvizDotViewer } from "./GraphvizDotVisualizer";
import {
ReactVisualization,
globalVisualizationFactory,
createVisualizer,
createReactVisualization,
} from "@hediet/visualization-core";
import { visualizationNs } from "../../../consts";
@@ -17,10 +17,17 @@ export const graphvizDotVisualizer = createVisualizer({
}),
text: sString(),
}).defineAs(visualizationNs("GraphvizDotVisualizationData")),
getVisualization: (data, self) =>
new ReactVisualization(self, { priority: 1500 }, () => (
<GraphvizDotViewer dotCode={data.text} />
)),
getVisualization: (data, self) => {
const svgSource = getSvgFromDotCode(data.text);
return createReactVisualization(
self,
{
priority: 1500,
preload: () => svgSource.load(),
},
() => <GraphvizDotViewer svgSource={svgSource} />
);
},
});
globalVisualizationFactory.addVisualizer(graphvizDotVisualizer);

View File

@@ -1,36 +0,0 @@
import { observer } from "mobx-react";
import * as React from "react";
import { GraphvizDotViewer } from "../dot-graphviz-visualizer/GraphvizDotVisualizer";
@observer
export class GraphvizGraphViewer extends React.Component<{
nodes: { id: string; label?: string }[];
edges: { from: string; to: string; label?: string; style: 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 : ""
)} style = ${JSON.stringify(e.style || "")} ];`
)
.join("\n")}
}
`;
return <GraphvizDotViewer dotCode={dotContent} />;
}
}

View File

@@ -0,0 +1,34 @@
import { Loadable } from "../../../utils/Loadable";
import { getSvgFromDotCode } from "../dot-graphviz-visualizer/GraphvizDotVisualizer";
export function getSvgFromGraphData(
nodes: { id: string; label?: string }[],
edges: {
from: string;
to: string;
label?: string;
style?: string;
}[]
): Loadable<string> {
const dotContent = `
digraph MyGraph {
${nodes
.map(
n =>
`"${n.id}" [ label = ${JSON.stringify(
n.label !== undefined ? n.label : n.id
)} ];`
)
.join("\n ")}
${edges
.map(
e =>
`"${e.from}" -> "${e.to}" [ label = ${JSON.stringify(
e.label !== undefined ? e.label : ""
)} style = ${JSON.stringify(e.style || "")} ];`
)
.join("\n")}
}
`;
return getSvgFromDotCode(dotContent);
}

View File

@@ -1,20 +1,25 @@
import * as React from "react";
import { GraphvizGraphViewer } from "./GraphvizGraphVisualizer";
import { sGraph } from "../sGraph";
import {
createVisualizer,
ReactVisualization,
globalVisualizationFactory,
createReactVisualization,
} from "@hediet/visualization-core";
import { GraphvizDotViewer } from "../dot-graphviz-visualizer/GraphvizDotVisualizer";
import { getSvgFromGraphData } from "./getGraphVizData";
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} />
)),
getVisualization: (data, self) => {
const svgSource = getSvgFromGraphData(data.nodes, data.edges);
return createReactVisualization(
self,
{ priority: 1000, preload: () => svgSource.load() },
() => <GraphvizDotViewer svgSource={svgSource} />
);
},
});
globalVisualizationFactory.addVisualizer(graphvizGraphVisualizer);

View File

@@ -1,8 +1,8 @@
import * as React from "react";
import { makeLazyLoadable } from "../../../utils/LazyLoadable";
import {
createReactVisualization,
createVisualizer,
ReactVisualization,
globalVisualizationFactory,
} from "@hediet/visualization-core";
import { sGraph } from "../sGraph";
@@ -16,12 +16,19 @@ export const visJsGraphVisualizer = createVisualizer({
name: "vis.js",
serializer: sGraph,
getVisualization: (data, self) =>
new ReactVisualization(self, { priority: 1500 }, () => (
<VisJsGraphViewerLazyLoadable
edges={data.edges}
nodes={data.nodes}
/>
)),
createReactVisualization(
self,
{
priority: 1500,
preload: VisJsGraphViewerLazyLoadable.preload,
},
() => (
<VisJsGraphViewerLazyLoadable
edges={data.edges}
nodes={data.nodes}
/>
)
),
});
globalVisualizationFactory.addVisualizer(visJsGraphVisualizer);

View File

@@ -4,15 +4,14 @@ import {
sLiteral,
sString,
sArrayOf,
sObject,
sOptionalProp,
sNumber,
} from "@hediet/semantic-json";
import { DecoratedGridComponent } from "./GridVisualizer";
import {
createVisualizer,
ReactVisualization,
globalVisualizationFactory,
createReactVisualization,
} from "@hediet/visualization-core";
import { visualizationNs } from "../../consts";
@@ -65,7 +64,7 @@ export const gridVisualizer = createVisualizer({
name: "Grid",
serializer: sGrid,
getVisualization: (data, self) =>
new ReactVisualization(self, { priority: 1000 }, () => (
createReactVisualization(self, { priority: 1000 }, () => (
<DecoratedGridComponent data={data} />
)),
});

View File

@@ -2,8 +2,8 @@ import { sOpenObject, sLiteral, sString, sProp } from "@hediet/semantic-json";
import * as React from "react";
import {
createVisualizer,
ReactVisualization,
globalVisualizationFactory,
createReactVisualization,
} from "@hediet/visualization-core";
import { SvgViewer } from "../svg-visualizer/SvgViewer";
import { Observer, observer, disposeOnUnmount } from "mobx-react";
@@ -22,7 +22,7 @@ export const imageVisualizer = createVisualizer({
}),
}).defineAs(visualizationNs("ImageVisualizationData")),
getVisualization: (data, self) =>
new ReactVisualization(self, { priority: 1000 }, () => {
createReactVisualization(self, { priority: 1000 }, () => {
return <ImageViewer base64Data={data.base64Data} />;
}),
});

View File

@@ -33,10 +33,10 @@ export function getLanguageId(fileName: string): string {
}
@observer
export class MonacoEditor extends React.Component<{
export class MonacoDiffEditor extends React.Component<{
originalText: string;
modifiedText: string;
languageId: string;
fileName?: string;
theme: Theme;
}> {
@observable private editor:
@@ -49,6 +49,13 @@ export class MonacoEditor extends React.Component<{
}
}
get languageId(): string {
if (!this.props.fileName) {
return "text";
}
return getLanguageId(this.props.fileName);
}
private originalModel:
| monacoTypes.editor.ITextModel
| undefined = undefined;
@@ -61,12 +68,12 @@ export class MonacoEditor extends React.Component<{
if (this.editor) {
const originalModel = getLoadedMonaco().editor.createModel(
this.props.originalText,
this.props.languageId,
this.languageId,
undefined
);
const modifiedModel = getLoadedMonaco().editor.createModel(
this.props.modifiedText,
this.props.languageId,
this.languageId,
undefined
);

View File

@@ -7,15 +7,16 @@ import {
} from "@hediet/semantic-json";
import * as React from "react";
import {
getLanguageId,
MonacoEditor as MonacoDiffEditor,
} from "./MonacoEditor";
import {
createReactVisualization,
createVisualizer,
ReactVisualization,
globalVisualizationFactory,
} from "@hediet/visualization-core";
import { visualizationNs } from "../../consts";
import { makeLazyLoadable } from "../../utils/LazyLoadable";
const MonacoDiffEditorLazyLoadable = makeLazyLoadable(
async () => (await import("./MonacoEditor")).MonacoDiffEditor
);
export const monacoTextDiffVisualizer = createVisualizer({
id: "monaco-text-diff",
@@ -34,20 +35,18 @@ export const monacoTextDiffVisualizer = createVisualizer({
}),
}).defineAs(visualizationNs("MonacoTextDiffVisualizationData")),
getVisualization: (data, self) =>
new ReactVisualization(self, { priority: 900 }, theme => {
let id = "text";
if (data.fileName) {
id = getLanguageId(data.fileName);
}
return (
<MonacoDiffEditor
createReactVisualization(
self,
{ priority: 900, preload: MonacoDiffEditorLazyLoadable.preload },
({ theme }) => (
<MonacoDiffEditorLazyLoadable
originalText={data.otherText}
modifiedText={data.text}
languageId={id}
fileName={data.fileName}
theme={theme}
/>
);
}),
)
),
});
globalVisualizationFactory.addVisualizer(monacoTextDiffVisualizer);

View File

@@ -4,39 +4,13 @@ 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";
}
import { getLanguageId } from "./getLanguageId";
@observer
export class MonacoEditor extends React.Component<{
text: string;
languageId: string;
theme: Theme;
fileName?: string;
}> {
@observable private editor:
| monacoTypes.editor.IStandaloneCodeEditor
@@ -48,6 +22,13 @@ export class MonacoEditor extends React.Component<{
}
}
get languageId(): string {
if (!this.props.fileName) {
return "text";
}
return getLanguageId(this.props.fileName);
}
private model: monacoTypes.editor.ITextModel | undefined = undefined;
@disposeOnUnmount
@@ -55,7 +36,7 @@ export class MonacoEditor extends React.Component<{
if (this.editor) {
const model = getLoadedMonaco().editor.createModel(
this.props.text,
this.props.languageId,
this.languageId,
undefined
);

View File

@@ -0,0 +1,28 @@
import { getLoadedMonaco } from "@hediet/monaco-editor-react";
export function getLanguageId(fileName: string): string {
const l = getLoadedMonaco().languages.getLanguages();
const result = l.find(l => {
if (l.filenamePatterns) {
for (const p of l.filenamePatterns) {
if (new RegExp(p).test(fileName)) {
return true;
}
}
}
if (l.extensions) {
for (const p of l.extensions) {
if (fileName.endsWith(p)) {
return true;
}
}
}
return false;
});
if (result) {
return result.id;
}
return "text";
}

View File

@@ -6,13 +6,17 @@ import {
sProp,
} from "@hediet/semantic-json";
import * as React from "react";
import { getLanguageId, MonacoEditor } from "./MonacoEditor";
import {
createVisualizer,
ReactVisualization,
globalVisualizationFactory,
createReactVisualization,
} from "@hediet/visualization-core";
import { visualizationNs } from "../../consts";
import { makeLazyLoadable } from "../../utils/LazyLoadable";
export const MonacoEditorLazyLoadable = makeLazyLoadable(
async () => (await import("./MonacoEditor")).MonacoEditor
);
export const monacoTextVisualizer = createVisualizer({
id: "monaco-text",
@@ -28,15 +32,17 @@ export const monacoTextVisualizer = createVisualizer({
}),
}).defineAs(visualizationNs("MonacoTextVisualizationData")),
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} />
);
}),
createReactVisualization(
self,
{ priority: 500, preload: MonacoEditorLazyLoadable.preload },
({ theme }) => (
<MonacoEditorLazyLoadable
text={data.text}
fileName={data.fileName}
theme={theme}
/>
)
),
});
globalVisualizationFactory.addVisualizer(monacoTextVisualizer);

View File

@@ -3,8 +3,8 @@ import { sOpenObject, sLiteral, sArrayOf, sProp } from "@hediet/semantic-json";
import { toJS } from "mobx";
import {
createVisualizer,
ReactVisualization,
globalVisualizationFactory,
createReactVisualization,
} from "@hediet/visualization-core";
import { makeLazyLoadable } from "../../utils/LazyLoadable";
import { visualizationNs } from "../../consts";
@@ -28,21 +28,28 @@ export const perspectiveTableVisualizer = createVisualizer({
name: "Perspective Table",
serializer: sTable,
getVisualization: (data, self) =>
new ReactVisualization(self, { priority: 1000 }, theme => (
<PerspectiveDataViewerLazyLoadable
className={
{
light: "perspective-viewer-material",
dark: "perspective-viewer-material-dark",
}[theme.kind]
}
style={{
width: "100%",
height: "100%",
}}
data={toJS(data.rows)}
/>
)),
createReactVisualization(
self,
{
priority: 1000,
preload: PerspectiveDataViewerLazyLoadable.preload,
},
({ theme }) => (
<PerspectiveDataViewerLazyLoadable
className={
{
light: "perspective-viewer-material",
dark: "perspective-viewer-material-dark",
}[theme.kind]
}
style={{
width: "100%",
height: "100%",
}}
data={toJS(data.rows)}
/>
)
),
});
globalVisualizationFactory.addVisualizer(perspectiveTableVisualizer);

View File

@@ -1,3 +1,4 @@
import { Theme } from "@hediet/visualization-core";
import { observer } from "mobx-react";
import * as React from "react";
import Plot from "react-plotly.js";
@@ -6,13 +7,14 @@ import Plot from "react-plotly.js";
export class PlotlyViewer extends React.Component<{
data: Plotly.Data[];
layout?: Partial<Plotly.Layout>;
theme: "light" | "dark";
theme: Theme;
onReady: () => void;
}> {
render() {
const { theme, data, layout } = this.props;
const computedLayout = Object.assign(
{},
theme === "dark" ? darkLayout : {},
theme.id === "dark" ? getLayout(theme) : {},
layout || {}
);
@@ -39,11 +41,108 @@ export class PlotlyViewer extends React.Component<{
data={data}
layout={computedLayout}
config={{ responsive: true }}
onInitialized={this.props.onReady}
/>
);
}
}
function getLayout(theme: Theme): Partial<Plotly.Layout> {
return {
colorway: [
"#636efa",
"#EF553B",
"#00cc96",
"#ab63fa",
"#19d3f3",
"#e763fa",
"#fecb52",
"#ffa15a",
"#ff6692",
"#b6e880",
],
autosize: true,
font: { color: "#f2f5fa" },
title: { x: 0.05 },
paper_bgcolor: theme.resolveVarToColor("--visualizer-background"), // "rgb(17,17,17)",
plot_bgcolor: theme.resolveVarToColor("--visualizer-background"), // "rgb(17,17,17)",
hovermode: "closest",
polar: {
bgcolor: theme.resolveVarToColor("--visualizer-background"),
angularaxis: {
gridcolor: "#506784",
linecolor: "#506784",
ticks: "",
},
radialaxis: {
gridcolor: "#506784",
linecolor: "#506784",
ticks: "",
},
},
ternary: {
bgcolor: theme.resolveVarToColor("--visualizer-background"),
aaxis: { gridcolor: "#506784", linecolor: "#506784", ticks: "" },
baxis: { gridcolor: "#506784", linecolor: "#506784", ticks: "" },
caxis: { gridcolor: "#506784", linecolor: "#506784", ticks: "" },
},
xaxis: {
gridcolor: "#283442",
linecolor: "#506784",
ticks: "",
zerolinecolor: "#283442",
zerolinewidth: 2,
automargin: true,
},
yaxis: {
gridcolor: "#283442",
linecolor: "#506784",
ticks: "",
zerolinecolor: "#283442",
zerolinewidth: 2,
automargin: true,
},
scene: {
xaxis: {
gridcolor: "#506784",
linecolor: "#506784",
showbackground: false,
ticks: "",
zerolinecolor: "#C8D4E3",
gridwidth: 2,
},
yaxis: {
gridcolor: "#506784",
linecolor: "#506784",
showbackground: false,
ticks: "",
zerolinecolor: "#C8D4E3",
gridwidth: 2,
},
zaxis: {
gridcolor: "#506784",
linecolor: "#506784",
showbackground: false,
ticks: "",
zerolinecolor: "#C8D4E3",
gridwidth: 2,
},
},
geo: {
bgcolor: theme.resolveVarToColor("--visualizer-plotly-background"),
landcolor: theme.resolveVarToColor(
"--visualizer-plotly-background"
),
subunitcolor: "#506784",
showland: true,
showlakes: true,
lakecolor: theme.resolveVarToColor(
"--visualizer-plotly-background"
),
},
};
}
// extracted from the plotly website
const darkLayout: Partial<Plotly.Layout> = {
colorway: [
@@ -59,53 +158,6 @@ const darkLayout: Partial<Plotly.Layout> = {
"#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)",
@@ -175,17 +227,6 @@ const darkLayout: Partial<Plotly.Layout> = {
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)",
@@ -194,13 +235,4 @@ const darkLayout: Partial<Plotly.Layout> = {
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

@@ -14,8 +14,9 @@ import {
import { makeLazyLoadable } from "../../utils/LazyLoadable";
import {
createVisualizer,
ReactVisualization,
globalVisualizationFactory,
createReactVisualization,
createLazyReactVisualization,
} from "@hediet/visualization-core";
import { visualizationNs } from "../../consts";
@@ -111,13 +112,21 @@ export const plotlyVisualizer = createVisualizer({
),
}).defineAs(visualizationNs("PlotlyVisualizationData")),
getVisualization: (data, self) =>
new ReactVisualization(self, { priority: 1000 }, theme => (
<PlotlyViewerLazyLoadable
data={data.data}
layout={data.layout}
theme={theme.kind}
/>
)),
createLazyReactVisualization(
self,
{
priority: 1000,
preload: PlotlyViewerLazyLoadable.preload,
},
({ theme, readyCallback }) => (
<PlotlyViewerLazyLoadable
data={data.data}
layout={data.layout}
theme={theme}
onReady={readyCallback}
/>
)
),
});
globalVisualizationFactory.addVisualizer(plotlyVisualizer);

View File

@@ -0,0 +1,3 @@
.themeable {
--visualizer-plotly-background: var(--visualizer-background);
}

View File

@@ -2,8 +2,8 @@ import { sOpenObject, sLiteral, sString } from "@hediet/semantic-json";
import * as React from "react";
import {
createVisualizer,
ReactVisualization,
globalVisualizationFactory,
createReactVisualization,
} from "@hediet/visualization-core";
import { visualizationNs } from "../../consts";
@@ -30,7 +30,7 @@ export const simpleTextVisualizer = createVisualizer({
) =>
// If you want to use react, this is the way to go.
// Return your own implementation if you want to render directly to the DOM.
new ReactVisualization(
createReactVisualization(
self,
{
// The priority is used for automatically selecting the best visualization

View File

@@ -1,12 +1,12 @@
import { sAny, sOpenObject } from "@hediet/semantic-json";
import { sOpenObject } from "@hediet/semantic-json";
import * as React from "react";
import {
createReactVisualization,
createVisualizer,
ReactVisualization,
globalVisualizationFactory,
} from "@hediet/visualization-core";
import { MonacoEditor } from "../monaco-text-visualizer/MonacoEditor";
import { visualizationNs } from "../../consts";
import { MonacoEditorLazyLoadable } from "../monaco-text-visualizer";
export const sourceVisualizer = createVisualizer({
id: "source",
@@ -15,10 +15,10 @@ export const sourceVisualizer = createVisualizer({
kind: sOpenObject({}),
}).defineAs(visualizationNs("SourceVisualizationData")),
getVisualization: (data, self) =>
new ReactVisualization(self, { priority: -100 }, theme => (
<MonacoEditor
createReactVisualization(self, { priority: -100 }, ({ theme }) => (
<MonacoEditorLazyLoadable
text={JSON.stringify(data, undefined, 4)}
languageId={"json"}
fileName={"main.json"}
theme={theme}
/>
)),

View File

@@ -3,8 +3,8 @@ import { sOpenObject, sLiteral, sString, sProp } from "@hediet/semantic-json";
import { SvgViewer } from "./SvgViewer";
import {
createVisualizer,
ReactVisualization,
globalVisualizationFactory,
createReactVisualization,
} from "@hediet/visualization-core";
import { visualizationNs } from "../../consts";
@@ -18,7 +18,7 @@ export const svgVisualizer = createVisualizer({
text: sProp(sString(), { description: "The svg content" }),
}).defineAs(visualizationNs("SvgVisualizationData")),
getVisualization: (data, self) =>
new ReactVisualization(self, { priority: 1500 }, () => (
createReactVisualization(self, { priority: 1500 }, () => (
<SvgViewer svgContent={data.text} />
)),
});

View File

@@ -19,8 +19,8 @@ import {
} from "@hediet/semantic-json";
import {
createVisualizer,
ReactVisualization,
globalVisualizationFactory,
createReactVisualization,
} from "@hediet/visualization-core";
import { visualizationNs } from "../../consts";
@@ -86,7 +86,7 @@ export const treeVisualizer = createVisualizer({
name: "Tree",
serializer: sTree,
getVisualization: (data, self) =>
new ReactVisualization(self, { priority: 1200 }, () => {
createReactVisualization(self, { priority: 1200 }, () => {
const m = createTreeViewModelFromTreeNodeData(
data.root,
() => undefined

View File

@@ -7,3 +7,4 @@
@import "./src/visualizers/ast-visualizer/style.scss";
@import "./src/visualizers/svg-visualizer/style.scss";
@import "./src/visualizers/graph/graph-visjs-visualizer/style.scss";
@import "./src/visualizers/plotly-visualizer/style.scss";

View File

@@ -1,7 +1,7 @@
{
"name": "@hediet/visualization-core",
"description": "Provides infrastructure to find registered visualizations for given json data",
"version": "0.1.1",
"version": "0.1.2",
"license": "MIT",
"homepage": "https://github.com/hediet/visualization",
"repository": {
@@ -28,7 +28,7 @@
"types": "./dist/index.d.ts",
"dependencies": {
"@hediet/std": "^0.6.0",
"@hediet/semantic-json": "^0.3.14",
"@hediet/semantic-json": "^0.3.15",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-measure": "^2.3.0"

View File

@@ -1,7 +1,12 @@
import * as React from "react";
import * as ReactDom from "react-dom";
import { Visualization, Theme } from "./index";
import { Visualizer, VisualizationId, asVisualizationId } from "./Visualizer";
import {
Visualizer,
VisualizationId,
asVisualizationId,
VisualizationRenderOptions,
} from "./Visualizer";
interface Options {
id?: string;
@@ -27,30 +32,91 @@ export abstract class BaseReactVisualization implements Visualization<unknown> {
public render(
target: HTMLDivElement,
theme: Theme,
previous: unknown | undefined
): unknown {
const node = this.getReactNode(theme);
options: VisualizationRenderOptions<unknown>
): { renderState: unknown } {
const node = this.getReactNode({
theme: options.theme,
readyCallback: options.readyCallback,
});
// We could use this to get access to theme vars:
// getComputedStyle(target).getPropertyValue("--background-color")
//getComputedStyle(target).getPropertyValue("--background-color")
ReactDom.render(<>{node}</>, target);
return undefined;
return { renderState: undefined };
}
protected abstract getReactNode(theme: Theme): React.ReactChild;
public abstract preload(): Promise<void>;
protected abstract getReactNode(
options: ReactVisualizationRenderOptions
): React.ReactChild;
}
export class ReactVisualization extends BaseReactVisualization {
export interface ReactVisualizationRenderOptions {
theme: Theme;
readyCallback: () => void;
}
class ReactVisualization extends BaseReactVisualization {
private readonly _preload?: () => Promise<void>;
constructor(
sourceVisualizer: Visualizer,
options: Options,
private readonly renderReactNode: (theme: Theme) => React.ReactChild
options: Options & { preload?: () => Promise<void> },
private readonly renderReactNode: (
options: ReactVisualizationRenderOptions
) => React.ReactChild
) {
super(sourceVisualizer, options);
this._preload = options.preload;
}
protected getReactNode(theme: Theme): React.ReactChild {
return this.renderReactNode(theme);
protected getReactNode(
options: ReactVisualizationRenderOptions
): React.ReactChild {
return this.renderReactNode(options);
}
public async preload(): Promise<void> {
if (this._preload) {
return await this._preload();
}
}
}
export function createReactVisualization(
sourceVisualizer: Visualizer,
options: Options & { preload?: () => Promise<void> },
renderReactNode: (options: { theme: Theme }) => React.ReactChild
) {
return new ReactVisualization(sourceVisualizer, options, options => (
<ComponentWithOnMount
onDidMount={options.readyCallback}
child={renderReactNode(options)}
/>
));
}
class ComponentWithOnMount extends React.Component<{
child: React.ReactChild;
onDidMount: () => void;
}> {
componentDidMount() {
this.props.onDidMount();
}
render() {
return this.props.child;
}
}
export function createLazyReactVisualization(
sourceVisualizer: Visualizer,
options: Options & { preload?: () => Promise<void> },
renderReactNode: (
options: ReactVisualizationRenderOptions
) => React.ReactChild
) {
return new ReactVisualization(sourceVisualizer, options, options =>
renderReactNode(options)
);
}

View File

@@ -2,8 +2,22 @@ export class Theme {
public static readonly light = new Theme("light", "light");
public static readonly dark = new Theme("dark", "dark");
private readonly divElement = document.createElement("div");
constructor(
public readonly id: string,
public readonly kind: "light" | "dark"
) {}
) {
this.divElement.classList.add(
"themeable",
`theme-${this.id}`,
"visualization"
);
this.divElement.style.display = "none";
document.body.append(this.divElement);
}
public resolveVarToColor(varName: string): string {
return getComputedStyle(this.divElement).getPropertyValue(varName);
}
}

View File

@@ -5,6 +5,7 @@ import { Theme } from "./Theme";
export class VisualizationView extends React.Component<{
visualization: Visualization;
theme: Theme;
onReady?: () => void;
}> {
private readonly ref = React.createRef<HTMLDivElement>();
private visualizationRenderState: unknown = undefined;
@@ -19,11 +20,18 @@ export class VisualizationView extends React.Component<{
renderVisualization() {
const { visualization, theme } = this.props;
this.visualizationRenderState = visualization.render(
this.ref.current!,
const { renderState } = visualization.render(this.ref.current!, {
theme,
this.visualizationRenderState
);
previousRenderState: this.visualizationRenderState,
readyCallback: () => {
const r = this.props.onReady;
if (r) {
r();
}
},
});
this.visualizationRenderState = renderState;
}
render() {

View File

@@ -23,6 +23,12 @@ export function asVisualizationId(id: string): VisualizationId {
return (id as unknown) as VisualizationId;
}
export interface VisualizationRenderOptions<TRenderState> {
theme: Theme;
previousRenderState: TRenderState | undefined;
readyCallback: () => void;
}
export interface Visualization<TRenderState = any> {
readonly id: VisualizationId;
readonly name: string;
@@ -31,7 +37,11 @@ export interface Visualization<TRenderState = any> {
render(
target: HTMLDivElement,
theme: Theme,
previousRenderState: TRenderState | undefined
): TRenderState;
options: VisualizationRenderOptions<TRenderState>
): { renderState: TRenderState };
/**
* Preloads resources to speed up first rendering.
*/
preload(): Promise<void>;
}

View File

@@ -316,6 +316,11 @@
resolved "https://registry.yarnpkg.com/@hediet/semantic-json/-/semantic-json-0.3.14.tgz#876bd5c4b646c134641ad17e9ef352595fb163e7"
integrity sha512-u2QGfT98RNrcQjqRHwFUy+TU8+sLneBOYvHHNbkHyYNokqi8kCpE9LReTygGT+6SfMtydTWN6JT9L2ffxrvV6w==
"@hediet/semantic-json@^0.3.15":
version "0.3.15"
resolved "https://registry.yarnpkg.com/@hediet/semantic-json/-/semantic-json-0.3.15.tgz#e38f799b1a7dd2624a3ee2e05bca58cfe76cb58d"
integrity sha512-8t1NXiYDF2wqIt1jEknxNnpucdtgFAI2MGSWnnewDhaYqWOJLMY/MxH60gDX03vycf2ySb6vEYpEwsiB6r0O3Q==
"@hediet/std@^0.6.0":
version "0.6.0"
resolved "https://registry.yarnpkg.com/@hediet/std/-/std-0.6.0.tgz#f7d2ffc1180ecef6ff272d0d6780f0c0d9337e4e"