From c5fcf6acd29c5844de79a43cfda9129e419322f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loi=CC=88c=20Correnson?= <loic.correnson@cea.fr> Date: Fri, 24 May 2024 13:41:39 +0200 Subject: [PATCH] [dome/diagram] selection callback --- ivette/.dome-pkg-app.lock | 2 +- ivette/.dome-pkg-dev.lock | 2 +- ivette/package.json | 4 +- ivette/src/dome/renderer/graph/diagram.tsx | 50 +++++++++++++++------- ivette/src/dome/template/makefile.packages | 12 +++--- ivette/src/sandbox/dotdiagram.tsx | 4 ++ ivette/yarn.lock | 9 +++- 7 files changed, 57 insertions(+), 26 deletions(-) diff --git a/ivette/.dome-pkg-app.lock b/ivette/.dome-pkg-app.lock index da523751673..d10dcc94cc5 100644 --- a/ivette/.dome-pkg-app.lock +++ b/ivette/.dome-pkg-app.lock @@ -1 +1 @@ -react@^18 react-dom@^18 source-map-support lodash@^4 react-virtualized@9.22.5 react-draggable react-fast-compare diff@^5 codemirror@5.65.2 @codemirror/language@6.10.0 @codemirror/search@6.5.5 @codemirror/state@6.4.0 @codemirror/view@6.23.1 @lezer/cpp@^1 react-virtualized-auto-sizer@^1.0.22 react-force-graph-2d@^1.25.4 react-force-graph-3d@^1.24.2 d3-graphviz +react@^18 react-dom@^18 source-map-support lodash@^4 react-virtualized@9.22.5 react-draggable react-fast-compare diff@^5 codemirror@5.65.2 @codemirror/language@6.10.0 @codemirror/search@6.5.5 @codemirror/state@6.4.0 @codemirror/view@6.23.1 @lezer/cpp@^1 react-virtualized-auto-sizer@^1.0.22 react-force-graph-2d@^1.25.4 react-force-graph-3d@^1.24.2 d3-selection@^3 d3-graphviz@^5 diff --git a/ivette/.dome-pkg-dev.lock b/ivette/.dome-pkg-dev.lock index c77ef156115..5f7937b83c1 100644 --- a/ivette/.dome-pkg-dev.lock +++ b/ivette/.dome-pkg-dev.lock @@ -1 +1 @@ -electron@^28 electron-builder@^24 electron-vite@^2 electron-devtools-installer @types/lodash@^4 @types/react@^18 @types/node@^18 @types/react-dom@^18 @types/react-virtualized@^9.21.8 @types/diff@^5 @types/d3-graphviz typescript@^5 +electron@^28 electron-builder@^24 electron-vite@^2 electron-devtools-installer @types/lodash@^4 @types/react@^18 @types/node@^18 @types/react-dom@^18 @types/react-virtualized@^9.21.8 @types/diff@^5 @types/d3-selection @types/d3-graphviz typescript@^5 diff --git a/ivette/package.json b/ivette/package.json index 57176f28b25..e44cabd9e3d 100644 --- a/ivette/package.json +++ b/ivette/package.json @@ -39,7 +39,8 @@ "cytoscape-klay": "", "cytoscape-panzoom": "", "cytoscape-popper": "", - "d3-graphviz": "^5.4.0", + "d3-graphviz": "^5", + "d3-selection": "^3", "diff": "^5", "lodash": "^4", "react": "^18", @@ -63,6 +64,7 @@ "@playwright/test": "^1.41.2", "@types/cytoscape": "^3.19.16", "@types/d3-graphviz": "^2.6.10", + "@types/d3-selection": "^3.0.10", "@types/diff": "^5", "@types/lodash": "^4", "@types/node": "^18", diff --git a/ivette/src/dome/renderer/graph/diagram.tsx b/ivette/src/dome/renderer/graph/diagram.tsx index 88e04b89e4b..871b34e94ee 100644 --- a/ivette/src/dome/renderer/graph/diagram.tsx +++ b/ivette/src/dome/renderer/graph/diagram.tsx @@ -24,7 +24,8 @@ import React from 'react'; import { Catch } from 'dome/errors'; import { classes } from 'dome/misc/utils'; import { Size } from 'react-virtualized'; -import * as d3 from 'd3-graphviz'; +import { select, selectAll } from 'd3-selection'; +import { graphviz } from 'd3-graphviz'; import AutoSizer from 'react-virtualized-auto-sizer'; import './style.css'; @@ -97,7 +98,7 @@ export interface DiagramProps { /** Element to focus on. - The graph is scrolled to make this node visible if necessary. + The default color for this element is `'selected'`. */ selected?: string; @@ -105,7 +106,7 @@ export interface DiagramProps { direction?: Direction; /** Invoked when a node is selected. */ - onSelection?: (node: string, evt: MouseEvent) => void; + onSelection?: (node: string | undefined) => void; /** Whether the Graph shall be displayed or not (defaults to true). */ display?: boolean; @@ -158,10 +159,11 @@ type edgeSpec = { source: string, target: string }; const edgeKey = (e: edgeSpec): string => `${e.source} -> ${e.target}`; class DotModel { - - // --- Basics + private selected: string | undefined; private spec = 'digraph {\n'; + constructor(s: string | undefined) { this.selected = s; } + print(...text: string[]): DotModel { this.spec = this.spec.concat(...text); return this; @@ -222,8 +224,9 @@ class DotModel { .attr('label', n.label) .attr('shape', n.shape); } - const color = n.color ?? 'white'; + const color = n.color ?? (n.id === this.selected ? 'selected' : 'white'); this + .attr('id', n.id) .attr('tooltip', n.title) .attr('fontcolor', FGCOLOR[color]) .attr('fillcolor', BGCOLOR[color]) @@ -256,18 +259,16 @@ const byEdge = (a: Edge, b: Edge): number => byStr(edgeKey(a), edgeKey(b)); /* -------------------------------------------------------------------------- */ let divId = 0; -const newDivId = (): string => `dome_d3gv_${++divId}`; +const newDivId = (): string => `dome_xDiagram_g${++divId}`; interface GraphvizProps extends DiagramProps { size: Size } function GraphvizView(props: GraphvizProps): JSX.Element { // --- Model Generation - const { - direction = 'LR', nodes, edges - } = props; + const { direction = 'LR', nodes, edges, selected } = props; const model = React.useMemo(() => { - const dot = new DotModel(); + const dot = new DotModel(selected); dot .attr('rankdir', direction) .attr('bgcolor', 'none') @@ -276,7 +277,7 @@ function GraphvizView(props: GraphvizProps): JSX.Element { nodes.concat().sort(byNode).forEach(n => dot.node(n)); edges.concat().sort(byEdge).forEach(e => dot.edge(e)); return dot.flush(); - }, [direction, nodes, edges]); + }, [direction, nodes, edges, selected]); // --- Model Update Callback const { onModelChanged } = props; @@ -284,19 +285,36 @@ function GraphvizView(props: GraphvizProps): JSX.Element { if (onModelChanged) onModelChanged(model); }, [model, onModelChanged]); + // --- Rendering & Remote const id = React.useMemo(newDivId, []); + const { onSelection } = props; const { width, height } = props.size; React.useEffect(() => { - d3.graphviz(`#${id}`, { + graphviz(`#${id}`, { useWorker: false, fit: true, zoom: true, width, height, - }).renderDot(model); - }, [id, model, width, height]); + }).renderDot(model).on('end', function () { + if (onSelection) { + selectAll('.node') + .on('click', function (evt: PointerEvent) { + const s = select(this).attr('id'); + if (s) { + evt.stopPropagation(); + onSelection(s); + } + }); + } + }); + }, [id, model, width, height, onSelection]); + + const onClick = React.useCallback((): void => { + if (onSelection) onSelection(undefined); + }, [onSelection]); return ( <Catch label='Graphviz Error'> - <div id={id} className={props.className} /> + <div id={id} className={props.className} onClick={onClick} /> </Catch> ); } diff --git a/ivette/src/dome/template/makefile.packages b/ivette/src/dome/template/makefile.packages index c252920ad78..ba3fa7df3f6 100644 --- a/ivette/src/dome/template/makefile.packages +++ b/ivette/src/dome/template/makefile.packages @@ -35,7 +35,8 @@ DOME_DEV_PACKAGES= \ @types/react-dom@^18 \ @types/react-virtualized@^9.21.8 \ @types/diff@^5 \ - @types/d3-graphviz\ + @types/d3-selection \ + @types/d3-graphviz \ typescript@^5 DOME_APP_PACKAGES= \ @@ -53,9 +54,10 @@ DOME_APP_PACKAGES= \ @codemirror/state@6.4.0 \ @codemirror/view@6.23.1 \ @lezer/cpp@^1 \ - react-virtualized-auto-sizer@^1.0.22\ - react-force-graph-2d@^1.25.4\ - react-force-graph-3d@^1.24.2\ - d3-graphviz + react-virtualized-auto-sizer@^1.0.22 \ + react-force-graph-2d@^1.25.4 \ + react-force-graph-3d@^1.24.2 \ + d3-selection@^3 \ + d3-graphviz@^5 # -------------------------------------------------------------------------- diff --git a/ivette/src/sandbox/dotdiagram.tsx b/ivette/src/sandbox/dotdiagram.tsx index 921acdf25e4..8eeaa2198c8 100644 --- a/ivette/src/sandbox/dotdiagram.tsx +++ b/ivette/src/sandbox/dotdiagram.tsx @@ -72,9 +72,11 @@ const edges: Edge[] = [ function DiagramSample(): JSX.Element { const [model, setModel] = React.useState(''); + const [selected, setSelected] = React.useState<string>(); return ( <HSplit settings='sandbox.diagram.split'> <Scroll> + <pre>Selected: {selected}</pre> <pre> {model} </pre> @@ -82,7 +84,9 @@ function DiagramSample(): JSX.Element { <Diagram nodes={nodes} edges={edges} + selected={selected} onModelChanged={setModel} + onSelection={setSelected} /> </HSplit > ); diff --git a/ivette/yarn.lock b/ivette/yarn.lock index 3e3c1ce2443..573ecf8a41a 100644 --- a/ivette/yarn.lock +++ b/ivette/yarn.lock @@ -943,6 +943,11 @@ resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-1.4.6.tgz#3e6056117b19d8bb6c729c872ca7234622099fb6" integrity sha512-0MhJ/LzJe6/vQVxiYJnvNq5CD/MF6Qy0dLp4BEQ6Dz8oOaB0EMXfx1GGeBFSW+3VzgjaUrxK6uECDQj9VLa/Mg== +"@types/d3-selection@^3.0.10": + version "3.0.10" + resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-3.0.10.tgz#98cdcf986d0986de6912b5892e7c015a95ca27fe" + integrity sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg== + "@types/d3-transition@^1": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-1.3.5.tgz#5ef69ea917d6935b0a1db895a7e5698ba7a08af1" @@ -2271,7 +2276,7 @@ d3-drag@^1.0.4: resolved "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz" integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA== -d3-graphviz@^5.4.0: +d3-graphviz@^5: version "5.4.0" resolved "https://registry.yarnpkg.com/d3-graphviz/-/d3-graphviz-5.4.0.tgz#a63ecb4345ff31aacb8813c74533fdd1ec4304ed" integrity sha512-e/kvvdfIfARiB4bF9/vDgY6WwvLxGCny2tS6ozUaOwgbL/CfaBWT7EwvCH5PiDQuvdx+xscnxjCsoUjw2CR86A== @@ -2336,7 +2341,7 @@ d3-selection@1: resolved "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz" integrity sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg== -"d3-selection@2 - 3", d3-selection@3: +"d3-selection@2 - 3", d3-selection@3, d3-selection@^3: version "3.0.0" resolved "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz" integrity sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ== -- GitLab