From cf382c71c93e6dc2a1c98f4a8b395c3473b9a885 Mon Sep 17 00:00:00 2001 From: Henning Dieterichs Date: Thu, 8 Oct 2020 10:50:19 +0200 Subject: [PATCH] Improves lazy loading. --- visualization-bundle/package.json | 4 +- .../src/utils/LazyLoadable.tsx | 25 +-- visualization-bundle/src/utils/Loadable.tsx | 22 +++ .../ast-visualizer/AstVisualizer.tsx | 2 +- .../src/visualizers/ast-visualizer/index.tsx | 4 +- .../GraphvizDotVisualizer.tsx | 35 ++-- .../graph/dot-graphviz-visualizer/index.tsx | 19 +- .../GraphvizGraphVisualizer.tsx | 36 ---- .../getGraphVizData.tsx | 34 ++++ .../graph/graph-graphviz-visualizer/index.tsx | 17 +- .../graph/graph-visjs-visualizer/index.tsx | 21 ++- .../src/visualizers/grid-visualizer/index.tsx | 5 +- .../visualizers/image-visualizer/index.tsx | 4 +- .../MonacoEditor.tsx | 15 +- .../monaco-text-diff-visualizer/index.tsx | 29 ++- .../monaco-text-visualizer/MonacoEditor.tsx | 39 ++-- .../monaco-text-visualizer/getLanguageId.tsx | 28 +++ .../monaco-text-visualizer/index.tsx | 28 +-- .../perspective-table-visualizer/index.tsx | 39 ++-- .../plotly-visualizer/PlotlyViewer.tsx | 170 +++++++++++------- .../visualizers/plotly-visualizer/index.tsx | 25 ++- .../visualizers/plotly-visualizer/style.scss | 3 + .../simple-text-visualizer/index.tsx | 4 +- .../visualizers/source-visualizer/index.tsx | 12 +- .../src/visualizers/svg-visualizer/index.tsx | 4 +- .../src/visualizers/tree-visualizer/index.tsx | 4 +- visualization-bundle/style.scss | 1 + visualization-core/package.json | 4 +- visualization-core/src/ReactVisualization.tsx | 92 ++++++++-- visualization-core/src/Theme.ts | 16 +- visualization-core/src/VisualizationView.tsx | 16 +- visualization-core/src/Visualizer.tsx | 16 +- yarn.lock | 5 + 33 files changed, 498 insertions(+), 280 deletions(-) create mode 100644 visualization-bundle/src/utils/Loadable.tsx delete mode 100644 visualization-bundle/src/visualizers/graph/graph-graphviz-visualizer/GraphvizGraphVisualizer.tsx create mode 100644 visualization-bundle/src/visualizers/graph/graph-graphviz-visualizer/getGraphVizData.tsx create mode 100644 visualization-bundle/src/visualizers/monaco-text-visualizer/getLanguageId.tsx create mode 100644 visualization-bundle/src/visualizers/plotly-visualizer/style.scss diff --git a/visualization-bundle/package.json b/visualization-bundle/package.json index 37b6001..a7a8fbf 100644 --- a/visualization-bundle/package.json +++ b/visualization-bundle/package.json @@ -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", diff --git a/visualization-bundle/src/utils/LazyLoadable.tsx b/visualization-bundle/src/utils/LazyLoadable.tsx index a8dcd37..9a26fec 100644 --- a/visualization-bundle/src/utils/LazyLoadable.tsx +++ b/visualization-bundle/src/utils/LazyLoadable.tsx @@ -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( selector: () => Promise> -): React.ComponentClass { +): React.ComponentClass & { preload(): Promise } { + const loader = new Loadable(selector); + @observer class MyComponent extends React.Component { - @observable - component: React.ComponentClass | 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
Loading...
; } - const C = this.component; + const C = loader.result; return ; } + + static async preload(): Promise { + await loader.load(); + } } return MyComponent; diff --git a/visualization-bundle/src/utils/Loadable.tsx b/visualization-bundle/src/utils/Loadable.tsx new file mode 100644 index 0000000..e2b66b2 --- /dev/null +++ b/visualization-bundle/src/utils/Loadable.tsx @@ -0,0 +1,22 @@ +import { observable, runInAction } from "mobx"; + +export class Loadable { + @observable.ref + public result: T | undefined; + + private operation?: Promise = undefined; + + constructor(private readonly _load: () => Promise) {} + + public load(): Promise { + if (!this.operation) { + this.operation = (async () => { + const r = await this._load(); + runInAction(() => { + this.result = r; + }); + })(); + } + return this.operation; + } +} diff --git a/visualization-bundle/src/visualizers/ast-visualizer/AstVisualizer.tsx b/visualization-bundle/src/visualizers/ast-visualizer/AstVisualizer.tsx index 0cfa153..60a27a3 100644 --- a/visualization-bundle/src/visualizers/ast-visualizer/AstVisualizer.tsx +++ b/visualization-bundle/src/visualizers/ast-visualizer/AstVisualizer.tsx @@ -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, diff --git a/visualization-bundle/src/visualizers/ast-visualizer/index.tsx b/visualization-bundle/src/visualizers/ast-visualizer/index.tsx index 634da26..c9a7dac 100644 --- a/visualization-bundle/src/visualizers/ast-visualizer/index.tsx +++ b/visualization-bundle/src/visualizers/ast-visualizer/index.tsx @@ -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 diff --git a/visualization-bundle/src/visualizers/graph/dot-graphviz-visualizer/GraphvizDotVisualizer.tsx b/visualization-bundle/src/visualizers/graph/dot-graphviz-visualizer/GraphvizDotVisualizer.tsx index 4fd86ee..d869455 100644 --- a/visualization-bundle/src/visualizers/graph/dot-graphviz-visualizer/GraphvizDotVisualizer.tsx +++ b/visualization-bundle/src/visualizers/graph/dot-graphviz-visualizer/GraphvizDotVisualizer.tsx @@ -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 { + 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; 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
Loading...
; } - return ; + return ( + + ); } } diff --git a/visualization-bundle/src/visualizers/graph/dot-graphviz-visualizer/index.tsx b/visualization-bundle/src/visualizers/graph/dot-graphviz-visualizer/index.tsx index 4839b7f..c97b621 100644 --- a/visualization-bundle/src/visualizers/graph/dot-graphviz-visualizer/index.tsx +++ b/visualization-bundle/src/visualizers/graph/dot-graphviz-visualizer/index.tsx @@ -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 }, () => ( - - )), + getVisualization: (data, self) => { + const svgSource = getSvgFromDotCode(data.text); + return createReactVisualization( + self, + { + priority: 1500, + preload: () => svgSource.load(), + }, + () => + ); + }, }); globalVisualizationFactory.addVisualizer(graphvizDotVisualizer); diff --git a/visualization-bundle/src/visualizers/graph/graph-graphviz-visualizer/GraphvizGraphVisualizer.tsx b/visualization-bundle/src/visualizers/graph/graph-graphviz-visualizer/GraphvizGraphVisualizer.tsx deleted file mode 100644 index 4009d85..0000000 --- a/visualization-bundle/src/visualizers/graph/graph-graphviz-visualizer/GraphvizGraphVisualizer.tsx +++ /dev/null @@ -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 ; - } -} diff --git a/visualization-bundle/src/visualizers/graph/graph-graphviz-visualizer/getGraphVizData.tsx b/visualization-bundle/src/visualizers/graph/graph-graphviz-visualizer/getGraphVizData.tsx new file mode 100644 index 0000000..8516f22 --- /dev/null +++ b/visualization-bundle/src/visualizers/graph/graph-graphviz-visualizer/getGraphVizData.tsx @@ -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 { + 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); +} diff --git a/visualization-bundle/src/visualizers/graph/graph-graphviz-visualizer/index.tsx b/visualization-bundle/src/visualizers/graph/graph-graphviz-visualizer/index.tsx index 9aedb5c..dd80a1d 100644 --- a/visualization-bundle/src/visualizers/graph/graph-graphviz-visualizer/index.tsx +++ b/visualization-bundle/src/visualizers/graph/graph-graphviz-visualizer/index.tsx @@ -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 }, () => ( - - )), + getVisualization: (data, self) => { + const svgSource = getSvgFromGraphData(data.nodes, data.edges); + return createReactVisualization( + self, + { priority: 1000, preload: () => svgSource.load() }, + () => + ); + }, }); globalVisualizationFactory.addVisualizer(graphvizGraphVisualizer); diff --git a/visualization-bundle/src/visualizers/graph/graph-visjs-visualizer/index.tsx b/visualization-bundle/src/visualizers/graph/graph-visjs-visualizer/index.tsx index 7ac81d5..2b61ef4 100644 --- a/visualization-bundle/src/visualizers/graph/graph-visjs-visualizer/index.tsx +++ b/visualization-bundle/src/visualizers/graph/graph-visjs-visualizer/index.tsx @@ -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 }, () => ( - - )), + createReactVisualization( + self, + { + priority: 1500, + preload: VisJsGraphViewerLazyLoadable.preload, + }, + () => ( + + ) + ), }); globalVisualizationFactory.addVisualizer(visJsGraphVisualizer); diff --git a/visualization-bundle/src/visualizers/grid-visualizer/index.tsx b/visualization-bundle/src/visualizers/grid-visualizer/index.tsx index 7916a1a..047ab3a 100644 --- a/visualization-bundle/src/visualizers/grid-visualizer/index.tsx +++ b/visualization-bundle/src/visualizers/grid-visualizer/index.tsx @@ -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 }, () => ( )), }); diff --git a/visualization-bundle/src/visualizers/image-visualizer/index.tsx b/visualization-bundle/src/visualizers/image-visualizer/index.tsx index 8848463..6d202aa 100644 --- a/visualization-bundle/src/visualizers/image-visualizer/index.tsx +++ b/visualization-bundle/src/visualizers/image-visualizer/index.tsx @@ -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 ; }), }); diff --git a/visualization-bundle/src/visualizers/monaco-text-diff-visualizer/MonacoEditor.tsx b/visualization-bundle/src/visualizers/monaco-text-diff-visualizer/MonacoEditor.tsx index 14783b7..26ae906 100644 --- a/visualization-bundle/src/visualizers/monaco-text-diff-visualizer/MonacoEditor.tsx +++ b/visualization-bundle/src/visualizers/monaco-text-diff-visualizer/MonacoEditor.tsx @@ -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 ); diff --git a/visualization-bundle/src/visualizers/monaco-text-diff-visualizer/index.tsx b/visualization-bundle/src/visualizers/monaco-text-diff-visualizer/index.tsx index 58877e9..7e1146c 100644 --- a/visualization-bundle/src/visualizers/monaco-text-diff-visualizer/index.tsx +++ b/visualization-bundle/src/visualizers/monaco-text-diff-visualizer/index.tsx @@ -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 ( - ( + - ); - }), + ) + ), }); globalVisualizationFactory.addVisualizer(monacoTextDiffVisualizer); diff --git a/visualization-bundle/src/visualizers/monaco-text-visualizer/MonacoEditor.tsx b/visualization-bundle/src/visualizers/monaco-text-visualizer/MonacoEditor.tsx index e775b31..3d2ac16 100644 --- a/visualization-bundle/src/visualizers/monaco-text-visualizer/MonacoEditor.tsx +++ b/visualization-bundle/src/visualizers/monaco-text-visualizer/MonacoEditor.tsx @@ -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 ); diff --git a/visualization-bundle/src/visualizers/monaco-text-visualizer/getLanguageId.tsx b/visualization-bundle/src/visualizers/monaco-text-visualizer/getLanguageId.tsx new file mode 100644 index 0000000..988be6f --- /dev/null +++ b/visualization-bundle/src/visualizers/monaco-text-visualizer/getLanguageId.tsx @@ -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"; +} diff --git a/visualization-bundle/src/visualizers/monaco-text-visualizer/index.tsx b/visualization-bundle/src/visualizers/monaco-text-visualizer/index.tsx index 1be890e..7a1361a 100644 --- a/visualization-bundle/src/visualizers/monaco-text-visualizer/index.tsx +++ b/visualization-bundle/src/visualizers/monaco-text-visualizer/index.tsx @@ -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 ( - - ); - }), + createReactVisualization( + self, + { priority: 500, preload: MonacoEditorLazyLoadable.preload }, + ({ theme }) => ( + + ) + ), }); globalVisualizationFactory.addVisualizer(monacoTextVisualizer); diff --git a/visualization-bundle/src/visualizers/perspective-table-visualizer/index.tsx b/visualization-bundle/src/visualizers/perspective-table-visualizer/index.tsx index 258ccc6..24abed5 100644 --- a/visualization-bundle/src/visualizers/perspective-table-visualizer/index.tsx +++ b/visualization-bundle/src/visualizers/perspective-table-visualizer/index.tsx @@ -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 => ( - - )), + createReactVisualization( + self, + { + priority: 1000, + preload: PerspectiveDataViewerLazyLoadable.preload, + }, + ({ theme }) => ( + + ) + ), }); globalVisualizationFactory.addVisualizer(perspectiveTableVisualizer); diff --git a/visualization-bundle/src/visualizers/plotly-visualizer/PlotlyViewer.tsx b/visualization-bundle/src/visualizers/plotly-visualizer/PlotlyViewer.tsx index cb216bd..050ed8c 100644 --- a/visualization-bundle/src/visualizers/plotly-visualizer/PlotlyViewer.tsx +++ b/visualization-bundle/src/visualizers/plotly-visualizer/PlotlyViewer.tsx @@ -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; - 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 { + 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 = { colorway: [ @@ -59,53 +158,6 @@ const darkLayout: Partial = { "#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 = { 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 = { showlakes: true, lakecolor: "rgb(17,17,17)", }, - /* - updatemenudefaults: { bgcolor: "#506784", borderwidth: 0 }, - sliderdefaults: { - bgcolor: "#C8D4E3", - borderwidth: 1, - bordercolor: "rgb(17,17,17)", - tickwidth: 0, - }, - */ }; diff --git a/visualization-bundle/src/visualizers/plotly-visualizer/index.tsx b/visualization-bundle/src/visualizers/plotly-visualizer/index.tsx index 07d3392..a2f5cf9 100644 --- a/visualization-bundle/src/visualizers/plotly-visualizer/index.tsx +++ b/visualization-bundle/src/visualizers/plotly-visualizer/index.tsx @@ -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 => ( - - )), + createLazyReactVisualization( + self, + { + priority: 1000, + preload: PlotlyViewerLazyLoadable.preload, + }, + ({ theme, readyCallback }) => ( + + ) + ), }); globalVisualizationFactory.addVisualizer(plotlyVisualizer); diff --git a/visualization-bundle/src/visualizers/plotly-visualizer/style.scss b/visualization-bundle/src/visualizers/plotly-visualizer/style.scss new file mode 100644 index 0000000..d97c48b --- /dev/null +++ b/visualization-bundle/src/visualizers/plotly-visualizer/style.scss @@ -0,0 +1,3 @@ +.themeable { + --visualizer-plotly-background: var(--visualizer-background); +} diff --git a/visualization-bundle/src/visualizers/simple-text-visualizer/index.tsx b/visualization-bundle/src/visualizers/simple-text-visualizer/index.tsx index 0fd4a62..e7deeba 100644 --- a/visualization-bundle/src/visualizers/simple-text-visualizer/index.tsx +++ b/visualization-bundle/src/visualizers/simple-text-visualizer/index.tsx @@ -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 diff --git a/visualization-bundle/src/visualizers/source-visualizer/index.tsx b/visualization-bundle/src/visualizers/source-visualizer/index.tsx index dd6694e..2ad3aff 100644 --- a/visualization-bundle/src/visualizers/source-visualizer/index.tsx +++ b/visualization-bundle/src/visualizers/source-visualizer/index.tsx @@ -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 => ( - ( + )), diff --git a/visualization-bundle/src/visualizers/svg-visualizer/index.tsx b/visualization-bundle/src/visualizers/svg-visualizer/index.tsx index 5295957..e3d4a81 100644 --- a/visualization-bundle/src/visualizers/svg-visualizer/index.tsx +++ b/visualization-bundle/src/visualizers/svg-visualizer/index.tsx @@ -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 }, () => ( )), }); diff --git a/visualization-bundle/src/visualizers/tree-visualizer/index.tsx b/visualization-bundle/src/visualizers/tree-visualizer/index.tsx index 3ffc28a..3e3f01b 100644 --- a/visualization-bundle/src/visualizers/tree-visualizer/index.tsx +++ b/visualization-bundle/src/visualizers/tree-visualizer/index.tsx @@ -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 diff --git a/visualization-bundle/style.scss b/visualization-bundle/style.scss index 1cdc4bc..878b2f7 100644 --- a/visualization-bundle/style.scss +++ b/visualization-bundle/style.scss @@ -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"; diff --git a/visualization-core/package.json b/visualization-core/package.json index 6fabc45..e87aef2 100644 --- a/visualization-core/package.json +++ b/visualization-core/package.json @@ -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" diff --git a/visualization-core/src/ReactVisualization.tsx b/visualization-core/src/ReactVisualization.tsx index 9090378..800f6cf 100644 --- a/visualization-core/src/ReactVisualization.tsx +++ b/visualization-core/src/ReactVisualization.tsx @@ -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 { public render( target: HTMLDivElement, - theme: Theme, - previous: unknown | undefined - ): unknown { - const node = this.getReactNode(theme); + options: VisualizationRenderOptions + ): { 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; + 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; + constructor( sourceVisualizer: Visualizer, - options: Options, - private readonly renderReactNode: (theme: Theme) => React.ReactChild + options: Options & { preload?: () => Promise }, + 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 { + if (this._preload) { + return await this._preload(); + } } } + +export function createReactVisualization( + sourceVisualizer: Visualizer, + options: Options & { preload?: () => Promise }, + renderReactNode: (options: { theme: Theme }) => React.ReactChild +) { + return new ReactVisualization(sourceVisualizer, options, 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 }, + renderReactNode: ( + options: ReactVisualizationRenderOptions + ) => React.ReactChild +) { + return new ReactVisualization(sourceVisualizer, options, options => + renderReactNode(options) + ); +} diff --git a/visualization-core/src/Theme.ts b/visualization-core/src/Theme.ts index 4507492..42e7694 100644 --- a/visualization-core/src/Theme.ts +++ b/visualization-core/src/Theme.ts @@ -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); + } } diff --git a/visualization-core/src/VisualizationView.tsx b/visualization-core/src/VisualizationView.tsx index 64702fc..6d831c6 100644 --- a/visualization-core/src/VisualizationView.tsx +++ b/visualization-core/src/VisualizationView.tsx @@ -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(); 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() { diff --git a/visualization-core/src/Visualizer.tsx b/visualization-core/src/Visualizer.tsx index 5620caa..76893f5 100644 --- a/visualization-core/src/Visualizer.tsx +++ b/visualization-core/src/Visualizer.tsx @@ -23,6 +23,12 @@ export function asVisualizationId(id: string): VisualizationId { return (id as unknown) as VisualizationId; } +export interface VisualizationRenderOptions { + theme: Theme; + previousRenderState: TRenderState | undefined; + readyCallback: () => void; +} + export interface Visualization { readonly id: VisualizationId; readonly name: string; @@ -31,7 +37,11 @@ export interface Visualization { render( target: HTMLDivElement, - theme: Theme, - previousRenderState: TRenderState | undefined - ): TRenderState; + options: VisualizationRenderOptions + ): { renderState: TRenderState }; + + /** + * Preloads resources to speed up first rendering. + */ + preload(): Promise; } diff --git a/yarn.lock b/yarn.lock index 09372be..b58b83e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"