MCPcopy Create free account
hub / github.com/ScriptedAlchemy/tracedecay / GraphCanvas

Function GraphCanvas

dashboard/graph/src/GraphCanvas.tsx:48–468  ·  view source on GitHub ↗
({
  nodes,
  edges,
  focusId,
  selectedId,
  pathIds,
  fitSignal = 0,
  onSelect,
  onExpand,
}: GraphCanvasProps)

Source from the content-addressed store, hash-verified

46const DIM_ALPHA = 0.13;
47
48export default function GraphCanvas({
49 nodes,
50 edges,
51 focusId,
52 selectedId,
53 pathIds,
54 fitSignal = 0,
55 onSelect,
56 onExpand,
57}: GraphCanvasProps) {
58 const canvasRef = useRef<HTMLCanvasElement | null>(null);
59 const simRef = useRef<Simulation | null>(null);
60 const cameraRef = useRef<Camera>({ x: 0, y: 0, k: 1 });
61 const hoverRef = useRef<SimNode | null>(null);
62 const dragRef = useRef<{ node: SimNode | null; panning: boolean; lastX: number; lastY: number }>(
63 { node: null, panning: false, lastX: 0, lastY: 0 },
64 );
65 const needsRenderRef = useRef(true);
66 const rafRef = useRef(0);
67 // Cached per-frame inputs: CSS-pixel canvas size (refreshed by the
68 // ResizeObserver) and resolved theme tokens (dropped on data-theme flips) —
69 // getBoundingClientRect/getComputedStyle are too expensive at 60fps.
70 const sizeRef = useRef<CanvasSize>({ width: 0, height: 0 });
71 const themeRef = useRef<ThemeColors | null>(null);
72 const propsRef = useRef({ selectedId, pathIds, focusId, onSelect, onExpand });
73 propsRef.current = { selectedId, pathIds, focusId, onSelect, onExpand };
74
75 // Rebuild the simulation when data changes, preserving prior positions.
76 useEffect(() => {
77 simRef.current = createSimulation(nodes, edges, simRef.current, focusId);
78 simRef.current.reheat(0.9);
79 needsRenderRef.current = true;
80 }, [nodes, edges]); // eslint-disable-line react-hooks/exhaustive-deps
81
82 // Fly to the focused node, then keep tracking it while the layout settles
83 // (cleared as soon as the user pans manually).
84 const followIdRef = useRef<string | null>(null);
85 // Keep the whole graph framed while the layout settles after a fit
86 // (cleared on any manual pan/zoom or focus-follow).
87 const followFitRef = useRef(false);
88 useEffect(() => {
89 if (!focusId || !simRef.current) return;
90 const node = simRef.current.nodes.find((n) => n.id === focusId);
91 if (!node) return;
92 const camera = cameraRef.current;
93 camera.x = node.x;
94 camera.y = node.y;
95 camera.k = Math.max(camera.k, 0.8);
96 followIdRef.current = focusId;
97 followFitRef.current = false;
98 needsRenderRef.current = true;
99 }, [focusId, nodes]);
100
101 useEffect(() => {
102 needsRenderRef.current = true;
103 }, [selectedId, pathIds]);
104
105 // Zoom-to-fit: frame the bounding box of every simulated node, and keep

Callers

nothing calls this directly

Calls 7

observeMethod · 0.95
disconnectMethod · 0.95
createSimulationFunction · 0.90
fitCameraToNodesFunction · 0.90
useEffectFunction · 0.85
readSizeFunction · 0.85
reheatMethod · 0.80

Tested by

no test coverage detected