From dbde6b86f4eb7650f2f56c9b99ad3249408386a2 Mon Sep 17 00:00:00 2001 From: monsterkrampe Date: Tue, 30 Aug 2022 13:03:38 +0200 Subject: [PATCH] Finish basic visualization of solve response --- frontend/src/components/app.tsx | 87 +++++++++------- frontend/src/components/graph.tsx | 158 ++++++++++++++++++++++++++---- 2 files changed, 191 insertions(+), 54 deletions(-) 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;