diff --git a/frontend/src/components/app.tsx b/frontend/src/components/app.tsx
index 1cd0cdd..7369a0b 100644
--- a/frontend/src/components/app.tsx
+++ b/frontend/src/components/app.tsx
@@ -1,11 +1,13 @@
import * as React from 'react';
-const { useState, useCallback } = React;
-
import { ThemeProvider, createTheme } from '@mui/material/styles';
-import { Backdrop, Button, CircularProgress, CssBaseline, Container, Link, Typography, TextField } from '@mui/material';
+import {
+ Backdrop, Button, CircularProgress, CssBaseline, Container, Link, Paper, Typography, TextField,
+} from '@mui/material';
-//import Graph from './graph.tsx';
+import Graph from './graph.tsx';
+
+const { useState, useCallback } = React;
const darkTheme = createTheme({
palette: {
@@ -49,8 +51,8 @@ function App() {
},
body: JSON.stringify({ code }),
})
- .then(res => res.json())
- .then(data => setGraph(data))
+ .then((res) => res.json())
+ .then((data) => setGraph(data))
.finally(() => setLoading(false));
// TODO: error handling
},
@@ -59,36 +61,53 @@ function App() {
console.log(graph);
- return
-
-
-
- Solve your ADF Problem with Style!
-
+ return (
+
+
+
+
+ Solve your ADF Problem with Style!
+
-
- For more info on the syntax, have a look here.>}
- multiline
- fullWidth
- variant="filled"
- value={code}
- onChange={(event) => { setCode(event.target.value); }}
- />
-
-
-
-
-
+
+
+ For more info on the syntax, have a look
+ here
+ .
+ >
+)}
+ multiline
+ fullWidth
+ variant="filled"
+ value={code}
+ onChange={(event) => { setCode(event.target.value); }}
+ />
+
+
+
+
-
-
-
- ;
+ {graph
+ && (
+
+
+
+
+
+ )}
+
+
+
+
+
+
+ );
}
export default App;
diff --git a/frontend/src/components/graph.tsx b/frontend/src/components/graph.tsx
index d92f4d4..f9085a8 100644
--- a/frontend/src/components/graph.tsx
+++ b/frontend/src/components/graph.tsx
@@ -1,27 +1,50 @@
import React, { useEffect, useRef } from 'react';
import G6 from '@antv/g6';
-import testData from '../test-data.ts';
-function Graph() {
+interface Props {
+ graph: {
+ lo_edges: [number, number][],
+ hi_edges: [number, number][],
+ node_labels: { [key: number]: string },
+ tree_root_labels: { [key: number]: string[] },
+ }
+}
+
+function Graph(props: Props) {
+ const { graph: graphProps } = props;
+
const ref = useRef(null);
- let graph = null;
+ const graphRef = useRef();
useEffect(
() => {
- if (!graph) {
- graph = new G6.Graph({
+ if (!graphRef.current) {
+ graphRef.current = new G6.Graph({
container: ref.current,
width: 1200,
- height: 800,
+ height: 600,
+ fitView: true,
+ rankdir: 'TB',
+ align: 'DR',
+ nodesep: 100,
+ ranksep: 100,
modes: {
default: ['drag-canvas', 'zoom-canvas', 'drag-node'],
},
layout: { type: 'dagre' },
defaultNode: {
+ anchorPoints: [[0.5, 0], [0, 0.5], [1, 0.5], [0.5, 1]],
+ type: 'rect',
style: {
- r: 20,
+ radius: 2,
+ },
+ labelCfg: {
+ style: {
+ // fontWeight: 700,
+ fontFamily: 'Roboto',
+ },
},
},
defaultEdge: {
@@ -49,6 +72,8 @@ function Graph() {
});
}
+ const graph = graphRef.current;
+
// Mouse enter a node
graph.on('node:mouseenter', (e) => {
const nodeItem = e.item; // Get the target item
@@ -63,12 +88,31 @@ function Graph() {
// Click a node
graph.on('node:click', (e) => {
- // Swich the 'click' state of the node to be false
+ const nodeItem = e.item; // et the clicked item
+
+ let onlyRemoveStates = false;
+ if (nodeItem.hasState('highlight')) {
+ onlyRemoveStates = true;
+ }
+
const clickNodes = graph.findAllByState('node', 'highlight');
clickNodes.forEach((cn) => {
graph.setItemState(cn, 'highlight', false);
});
+ const lowlightNodes = graph.findAllByState('node', 'lowlight');
+ lowlightNodes.forEach((cn) => {
+ graph.setItemState(cn, 'lowlight', false);
+ });
+ const lowlightEdges = graph.findAllByState('edge', 'lowlight');
+ lowlightEdges.forEach((cn) => {
+ graph.setItemState(cn, 'lowlight', false);
+ });
+
+ if (onlyRemoveStates) {
+ return;
+ }
+
graph.getNodes().forEach((node) => {
graph.setItemState(node, 'lowlight', true);
});
@@ -76,24 +120,98 @@ function Graph() {
graph.setItemState(edge, 'lowlight', true);
});
- const nodeItem = e.item; // et the clicked item
- graph.setItemState(nodeItem, 'lowlight', false);
- graph.setItemState(nodeItem, 'highlight', true);
- nodeItem.getEdges().forEach((edge) => {
- graph.setItemState(edge, 'lowlight', false);
- });
- });
+ const relevantNodeIds = [];
+ const relevantLoEdges = [];
+ const relevantHiEdges = [];
+ let newNodeIds = [nodeItem.getModel().id];
+ let newLoEdges = [];
+ let newHiEdges = [];
- graph.data({
- nodes: testData.map((node, index) => ({ id: index.toString(), label: node.label })),
- edges: testData.flatMap((node, index) => [{ source: index.toString(), target: node.lo.toString(), style: { stroke: 'red', lineWidth: 2 } }, { source: index.toString(), target: node.hi.toString(), style: { stroke: 'green', lineWidth: 2 } }]),
+ while (newNodeIds.length > 0 || newLoEdges.length > 0 || newHiEdges.length > 0) {
+ relevantNodeIds.push(...newNodeIds);
+ relevantLoEdges.push(...newLoEdges);
+ relevantHiEdges.push(...newHiEdges);
+
+ newLoEdges = graphProps.lo_edges
+ .filter((edge) => relevantNodeIds.includes(edge[0].toString())
+ && !relevantLoEdges.includes(edge));
+ newHiEdges = graphProps.hi_edges
+ .filter((edge) => relevantNodeIds.includes(edge[0].toString())
+ && !relevantHiEdges.includes(edge));
+
+ newNodeIds = newLoEdges
+ .concat(newHiEdges)
+ .map((edge) => edge[1].toString())
+ .filter((id) => !relevantNodeIds.includes(id));
+ }
+
+ const relevantEdgeIds = relevantLoEdges
+ .map(([source, target]) => `LO_${source}_${target}`)
+ .concat(
+ relevantHiEdges
+ .map(([source, target]) => `HI_${source}_${target}`),
+ );
+
+ console.log(relevantNodeIds);
+ console.log(relevantEdgeIds);
+
+ relevantNodeIds
+ .forEach((id) => {
+ graph.setItemState(id, 'lowlight', false);
+ graph.setItemState(id, 'highlight', true);
+ });
+
+ relevantEdgeIds
+ .forEach((id) => {
+ graph.setItemState(id, 'lowlight', false);
+ });
+
+ // graph.setItemState(nodeItem, 'lowlight', false);
+ // graph.setItemState(nodeItem, 'highlight', true);
+ // nodeItem.getEdges().forEach((edge) => {
+ // graph.setItemState(edge, 'lowlight', false);
+ // });
});
- graph.render();
},
[],
);
- return
;
+ useEffect(
+ () => {
+ const graph = graphRef.current;
+
+ const nodes = Object.keys(graphProps.node_labels).map((id) => {
+ const mainLabel = graphProps.node_labels[id];
+ const subLabel = graphProps.tree_root_labels[id].length > 0 ? `Root for: ${graphProps.tree_root_labels[id].join(' ; ')}` : '';
+
+ const label = subLabel.length > 0 ? `${mainLabel}\n${subLabel}` : mainLabel;
+
+ return {
+ id: id.toString(),
+ label,
+ style: {
+ height: subLabel.length > 0 ? 60 : 30,
+ width: Math.max(30, 10 * mainLabel.length, 10 * subLabel.length),
+ },
+ };
+ });
+ const edges = graphProps.lo_edges.map(([source, target]) => ({
+ id: `LO_${source}_${target}`, source: source.toString(), target: target.toString(), style: { stroke: 'red', lineWidth: 2 },
+ }))
+ .concat(graphProps.hi_edges.map(([source, target]) => ({
+ id: `HI_${source}_${target}`, source: source.toString(), target: target.toString(), style: { stroke: 'green', lineWidth: 2 },
+ })));
+
+ graph.data({
+ nodes,
+ edges,
+ });
+ graph.render();
+ },
+ [graphProps],
+ );
+
+ return ;
}
export default Graph;