diff --git a/ivette/src/dome/renderer/graph/diagram.tsx b/ivette/src/dome/renderer/graph/diagram.tsx index ac817f4459a8527eeaec84c4e165fb398dc9085f..d726c9ab7ef5497b74f8c4b8e7e6c13d3d883a99 100644 --- a/ivette/src/dome/renderer/graph/diagram.tsx +++ b/ivette/src/dome/renderer/graph/diagram.tsx @@ -69,30 +69,70 @@ export interface DiagramProps { /** Styling the Graph main div element. */ className?: string; - /** Prints the DOT specification instead of the graph (only in DEV) */ - debug?: boolean; + /** Debug the generated DotModel */ + onModelChanged?: (model: string) => void; + } /* -------------------------------------------------------------------------- */ /* --- Dot Model --- */ /* -------------------------------------------------------------------------- */ -type edgeSpec = { source : string, target : string }; -const edgeKey = (e: edgeSpec):string => `${e.source} -> ${e.target}`; +type edgeSpec = { source: string, target: string }; +const edgeKey = (e: edgeSpec): string => `${e.source} -> ${e.target}`; class DotModel { - private spec = 'digraph {'; - print(...text: string []): DotModel { + + // --- Basics + private spec = 'digraph {\n'; + print(...text: string[]): DotModel { this.spec = this.spec.concat(...text); return this; } - println(...text: string []): DotModel { + println(...text: string[]): DotModel { this.spec = this.spec.concat(...text).concat('\n'); return this; } flush(): string { return this.spec.concat('}'); } + + // --- Graph + rankdir(d: Direction): DotModel { + return this.println(' rankdir="', d, '";'); + } + + // --- Special + quoted(a: string): DotModel { return this.print('"', a, '"'); } + + // --- Node + node(n: Node): void { + this + .print(' ') + .quoted(n.id) + .print(' [') + .println('];'); + } + + // --- Edge + edge(e: Edge): void { + this + .print(' ') + .quoted(e.source) + .print(' -> ') + .quoted(e.target) + .print(' [') + .println('];'); + } } +const byStr = (a: string, b: string): number => { + if (a < b) return -1; + if (a > b) return +1; + return 0; +} + +const byNode = (a: Node, b: Node): number => byStr(a.id, b.id); +const byEdge = (a: Edge, b: Edge): number => byStr(edgeKey(a), edgeKey(b)); + /* -------------------------------------------------------------------------- */ /* --- d3-Graphviz Component --- */ /* -------------------------------------------------------------------------- */ @@ -100,17 +140,27 @@ class DotModel { interface GraphvizProps extends DiagramProps { size: Size } function Graphviz(props: GraphvizProps): JSX.Element { - const { nodes, edges, size } = props; + // --- Model Generation + const { direction = 'LR', nodes, edges } = props; const model = React.useMemo(() => { const dot = new DotModel(); + dot.rankdir(direction); + nodes.concat().sort(byNode).forEach(n => dot.node(n)); + edges.concat().sort(byEdge).forEach(e => dot.edge(e)); return dot.flush(); }, [nodes, edges]); + // --- Model Update Callback + const { onModelChanged } = props; + React.useEffect(() => { + if (onModelChanged) onModelChanged(model); + }, [model, onModelChanged]); + // --- Rendering return ( - <Scroll style={size}> - <pre> + <div style={props.size}> + <pre style={{background: 'red'}}> {model} </pre> - </Scroll> + </div> ); } diff --git a/ivette/src/sandbox/diagram.tsx b/ivette/src/sandbox/diagram.tsx index ab56f3d2d4a2e01f17ba797d7a724b8797c51f86..ca602a70a164f78c44805b796b9863a447244987 100644 --- a/ivette/src/sandbox/diagram.tsx +++ b/ivette/src/sandbox/diagram.tsx @@ -25,15 +25,40 @@ /* -------------------------------------------------------------------------- */ import React from 'react'; -import { Diagram } from 'dome/graph/diagram'; +import { Scroll } from 'dome/layout/boxes'; +import { HSplit } from 'dome/layout/splitters'; +import { Diagram, Node, Edge } from 'dome/graph/diagram'; import { registerSandbox } from 'ivette'; // -------------------------------------------------------------------------- // --- Init functions for nodes and edges // -------------------------------------------------------------------------- +const nodes : Node[] = [ + { id: 'A' }, + { id: 'B' }, +]; + +const edges : Edge[] = [ + { source: 'A', target: 'B' } +]; + function DiagramSample(): JSX.Element { - return <Diagram nodes={[]} edges={[]} />; + const [model, setModel] = React.useState(''); + return ( + <HSplit settings='sandbox.diagram.split'> + <Scroll> + <pre> + {model} + </pre> + </Scroll> + <Diagram + nodes={nodes} + edges={edges} + onModelChanged={setModel} + /> + </HSplit > + ); } /* -------------------------------------------------------------------------- */