diff --git a/graphviz/frontend/src/App.tsx b/graphviz/frontend/src/App.tsx index 39f9967..6e406ce 100644 --- a/graphviz/frontend/src/App.tsx +++ b/graphviz/frontend/src/App.tsx @@ -1,114 +1,192 @@ -import React, { useEffect, useMemo, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import ForceGraph2D from "react-force-graph-2d"; +import * as d3 from "d3-force-3d"; import data from "./data.json"; +function drawRoundedRect(ctx, x, y, width, height, radius) { + const r = Math.min(radius, width / 2, height / 2); + + ctx.beginPath(); + ctx.moveTo(x + r, y); + ctx.lineTo(x + width - r, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + r); + ctx.lineTo(x + width, y + height - r); + ctx.quadraticCurveTo(x + width, y + height, x + width - r, y + height); + ctx.lineTo(x + r, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - r); + ctx.lineTo(x, y + r); + ctx.quadraticCurveTo(x, y, x + r, y); + ctx.closePath(); +} + function buildGraph(data) { const nodes = []; const links = []; - const claimClusterMap = new Map(); - const eventClusterMap = new Map(); - - // Build cluster nodes data.claim_clusters.forEach((cluster) => { - const clusterNode = { + nodes.push({ id: cluster.cluster_id, label: cluster.title || "Unnamed Claim Cluster", type: "claim_cluster", members: cluster.members - }; - nodes.push(clusterNode); - claimClusterMap.set(cluster.cluster_id, clusterNode); + }); }); data.event_clusters.forEach((cluster) => { - const clusterNode = { + nodes.push({ id: cluster.cluster_id, label: cluster.title || "Unnamed Event Cluster", type: "event_cluster", members: cluster.members - }; - nodes.push(clusterNode); - eventClusterMap.set(cluster.cluster_id, clusterNode); + }); }); - // Build links between clusters data.cluster_links.forEach((link) => { - links.push({ source: link.claim_cluster_id, target: link.event_cluster_id }); + links.push({ + source: link.claim_cluster_id, + target: link.event_cluster_id + }); }); return { nodes, links }; } export function App() { + const fgRef = useRef(); const [selectedNode, setSelectedNode] = useState(null); const graphData = useMemo(() => buildGraph(data), []); - function setNode(node) { - console.log(node) - setSelectedNode(node) - } + + useEffect(() => { + if (!fgRef.current) return; + + // Stronger repulsion + fgRef.current.d3Force( + "charge", + d3.forceManyBody().strength(-3000) + ); + + // Link distance + fgRef.current.d3Force( + "link", + d3.forceLink().distance(240) + ); + + // Collision based on dynamic box size + fgRef.current.d3Force( + "collision", + d3.forceCollide((node) => { + const dims = node.__bckgDimensions; + return dims ? Math.max(dims[0], dims[1]) / 2 + 16 : 20; + }) + ); + + fgRef.current.d3ReheatSimulation(); + }, []); + return (
ID: {selectedNode.id}
Type: {selectedNode.type}
-Title / Label: {selectedNode.label}
+Title: {selectedNode.label}
+ {selectedNode.members && (Members:
Click a cluster node to see its members
+Right-click a cluster node to see its members
)}