({
allNodes,
allLinks,
communityData,
layoutConfig,
}: UseGraphInstanceOptions)
| 58 | } |
| 59 | |
| 60 | export function useGraphInstance({ |
| 61 | allNodes, |
| 62 | allLinks, |
| 63 | communityData, |
| 64 | layoutConfig, |
| 65 | }: UseGraphInstanceOptions): UseGraphInstanceResult { |
| 66 | // Stable graph instance — created once, never replaced. |
| 67 | const graph = useMemo(() => new Graph({ multi: true, type: 'directed' }), []); |
| 68 | |
| 69 | // Worker ref — persists across renders, terminated on unmount |
| 70 | const workerRef = useRef<Worker | null>(null); |
| 71 | |
| 72 | // Guard against unmount — worker.onmessage may fire after cleanup |
| 73 | const unmountedRef = useRef(false); |
| 74 | |
| 75 | // Track which dataset the worker is computing for (to discard stale results) |
| 76 | const requestIdRef = useRef(0); |
| 77 | |
| 78 | const [layoutReady, setLayoutReady] = useState(false); |
| 79 | |
| 80 | // Derived from config |
| 81 | const flatMode = layoutConfig.flatMode ?? false; |
| 82 | const structuralTypes = useMemo( |
| 83 | () => |
| 84 | flatMode ? new Set<string>() : new Set(layoutConfig.structuralTypes), |
| 85 | [layoutConfig.structuralTypes, flatMode], |
| 86 | ); |
| 87 | |
| 88 | // Single effect: rebuild graph and run d3-force worker when data changes. |
| 89 | useEffect(() => { |
| 90 | graph.clear(); |
| 91 | setLayoutReady(false); |
| 92 | |
| 93 | if (allNodes.length === 0) return; |
| 94 | |
| 95 | const { assignments, colorMap, names } = communityData; |
| 96 | const { getNodeColor, getLinkColor, getCommunityColor } = layoutConfig; |
| 97 | |
| 98 | // ── Pre-compute degree (connection count) per node ───────────────── |
| 99 | const degreeMap = new Map<string, number>(); |
| 100 | for (const link of allLinks) { |
| 101 | const s = endpointId(link.source); |
| 102 | const t = endpointId(link.target); |
| 103 | degreeMap.set(s, (degreeMap.get(s) || 0) + 1); |
| 104 | degreeMap.set(t, (degreeMap.get(t) || 0) + 1); |
| 105 | } |
| 106 | |
| 107 | // ── Build ALL nodes with initial x:0, y:0 ────────────────────────── |
| 108 | const nodeIdSet = new Set<string>(); |
| 109 | const serializedNodes = allNodes.map((node) => { |
| 110 | nodeIdSet.add(node.id); |
| 111 | const typeColor = getNodeColor(node.type); |
| 112 | const commColor = getCommunityColor(assignments, colorMap, node.id); |
| 113 | const cid = assignments[node.id]; |
| 114 | const commName = cid !== undefined ? names.get(cid) : undefined; |
| 115 | const degree = degreeMap.get(node.id) || 0; |
| 116 | const size = nodeSize(degree, node.type, structuralTypes); |
| 117 | return { |
nothing calls this directly
no test coverage detected