Jelajahi Sumber

Make nodes in Graphviz view clickable (uses Graphviz' URL property, hacked together a bit + cannot differentiate between mouse buttons)

Joeri Exelmans 2 tahun lalu
induk
melakukan
c46944c0ef

+ 0 - 14
src/frontend/d3graph/d3graph.tsx

@@ -34,20 +34,6 @@ export const emptyGraph: D3GraphData<any,any> = {
   links: [],
 };
 
-function esc(str) {
-  return str.replace(/"/g, '\\"');
-}
-
-export function toGraphvizDot<N,L>(graph: D3GraphData<N,L>) {
-  const dot = `digraph {
-    bgcolor="transparent";
-    ${graph.nodes.map(node => `"${esc(node.id)}" [${node.label===""?`shape=circle, width=0.3, height=0.3`:`shape=box`}, label="${esc(node.label)}", fillcolor="${node.color}", style="filled,rounded${node.bold?`,bold`:``}"]`).join('\n')}
-    ${graph.links.map(link => `"${esc(link.source.id || link.source)}" -> "${esc(link.target.id || link.target)}" [label="${esc(link.label)}", color="${link.color}"${link.bidirectional?`, dir=none`:``}]`).join('\n')}
-  }`;
-  console.log(dot);
-  return dot;
-}
-
 export const defaultGraphForces = {charge: -200, center: 0.1, link: 1};
 
 class D3Link<LinkType> extends React.Component<{ link: D3LinkData<LinkType>, svgDefs: any }, {}> {

+ 30 - 2
src/frontend/versioned_model/graph_view.tsx

@@ -2,12 +2,40 @@ import * as React from "react";
 import * as Mantine from "@mantine/core";
 import { Graphviz } from 'graphviz-react';
 
-import {D3Graph, defaultGraphForces, toGraphvizDot} from "../d3graph/d3graph";
+import {D3Graph, D3GraphData, defaultGraphForces} from "../d3graph/d3graph";
 import {InfoHoverCardOverlay} from "../info_hover_card";
 
+function esc(str) {
+  return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
+}
+
+function esc2(str) {
+  return encodeURIComponent(str);
+}
+
+// I know global variables are bad, but a GraphViz URL can only call a global function:
+const graphvizMap = new Map();
+(window as any).graphvizClicked = function(nodeId, button) {
+  const callback = graphvizMap.get(nodeId);
+  if (callback) callback(button);
+}
+
 export function GraphView({graphData, help, mouseUpHandler}) {
   const [renderer, setRenderer] = React.useState<"d3"|"graphviz">("d3");
 
+  if (mouseUpHandler) {
+    for (const node of graphData.nodes) {
+      graphvizMap.set(esc2(node.id), (button) => mouseUpHandler({button}, {x:0, y:0}, node));
+    }
+  }
+  const dot = `digraph {
+    bgcolor="transparent";
+    ${graphData.nodes.map(node => `"${esc(node.id)}" [${node.label===""?`shape=circle, width=0.3, height=0.3`:`shape=box`}, label="${esc(node.label)}", fillcolor="${node.color}", style="filled,rounded${node.bold?`,bold`:``}", URL="javascript:${esc2(`graphvizClicked('${node.id}', 2)`)}"]`).join('\n')}
+    ${graphData.links.map(link => `"${esc(link.source.id || link.source)}" -> "${esc(link.target.id || link.target)}" [label="${esc(link.label)}", color="${link.color}"${link.bidirectional?`, dir=none`:``}, labelURL="javascript:${esc2(`graphvizClicked('${link.source.id||link.source}', 1)`)}"]`).join('\n')}
+  }`;
+
+  // console.log(dot);
+
   return <Mantine.Stack>
     <Mantine.Group position="center">
       <Mantine.SegmentedControl
@@ -26,7 +54,7 @@ export function GraphView({graphData, help, mouseUpHandler}) {
       :
       // @ts-ignore:
       <Mantine.ScrollArea style={{backgroundColor:"#eee"}}>
-        <Graphviz dot={toGraphvizDot(graphData)} options={{fit:false, width:null, height:null, scale:0.8}}/>
+        <Graphviz dot={dot} options={{fit:false, width:null, height:null, scale:0.8}}/>
       </Mantine.ScrollArea>
     }
   </Mantine.Stack>;