@salilvnair/dui 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1235 -1254
- package/dist/index.js.map +1 -1
- package/dist/lib/components/input/HighlightedInputView.d.ts +1 -1
- package/dist/style.css +1 -1
- package/dist/vis-setup.js +323 -240
- package/dist/vis-setup.js.map +1 -1
- package/package.json +1 -1
package/dist/vis-setup.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vis-setup.js","sources":["../src/lib/components/display/NetworkGraphView.vis.tsx","../src/vis-setup.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\nimport { DataSet } from 'vis-data/peer';\nimport { Network } from 'vis-network/peer';\nimport type { Edge as VisEdge, Node as VisNode, Options } from 'vis-network/peer';\nimport type { NetworkGraphViewProps, NetworkGraphNode } from './NetworkGraphView';\n\nconst DEFAULT_PALETTE = [\n '#4E79A7', '#F28E2B', '#E15759', '#76B7B2', '#59A14F',\n '#EDC948', '#B07AA1', '#FF9DA7', '#9C755F', '#BAB0AC',\n];\n\nconst DIMMED_OPACITY = 0.12;\n// Node/edge shadows and continuous-curve edge smoothing look nice but are\n// recomputed by vis-network's own canvas draw loop on every single frame —\n// above this size that cost (not anything in our own event handlers) is\n// what makes zooming/panning feel sluggish on a large graph.\nconst LARGE_GRAPH_NODE_THRESHOLD = 300;\n// Below this zoom the leaf chip labels collide into noise — hide them\n// wholesale and restore on zoom-in. Collapsed community/cluster nodes carry\n// their own always-legible member-count badge natively (see\n// clusterCommunity) instead of a DOM chip, so they're unaffected either way.\nconst LABEL_ZOOM_THRESHOLD = 0.45;\n\n// Canvas can't resolve CSS custom properties (`var(--color-text-primary)`\n// etc. only resolve against a real DOM element's computed style), so the\n// resolved theme name is passed in as a prop and mapped to the same hex\n// values dui's own [data-theme] CSS blocks use — keeps the minimap and\n// edge-label chips visually consistent with the rest of the app in both\n// themes instead of always-white/black.\nconst THEME_TOKENS = {\n dark: {\n text: '#d4d4d4', chipBg: 'rgba(37, 37, 38, 0.85)', chipBorder: 'rgba(255,255,255,0.12)',\n minimapBg: 'rgba(17, 24, 39, 0.85)', minimapBorder: 'rgba(255,255,255,0.2)', viewportStroke: '#ffffff',\n },\n light: {\n text: '#1f2328', chipBg: 'rgba(255, 255, 255, 0.9)', chipBorder: 'rgba(0,0,0,0.12)',\n minimapBg: 'rgba(249, 250, 251, 0.92)', minimapBorder: 'rgba(0,0,0,0.15)', viewportStroke: '#1f2328',\n },\n} as const;\n\nfunction defaultColor(n: NetworkGraphNode): string {\n if (n.color) return n.color;\n if (n.communityId != null) return DEFAULT_PALETTE[n.communityId % DEFAULT_PALETTE.length];\n return '#6B7280';\n}\n\n/** Plain axis-aligned rect used for chip collision checks — deliberately\n * not `DOMRect` (no need for its read-only/class overhead here). */\ninterface Rect {\n left: number;\n top: number;\n right: number;\n bottom: number;\n}\n\ninterface ChipStyle {\n color: string;\n bg: string;\n border: string;\n fontSize: number;\n fontWeight: number;\n padding: string;\n}\n\n/** Creates one dui-ChipView-styled label chip (rounded pill, color-mix\n * background/border, colored text) inside the labels overlay. Shared by\n * node, cluster, and edge chips — only the palette/size differ. */\nfunction createChipElement(overlay: HTMLDivElement | null, text: string, s: ChipStyle): HTMLDivElement {\n const el = document.createElement('div');\n el.textContent = text;\n el.style.position = 'absolute';\n el.style.padding = s.padding;\n el.style.borderRadius = '9999px';\n el.style.fontSize = `${s.fontSize}px`;\n el.style.fontWeight = String(s.fontWeight);\n el.style.letterSpacing = '0.01em';\n el.style.whiteSpace = 'nowrap';\n el.style.pointerEvents = 'none';\n el.style.background = s.bg;\n el.style.border = `1px solid ${s.border}`;\n el.style.color = s.color;\n overlay?.appendChild(el);\n return el;\n}\n\nfunction nodeChipStyle(color: string): ChipStyle {\n return {\n color,\n bg: `color-mix(in srgb, ${color} 16%, transparent)`,\n border: `color-mix(in srgb, ${color} 40%, transparent)`,\n fontSize: 12,\n fontWeight: 600,\n padding: '3px 9px',\n };\n}\n\nfunction edgeChipStyle(tokens: typeof THEME_TOKENS[keyof typeof THEME_TOKENS]): ChipStyle {\n // Neutral, not colored by an endpoint — relationship labels (\"contains\",\n // \"calls\") are secondary to node identity, so they shouldn't visually\n // compete with (or be mistaken for) a node's own colorful chip.\n return {\n color: tokens.text,\n bg: tokens.chipBg,\n border: tokens.chipBorder,\n fontSize: 10,\n fontWeight: 500,\n padding: '2px 7px',\n };\n}\n\n/**\n * Cluster every node sharing communityId `cid` into one collapsed node.\n * No-op if <2 members.\n *\n * Design note (was: a floating DOM \"Community N (count)\" pill chip above\n * every cluster — see git history): with dozens of same-sized communities\n * on a 1000+ node graph, those pills piled into an unreadable stack the\n * instant several sat near each other on screen (no amount of collision\n * nudging fixes running out of room). Matches how graphify/most graph\n * tools handle this instead: the canvas only ever shows geometry (a\n * colored, count-sized circle) plus a compact member-count badge baked\n * into the node's own vis-network label; the full \"Community N (count)\"\n * name lives in the hover tooltip (`title`) and the always-available\n * CommunityLegendPanel sidebar list (ns9-ui), never as an always-on\n * floating label competing for space with its neighbors.\n */\nfunction clusterCommunity(network: Network, cid: number, allNodes: NetworkGraphNode[]): void {\n const memberIds = new Set(allNodes.filter(n => n.communityId === cid).map(n => n.id));\n if (memberIds.size < 2) return;\n // Already clustered (or partially) — skip, avoids vis-network's \"cluster already exists\" throw.\n for (const id of memberIds) {\n if (!network.findNode(id).length) return;\n if (network.isCluster(id)) return;\n }\n const baseColor = DEFAULT_PALETTE[cid % DEFAULT_PALETTE.length];\n const clusterId = `cluster:community:${cid}`;\n const label = `Community ${cid} (${memberIds.size})`;\n const size = Math.min(20 + memberIds.size * 2, 60);\n // vis-network's 'dot' shape always draws its label BELOW the node (an\n // \"external label\"), never inside — there's no built-in \"labelled circle\n // sized independently of its text\" mode. `vadjust` (a font option, added\n // to that external position) is the documented way to relocate it;\n // shifting up by ~(radius + half the badge's own text height) lands the\n // count roughly centered inside the circle instead of floating under it.\n const badgeFontSize = Math.max(11, Math.min(16, Math.round(size * 0.32)));\n const vadjust = -(size + badgeFontSize * 0.5);\n network.cluster({\n joinCondition: (nodeOptions) => memberIds.has(nodeOptions.id as string),\n // vis-network genuinely accepts `id` in clusterNodeProperties at runtime\n // (it's how you address the cluster later via openCluster/isCluster) —\n // the shipped .d.ts's NodeOptions type just doesn't declare it.\n clusterNodeProperties: {\n id: clusterId,\n // Member count only — the full name is tooltip + legend-panel only\n // (see the doc comment above). Not empty string: vis-network defaults\n // `label` to the literal \"cluster\" when it's `undefined`, which would\n // otherwise draw a stray caption under every collapsed community.\n label: String(memberIds.size),\n font: { color: '#ffffff', size: badgeFontSize, vadjust },\n title: label,\n shape: 'dot',\n // Larger + white-ringed so a collapsed community reads as a distinct\n // \"group\" at a glance, not just another same-sized leaf node.\n size,\n borderWidth: 3,\n color: { background: baseColor, border: '#ffffff', highlight: { background: baseColor, border: '#ffffff' } },\n // Soft colored glow (a hint of the community's own color) instead of a\n // flat plain circle — same \"elevated card\" depth language dui's own\n // panels use, just expressed on canvas.\n shadow: { enabled: true, color: `${baseColor}66`, size: 16, x: 0, y: 4 },\n } as VisNode & { id: string },\n });\n}\n\nexport function NetworkGraphViewImpl({\n nodes, edges, onNodeClick, selectedId, fitTrigger, onReady, colorBy, sizeBy, className = '', style,\n enableClustering = false, enableHoverDim = false, enableMinimap = false, theme = 'dark',\n}: NetworkGraphViewProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const minimapCanvasRef = useRef<HTMLCanvasElement>(null);\n const networkRef = useRef<Network | null>(null);\n const nodesDataRef = useRef<DataSet<VisNode> | null>(null);\n const edgesDataRef = useRef<DataSet<VisEdge> | null>(null);\n const nodesById = useRef<Map<string, NetworkGraphNode>>(new Map());\n const neighborMapRef = useRef<Map<string, Set<string>>>(new Map());\n // node id -> ids of every edge touching it. Lets hoverNode/blurNode look\n // up \"which edges does this node touch\" in O(degree) instead of scanning\n // every edge in the graph on every hover — see the hoverNode handler.\n const nodeEdgesRef = useRef<Map<string, Set<string | number>>>(new Map());\n // Every label (node, collapsed-community, edge) renders as a real DOM chip\n // styled like dui's own ChipView (rounded pill, color-mix background/\n // border, colored text) instead of vis-network's canvas-only\n // `font.background`, which is a plain rectangle with no border-radius, no\n // real padding, and — short of computing per-node canvas fill colors by\n // hand — no way to tint it from outside the library. DOM chips also fix\n // the \"huge overlapping text at high zoom\" complaint for free: unlike\n // vis's canvas labels (which scale with zoom, so they can balloon and\n // collide at high zoom), these stay a fixed on-screen size regardless of\n // zoom level.\n const labelsOverlayRef = useRef<HTMLDivElement>(null);\n const nodeChipsRef = useRef<Map<string, HTMLDivElement>>(new Map());\n const edgeChipsRef = useRef<Map<string | number, HTMLDivElement>>(new Map());\n // Kept current every render (not via its own effect — just needs to be\n // readable-without-a-stale-closure from inside the settle callbacks\n // below, which are registered once per network instance via `.once()`).\n const selectedIdRef = useRef<string | null | undefined>(selectedId);\n selectedIdRef.current = selectedId;\n // Set while a network.focus()/moveTo() camera animation is in flight (see\n // the selectedId effect + the 'animationFinished' listener below). On a\n // graph with hundreds/thousands of chips, syncAllChipPositions and the\n // minimap redraw were still firing at their throttled ~10/sec cadence\n // DURING every one of vis-network's 1s click-to-focus animations — each\n // pass does a getBoundingBox/canvasToDOM + forced-reflow offsetWidth/\n // offsetHeight read PER CHIP, which is the actual \"slow motion\" jank on\n // large graphs (vis-network's own canvas redraw is comparatively cheap).\n // Skipping chip/minimap sync entirely while the camera is moving — and\n // doing exactly one full sync when it lands — removes ~10 of those\n // passes per click with no visible cost (chips settle the instant the\n // animation ends, same as they did after every throttled tick before).\n const isCameraAnimatingRef = useRef(false);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n // Declared here (not inside the enableHoverDim block below that uses\n // it) so this effect's own cleanup can clear a still-pending timer on\n // unmount — otherwise a debounced blur restore could fire after\n // network.destroy() and touch a torn-down dataset.\n let blurRestoreTimer: ReturnType<typeof setTimeout> | null = null;\n\n nodesById.current = new Map(nodes.map(n => [n.id, n]));\n\n const degree: Record<string, number> = {};\n const neighborMap = new Map<string, Set<string>>();\n const nodeEdges = new Map<string, Set<string | number>>();\n edges.forEach((e, i) => {\n degree[e.source] = (degree[e.source] ?? 0) + 1;\n degree[e.target] = (degree[e.target] ?? 0) + 1;\n if (!neighborMap.has(e.source)) neighborMap.set(e.source, new Set());\n if (!neighborMap.has(e.target)) neighborMap.set(e.target, new Set());\n neighborMap.get(e.source)!.add(e.target);\n neighborMap.get(e.target)!.add(e.source);\n // Same id derivation as visEdges below (e.id ?? i) — same array, same\n // iteration order, so the ids line up.\n const edgeId = e.id ?? i;\n if (!nodeEdges.has(e.source)) nodeEdges.set(e.source, new Set());\n if (!nodeEdges.has(e.target)) nodeEdges.set(e.target, new Set());\n nodeEdges.get(e.source)!.add(edgeId);\n nodeEdges.get(e.target)!.add(edgeId);\n });\n neighborMapRef.current = neighborMap;\n nodeEdgesRef.current = nodeEdges;\n const maxDeg = Math.max(1, ...Object.values(degree));\n\n const themeTokens = THEME_TOKENS[theme];\n const nodeColors = new Map<string, string>();\n\n const visNodes: VisNode[] = nodes.map(n => {\n const color = colorBy ? colorBy(n) : defaultColor(n);\n nodeColors.set(n.id, color);\n const deg = degree[n.id] ?? 1;\n const size = sizeBy ? sizeBy(n, deg, maxDeg) : 10 + 30 * (deg / maxDeg);\n return {\n id: n.id,\n title: n.label,\n // Selection keeps the node's own fill and flags it with a themed\n // border instead — the old highlight swapped the fill to the theme\n // text color, which read as a jarring near-black circle in light\n // mode (and near-white in dark).\n color: { background: color, border: color, highlight: { background: color, border: themeTokens.text } },\n size: Math.round(size * 10) / 10,\n shape: 'dot',\n };\n });\n\n const visEdges: VisEdge[] = edges.map((e, i) => ({\n id: e.id ?? i,\n from: e.source,\n to: e.target,\n title: e.type,\n // Blends from the source node's color to the target's at the\n // midpoint instead of a flat gray line — a vis-network-native\n // stand-in for the gradient edges ck8t's ReactFlow canvas uses.\n color: { inherit: 'both', opacity: 0.55 },\n arrows: { to: { enabled: true, scaleFactor: 0.6 } },\n }));\n\n const nodesData = new DataSet(visNodes);\n const edgesData = new DataSet(visEdges);\n nodesDataRef.current = nodesData;\n edgesDataRef.current = edgesData;\n\n // Above LARGE_GRAPH_NODE_THRESHOLD, shadows + continuous-curve edges are\n // disabled — see the constant's comment. Small graphs keep the nicer look.\n const isLargeGraph = nodes.length > LARGE_GRAPH_NODE_THRESHOLD;\n\n const options: Options = {\n physics: {\n enabled: true,\n solver: 'forceAtlas2Based',\n forceAtlas2Based: {\n gravitationalConstant: -60,\n centralGravity: 0.005,\n springLength: 120,\n springConstant: 0.08,\n damping: 0.4,\n avoidOverlap: 0.8,\n },\n stabilization: { iterations: 200, fit: true },\n },\n // hideEdgesOnZoom mirrors hideEdgesOnDrag below — edges (continuous\n // smooth curves + shadows) are by far the most expensive thing on\n // canvas to redraw every frame; hiding them for the duration of a\n // zoom gesture (mouse wheel or pinch) is what actually fixes \"zoom\n // feels like a turtle\" on a large graph, vs. anything in our own\n // event handlers (which only ever ran custom code on TOP of vis's\n // own native per-frame draw loop, never touched by throttling it).\n interaction: {\n hover: true, tooltipDelay: 100,\n hideEdgesOnDrag: true, hideEdgesOnZoom: true,\n navigationButtons: false,\n },\n // Soft drop shadow on every node — same elevated-card depth cue dui's\n // own panels use, expressed via canvas shadow instead of CSS box-shadow.\n // Shadows aren't covered by hideEdgesOnZoom (nodes stay visible while\n // zooming) and shadowBlur is one of canvas's most expensive per-shape\n // operations — skip them above LARGE_GRAPH_NODE_THRESHOLD.\n nodes: {\n shape: 'dot', borderWidth: 1.5, borderWidthSelected: 3,\n shadow: { enabled: !isLargeGraph, color: 'rgba(0,0,0,0.35)', size: 8, x: 0, y: 3 },\n },\n edges: {\n // 'continuous' recomputes bezier control points per edge per draw —\n // the most expensive of vis-network's smooth types. Straight lines\n // above the threshold; edges are hidden during zoom either way.\n smooth: isLargeGraph ? false : { enabled: true, type: 'continuous', roundness: 0.25 },\n shadow: { enabled: !isLargeGraph, color: 'rgba(0,0,0,0.12)', size: 3, x: 0, y: 1 },\n },\n };\n\n const network = new Network(containerRef.current, { nodes: nodesData, edges: edgesData }, options);\n networkRef.current = network;\n\n const drawMinimap = () => {\n const canvas = minimapCanvasRef.current;\n if (!canvas || !enableMinimap) return;\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n const positions = network.getPositions();\n const xs = Object.values(positions).map(p => p.x);\n const ys = Object.values(positions).map(p => p.y);\n if (xs.length === 0) return;\n const minX = Math.min(...xs), maxX = Math.max(...xs);\n const minY = Math.min(...ys), maxY = Math.max(...ys);\n const w = canvas.width, h = canvas.height;\n const spanX = Math.max(1, maxX - minX), spanY = Math.max(1, maxY - minY);\n const toCanvas = (x: number, y: number): [number, number] => [\n ((x - minX) / spanX) * (w - 10) + 5,\n ((y - minY) / spanY) * (h - 10) + 5,\n ];\n\n ctx.clearRect(0, 0, w, h);\n ctx.fillStyle = themeTokens.minimapBg;\n ctx.fillRect(0, 0, w, h);\n\n Object.entries(positions).forEach(([id, pos]) => {\n const [cx, cy] = toCanvas(pos.x, pos.y);\n const n = nodesById.current.get(id);\n ctx.fillStyle = n ? (colorBy ? colorBy(n) : defaultColor(n)) : '#6B7280';\n ctx.beginPath();\n // Degree-scaled dots so hubs read as landmarks when orienting.\n const deg = degree[id] ?? 0;\n ctx.arc(cx, cy, 1 + 1.5 * (deg / maxDeg), 0, Math.PI * 2);\n ctx.fill();\n });\n\n // Viewport rectangle\n const scale = network.getScale();\n const viewPos = network.getViewPosition();\n const canvasEl = containerRef.current;\n if (canvasEl && scale > 0) {\n const viewW = canvasEl.clientWidth / scale;\n const viewH = canvasEl.clientHeight / scale;\n const [rx1, ry1] = toCanvas(viewPos.x - viewW / 2, viewPos.y - viewH / 2);\n const [rx2, ry2] = toCanvas(viewPos.x + viewW / 2, viewPos.y + viewH / 2);\n ctx.strokeStyle = themeTokens.viewportStroke;\n ctx.lineWidth = 1;\n ctx.strokeRect(rx1, ry1, rx2 - rx1, ry2 - ry1);\n }\n };\n\n // ── Label chips (nodes, edges — NOT clusters, see clusterCommunity) ────\n // Node chips are created once up front (one per dataset node); their\n // visibility (declutter / absorbed-into-a-cluster) is decided every\n // sync, not at creation time.\n for (const n of nodes) {\n nodeChipsRef.current.set(n.id, createChipElement(labelsOverlayRef.current, n.label, nodeChipStyle(nodeColors.get(n.id) ?? '#6B7280')));\n }\n edges.forEach((e, i) => {\n if (!e.type) return;\n const id = e.id ?? i;\n edgeChipsRef.current.set(id, createChipElement(labelsOverlayRef.current, e.type, edgeChipStyle(themeTokens)));\n });\n\n /**\n * Positions every (non-cluster — see clusterCommunity) chip and decides\n * what's visible this frame:\n * - Node chips hide when their node is absorbed into a collapsed\n * cluster, or when fully zoomed out (declutter threshold).\n * - A visible node chip is never hidden for overlapping another —\n * instead it's nudged in a small grid search (a handful of pixels at\n * a time, capped) until it clears, the same \"stack the overlapping\n * pins\" trick map UIs use for crowded marker labels.\n * - Edge chips hide when either endpoint isn't currently visible\n * (absorbed into a cluster — the underlying edge itself isn't drawn\n * either), when fully zoomed out, OR when they'd visually overlap a\n * node chip (node identity wins over relationship labels).\n */\n const rectsOverlap = (a: Rect, b: Rect) =>\n a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top;\n\n const syncAllChipPositions = () => {\n const declutter = network.getScale() < LABEL_ZOOM_THRESHOLD;\n const reserved: Rect[] = [];\n const MAX_NUDGES = 12; // 4 rows × 3 columns (center/left/right per row)\n\n for (const [id, el] of nodeChipsRef.current) {\n if (declutter || network.findNode(id).length !== 1) {\n // Zoomed out, or this node is currently absorbed into a collapsed\n // community — nothing to position, just hide.\n el.style.display = 'none';\n continue;\n }\n const box = network.getBoundingBox(id);\n const domPos = network.canvasToDOM({ x: (box.left + box.right) / 2, y: box.bottom });\n el.style.display = '';\n const w = el.offsetWidth, h = el.offsetHeight;\n const baseLeft = domPos.x - w / 2;\n const baseTop = domPos.y + 6;\n // Grid search, not a straight-down stack: two overlapping chips are\n // just as often side-by-side (adjacent communities at similar\n // height) as stacked, and a pure vertical nudge never resolves a\n // horizontal collision. Each attempt tries center/left/right at a\n // given row before dropping to the next row down.\n let left = baseLeft, top = baseTop;\n for (let n = 0; n < MAX_NUDGES; n++) {\n const row = Math.floor(n / 3);\n const col = (n % 3) - 1; // -1, 0, 1\n left = baseLeft + col * (w + 6);\n top = baseTop + row * (h + 3);\n if (!reserved.some(r => rectsOverlap({ left, top, right: left + w, bottom: top + h }, r))) break;\n }\n el.style.left = `${left}px`;\n el.style.top = `${top}px`;\n reserved.push({ left, top, right: left + w, bottom: top + h });\n }\n\n for (const [id, el] of edgeChipsRef.current) {\n const e = edgesData.get(id) as VisEdge | null;\n const fromChain = e ? network.findNode(e.from as string) : [];\n const toChain = e ? network.findNode(e.to as string) : [];\n const bothVisible = fromChain.length === 1 && toChain.length === 1;\n if (declutter || !e || !bothVisible) {\n el.style.display = 'none';\n continue;\n }\n const positions = network.getPositions([e.from as string, e.to as string]);\n const from = positions[e.from as string];\n const to = positions[e.to as string];\n if (!from || !to) {\n el.style.display = 'none';\n continue;\n }\n const domPos = network.canvasToDOM({ x: (from.x + to.x) / 2, y: (from.y + to.y) / 2 });\n const w = el.offsetWidth, h = el.offsetHeight;\n const rect: Rect = { left: domPos.x - w / 2, top: domPos.y - h / 2, right: domPos.x + w / 2, bottom: domPos.y + h / 2 };\n\n // Collision check against every node/cluster chip (and every\n // earlier-placed edge chip) already reserved this frame — an edge's\n // relationship label loses to a node's identity label.\n if (reserved.some(r => rectsOverlap(rect, r))) {\n el.style.display = 'none';\n continue;\n }\n el.style.display = '';\n el.style.left = `${rect.left}px`;\n el.style.top = `${rect.top}px`;\n reserved.push(rect);\n }\n };\n\n let lastChipSync = 0;\n const syncAllChipPositionsThrottled = () => {\n // A network.focus()/moveTo() camera animation is running — chips will\n // get one full, non-throttled sync the instant it lands (see the\n // 'animationFinished' listener below). Skipping them mid-flight is\n // what actually fixes the large-graph \"slow motion\" click-to-zoom.\n if (isCameraAnimatingRef.current) return;\n const now = Date.now();\n // ~10 syncs/second — with up to a few hundred chips, an unthrottled\n // per-frame sync (every canvas redraw during a drag/zoom gesture) was\n // a measurable jank contributor, same lesson as the minimap below.\n if (now - lastChipSync < 100) return;\n lastChipSync = now;\n syncAllChipPositions();\n };\n\n network.once('stabilizationIterationsDone', () => {\n network.setOptions({ physics: { enabled: false } });\n let willRunClusterSeparationBurst = false;\n if (enableClustering) {\n const communityIds = new Set(nodes.map(n => n.communityId).filter((c): c is number => c != null));\n communityIds.forEach(cid => clusterCommunity(network, cid, nodes));\n // Collapsed community super-nodes inherit their members' centroid\n // position and can land overlapping each other. One short physics\n // burst separates them, then\n // physics goes back off so the layout stays stable.\n if (communityIds.size > 1) {\n willRunClusterSeparationBurst = true;\n network.once('stabilizationIterationsDone', () => {\n network.setOptions({ physics: { enabled: false } });\n drawMinimap();\n syncAllChipPositions();\n // A selectedId set before this rebuild's stabilization finished\n // (e.g. a filter change that just made the selected node visible\n // again) needs to be (re-)applied here — the standalone\n // selectedId effect below fires as soon as the network is\n // (re)created, well before physics has settled, so its focus()\n // call is invisibly overwritten by physics ticks still in\n // progress. This is the true final settle point; re-focus wins.\n if (selectedIdRef.current && network.findNode(selectedIdRef.current).length > 0) {\n network.selectNodes([selectedIdRef.current]);\n network.focus(selectedIdRef.current, { scale: 1.4, animation: true });\n isCameraAnimatingRef.current = true;\n }\n // Genuinely ready only now — calling onReady from the outer\n // callback too would reveal the canvas mid-cluster-separation\n // (nodes still visibly reflowing), the exact ugly transient\n // state onReady exists to hide.\n onReady?.();\n });\n network.setOptions({\n physics: { enabled: true, stabilization: { iterations: 80, fit: false } },\n });\n network.stabilize(80);\n }\n }\n // Slight zoom-out so labels/nodes at the layout's bounding box aren't\n // flush against (and clipped by) the viewport edges.\n const scale = network.getScale();\n if (scale > 0) network.moveTo({ scale: scale * 0.92 });\n drawMinimap();\n syncAllChipPositions();\n if (!willRunClusterSeparationBurst) {\n // See the matching comment in the nested cluster-separation settle\n // callback above — re-apply a pre-set selectedId here since this is\n // the true final settle point when no cluster-separation burst is\n // coming.\n if (selectedIdRef.current && network.findNode(selectedIdRef.current).length > 0) {\n network.selectNodes([selectedIdRef.current]);\n network.focus(selectedIdRef.current, { scale: 1.4, animation: true });\n isCameraAnimatingRef.current = true;\n }\n onReady?.();\n }\n });\n\n network.on('afterDrawing', syncAllChipPositionsThrottled);\n network.on('dragEnd', syncAllChipPositions);\n\n // vis-network's own animation-driven redraws (focus()/moveTo() with\n // animation: true) are what 'animationFinished' marks the end of — see\n // isCameraAnimatingRef's declaration above for why chip/minimap sync is\n // skipped while one is in flight.\n network.on('animationFinished', () => {\n isCameraAnimatingRef.current = false;\n syncAllChipPositions();\n if (enableMinimap) drawMinimap();\n });\n\n network.on('click', (params) => {\n if (params.nodes.length === 0) return;\n const nodeId = params.nodes[0];\n const n = nodesById.current.get(nodeId);\n if (n) {\n onNodeClick?.(n);\n return;\n }\n // Clicked node has no backing NetworkGraphNode — this is a collapsed\n // cluster's synthetic id (e.g. \"cluster:community:5\"), not a real node.\n // Still fire the callback with a lightweight synthetic node so the\n // caller's click handling (e.g. a side-panel summary) actually runs\n // instead of silently doing nothing, which is what a plain lookup\n // miss used to produce.\n if (network.isCluster(nodeId)) {\n onNodeClick?.({ id: nodeId, label: nodeId });\n }\n });\n\n if (enableClustering) {\n network.on('doubleClick', (params) => {\n if (params.nodes.length !== 1) return;\n const nodeId = params.nodes[0];\n if (network.isCluster(nodeId)) {\n network.openCluster(nodeId);\n } else {\n const n = nodesById.current.get(nodeId);\n if (n?.communityId != null) clusterCommunity(network, n.communityId, nodes);\n }\n drawMinimap();\n syncAllChipPositions();\n });\n }\n\n if (enableHoverDim) {\n // Diff against the PREVIOUS \"kept\" (opacity-1) set instead of\n // rewriting every node/edge on every hoverNode. hoverNode fires on\n // every node the mouse passes over while simply moving across the\n // canvas (not just on click) — a full nodesData.update() +\n // edgesData.update() over the WHOLE graph (1200+/1500+ items, each a\n // real vis-data change event → canvas redraw) on every single one of\n // those was the actual \"everything feels slow\" cause on a large\n // graph. nodeEdgesRef (built above, once) makes \"which edges touch\n // this node\" O(degree) instead of an O(all edges) scan too.\n //\n // A previous version of this fix (see git history if curious) had\n // hoverNode and blurNode using DIFFERENT, asymmetric logic — blurNode\n // \"restored\" prevKeptNodeIds (the already-correct, already-opacity-1\n // set) instead of the actually-dimmed complement, which is backwards:\n // it left everything ELSE stuck dimmed forever, visible as \"hovering\n // anything makes the graph disappear and it never comes back.\"\n // applyKeep() below is now the ONE diff routine both events call,\n // just with a different target \"keepNodes\" set — hoverNode passes\n // {hoveredId, neighbors}, blurNode passes \"everyone\" (the correct\n // definition of \"nothing is dimmed\", not the empty set).\n let prevKeptNodeIds = new Set<string>(nodesData.getIds() as string[]);\n let prevKeptEdgeIds = new Set<string | number>(edgesData.getIds());\n // blurNode is debounced: it fires just as often as hoverNode (every\n // node the mouse LEAVES while moving), and moving between two\n // adjacent nodes fires blur(A) then hover(B) back-to-back — doing the\n // full \"restore everyone\" work synchronously in blur would mean B's\n // hover immediately re-dims most of it again, paying the O(n) cost\n // TWICE per transition instead of once. Deferring it a beat lets a\n // following hoverNode cancel the pending restore and build its own\n // (cheap, small-diff) transition directly on top of whatever's\n // currently dimmed — the full restore only actually runs once the\n // mouse has genuinely stopped hovering anything. (blurRestoreTimer\n // itself is declared at the top of this effect, not here — so this\n // effect's cleanup can also clear it.)\n\n const applyKeep = (keepNodes: Set<string>, keepEdges: Set<string | number>) => {\n const nodeUpdates: { id: string; opacity: number }[] = [];\n for (const id of keepNodes) if (!prevKeptNodeIds.has(id)) nodeUpdates.push({ id, opacity: 1 });\n for (const id of prevKeptNodeIds) if (!keepNodes.has(id)) nodeUpdates.push({ id, opacity: DIMMED_OPACITY });\n if (nodeUpdates.length) nodesData.update(nodeUpdates);\n\n const edgeUpdates: { id: string | number; color: { inherit: 'both'; opacity: number } }[] = [];\n // `color` is replaced wholesale on update (not deep-merged), so\n // `inherit` has to be repeated here — omitting it would silently\n // drop the gradient-edge effect the instant an edge changes state.\n for (const id of keepEdges) {\n if (!prevKeptEdgeIds.has(id)) edgeUpdates.push({ id, color: { inherit: 'both', opacity: 0.9 } });\n }\n for (const id of prevKeptEdgeIds) {\n if (!keepEdges.has(id)) edgeUpdates.push({ id, color: { inherit: 'both', opacity: DIMMED_OPACITY } });\n }\n if (edgeUpdates.length) edgesData.update(edgeUpdates);\n\n for (const [id, el] of nodeChipsRef.current) {\n if (keepNodes.has(id) !== prevKeptNodeIds.has(id)) {\n el.style.opacity = keepNodes.has(id) ? '1' : String(DIMMED_OPACITY);\n }\n }\n for (const [id, el] of edgeChipsRef.current) {\n if (keepEdges.has(id) !== prevKeptEdgeIds.has(id)) {\n el.style.opacity = keepEdges.has(id) ? '1' : String(DIMMED_OPACITY);\n }\n }\n\n prevKeptNodeIds = keepNodes;\n prevKeptEdgeIds = keepEdges;\n };\n\n network.on('hoverNode', (params) => {\n if (blurRestoreTimer !== null) {\n clearTimeout(blurRestoreTimer);\n blurRestoreTimer = null;\n }\n const hoveredId: string = params.node;\n const neighbors = neighborMapRef.current.get(hoveredId) ?? new Set();\n // Real ids only. hoveredId can be a SYNTHETIC cluster id\n // (\"cluster:community:N\") — vis-network fires hoverNode for\n // collapsed communities too, and neighborMapRef (built from the\n // original nodes/edges, pre-clustering) never contains one. Left\n // unfiltered, pushing a cluster id into nodesData.update() silently\n // INSERTS a phantom real DataSet node sharing that same id —\n // colliding with and hijacking vis-network's own internal virtual\n // rendering of that cluster (part of the \"community circles\n // disappear\" bug — verified live: this INSERT is real, confirmed\n // by inspecting network.body.data.nodes before/after a direct\n // hoverNode emit on a cluster id).\n const keepNodes = new Set<string>(\n [hoveredId, ...neighbors].filter((id) => nodesData.get(id) != null)\n );\n const keepEdges = nodeEdgesRef.current.get(hoveredId) ?? new Set<string | number>();\n applyKeep(keepNodes, keepEdges);\n });\n\n network.on('blurNode', () => {\n blurRestoreTimer = setTimeout(() => {\n blurRestoreTimer = null;\n applyKeep(new Set(nodesData.getIds() as string[]), new Set(edgesData.getIds()));\n }, 80);\n });\n }\n\n if (enableMinimap) {\n network.on('dragEnd', drawMinimap);\n // 'zoom' fires on every scale-level change — completely unthrottled —\n // and mouse-wheel zooming fires a burst of these per tick, each one\n // previously doing a full getPositions() + one arc per node redraw.\n // On a 1,242-node graph that's the \"turtle\" scroll-to-zoom lag: shares\n // the same ~10/sec throttle budget as afterDrawing below so a fast\n // scroll doesn't queue up dozens of full minimap repaints.\n let lastMinimapDraw = 0;\n const throttledDrawMinimap = () => {\n if (isCameraAnimatingRef.current) return;\n const now = Date.now();\n if (now - lastMinimapDraw < 100) return;\n lastMinimapDraw = now;\n drawMinimap();\n };\n network.on('zoom', throttledDrawMinimap);\n // afterDrawing fires on EVERY canvas render — during pane resizes or\n // physics ticks that meant a full minimap repaint (getPositions + one\n // arc per node) per frame, a visible drag-lag contributor. Gate it to\n // ~10 repaints/second; dragEnd above still repaints instantly.\n network.on('afterDrawing', throttledDrawMinimap);\n }\n\n return () => {\n if (blurRestoreTimer !== null) clearTimeout(blurRestoreTimer);\n network.destroy();\n networkRef.current = null;\n nodesDataRef.current = null;\n edgesDataRef.current = null;\n for (const el of nodeChipsRef.current.values()) el.remove();\n nodeChipsRef.current.clear();\n for (const el of edgeChipsRef.current.values()) el.remove();\n edgeChipsRef.current.clear();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [nodes, edges, enableClustering, enableHoverDim, enableMinimap, theme]);\n\n useEffect(() => {\n const network = networkRef.current;\n if (!network || !selectedId) return;\n // `nodesById` only indexes the original (pre-cluster) node list, so a\n // collapsed cluster's synthetic id (e.g. \"cluster:community:5\") always\n // missed this check and never got the focus/zoom treatment real nodes\n // get. `network.findNode` looks up vis-network's own live node index,\n // which includes cluster super-nodes, so it correctly covers both.\n if (network.findNode(selectedId).length > 0) {\n network.selectNodes([selectedId]);\n // Set AFTER focus(), not before: vis-network forcibly finishes any\n // still-running animation synchronously inside focus() itself (and\n // synchronously emits 'animationFinished' for THAT one) before it\n // starts this new one — rapidly clicking a second node mid-animation\n // would otherwise have our own 'animationFinished' handler for the\n // interrupted animation flip the flag back to false a moment after\n // we'd set it true, immediately un-gating sync for the new animation\n // that just started.\n network.focus(selectedId, { scale: 1.4, animation: true });\n isCameraAnimatingRef.current = true;\n }\n // `nodes` is included so a previously-set selectedId gets re-applied to\n // a freshly (re)created network — e.g. a caller relaxing a node-type\n // filter that was hiding the selected node rebuilds `network` (see the\n // `[nodes, edges, ...]` effect above) without `selectedId` itself ever\n // changing, and the fresh network otherwise never learns it should\n // focus on it.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [selectedId, nodes]);\n\n // \"Reset zoom\" — a genuine zoom-to-fit-all, recomputed fresh every time\n // (the same bounding-box calculation vis-network runs when you scroll the\n // mouse wheel all the way out), not a restore of some earlier captured\n // camera snapshot — a snapshot can go stale (e.g. once a cluster has been\n // expanded, or after a click-to-focus) and \"reset\" would silently stop\n // matching what \"totally zoomed out\" actually looks like right now.\n // Skips the initial mount (fitTrigger starts at 0/undefined) — only fires\n // on a genuine increment from the host app's Reset button.\n const prevFitTriggerRef = useRef(fitTrigger);\n useEffect(() => {\n const network = networkRef.current;\n if (!network || fitTrigger === undefined || fitTrigger === prevFitTriggerRef.current) {\n prevFitTriggerRef.current = fitTrigger;\n return;\n }\n prevFitTriggerRef.current = fitTrigger;\n network.unselectAll();\n network.fit({ animation: true });\n isCameraAnimatingRef.current = true;\n }, [fitTrigger]);\n\n const minimapToGraph = (evt: { clientX: number; clientY: number }): { x: number; y: number } | null => {\n const network = networkRef.current;\n const canvas = minimapCanvasRef.current;\n if (!network || !canvas) return null;\n const positions = network.getPositions();\n const xs = Object.values(positions).map(p => p.x);\n const ys = Object.values(positions).map(p => p.y);\n if (xs.length === 0) return null;\n const minX = Math.min(...xs), maxX = Math.max(...xs);\n const minY = Math.min(...ys), maxY = Math.max(...ys);\n const rect = canvas.getBoundingClientRect();\n const cx = evt.clientX - rect.left, cy = evt.clientY - rect.top;\n const spanX = Math.max(1, maxX - minX), spanY = Math.max(1, maxY - minY);\n return {\n x: ((cx - 5) / (canvas.width - 10)) * spanX + minX,\n y: ((cy - 5) / (canvas.height - 10)) * spanY + minY,\n };\n };\n\n const handleMinimapClick = (evt: React.MouseEvent<HTMLCanvasElement>) => {\n const pos = minimapToGraph(evt);\n if (pos) networkRef.current?.moveTo({ position: pos, animation: true });\n };\n\n // Drag-the-viewport panning (UIP-3): press-and-drag on the minimap pans\n // the main canvas continuously (no animation during drag — it would lag\n // behind the pointer). Click-to-center still works via handleMinimapClick.\n const minimapDragging = useRef(false);\n const handleMinimapMouseDown = (evt: React.MouseEvent<HTMLCanvasElement>) => {\n minimapDragging.current = true;\n const move = (e: MouseEvent) => {\n if (!minimapDragging.current) return;\n const pos = minimapToGraph(e);\n if (pos) networkRef.current?.moveTo({ position: pos });\n };\n const up = () => {\n minimapDragging.current = false;\n window.removeEventListener('mousemove', move);\n window.removeEventListener('mouseup', up);\n };\n window.addEventListener('mousemove', move);\n window.addEventListener('mouseup', up);\n evt.preventDefault();\n };\n\n return (\n <div className={className} style={{ width: '100%', height: '100%', position: 'relative', ...style }}>\n <div ref={containerRef} style={{ width: '100%', height: '100%' }} />\n <div ref={labelsOverlayRef} style={{ position: 'absolute', inset: 0, overflow: 'hidden', pointerEvents: 'none' }} />\n {enableMinimap && (\n <canvas\n ref={minimapCanvasRef}\n width={140}\n height={100}\n onClick={handleMinimapClick}\n onMouseDown={handleMinimapMouseDown}\n style={{\n // bottom 72 (not 8): host apps commonly float a chat/action\n // orb in the bottom-right corner — leave that spot free.\n position: 'absolute', bottom: 72, right: 8,\n borderRadius: 4, border: `1px solid ${THEME_TOKENS[theme].minimapBorder}`,\n cursor: 'grab',\n }}\n />\n )}\n </div>\n );\n}\n","/**\n * Opt-in setup for NetworkGraphView — import this once at app startup after\n * installing 'vis-network' + 'vis-data' (both peerDependenciesMeta.optional):\n *\n * import '@salilvnair/dui/vis-setup';\n *\n * Registers the real vis-network-backed implementation; NetworkGraphView\n * renders a static fallback until this has run.\n */\nimport { registerNetworkGraphImpl, markVisReady } from './lib/vis-runtime';\nimport { NetworkGraphViewImpl } from './lib/components/display/NetworkGraphView.vis';\n\nregisterNetworkGraphImpl(NetworkGraphViewImpl);\nmarkVisReady();\n"],"names":["DEFAULT_PALETTE","DIMMED_OPACITY","LARGE_GRAPH_NODE_THRESHOLD","LABEL_ZOOM_THRESHOLD","THEME_TOKENS","defaultColor","n","createChipElement","overlay","text","s","el","nodeChipStyle","color","edgeChipStyle","tokens","clusterCommunity","network","cid","allNodes","memberIds","id","baseColor","clusterId","label","size","badgeFontSize","vadjust","nodeOptions","NetworkGraphViewImpl","nodes","edges","onNodeClick","selectedId","fitTrigger","onReady","colorBy","sizeBy","className","style","enableClustering","enableHoverDim","enableMinimap","theme","containerRef","useRef","minimapCanvasRef","networkRef","nodesDataRef","edgesDataRef","nodesById","neighborMapRef","nodeEdgesRef","labelsOverlayRef","nodeChipsRef","edgeChipsRef","selectedIdRef","isCameraAnimatingRef","useEffect","blurRestoreTimer","degree","neighborMap","nodeEdges","e","i","edgeId","maxDeg","themeTokens","nodeColors","visNodes","deg","visEdges","nodesData","DataSet","edgesData","isLargeGraph","options","Network","drawMinimap","canvas","ctx","positions","xs","p","ys","minX","maxX","minY","maxY","w","h","spanX","spanY","toCanvas","x","y","pos","cx","cy","scale","viewPos","canvasEl","viewW","viewH","rx1","ry1","rx2","ry2","rectsOverlap","a","b","syncAllChipPositions","declutter","reserved","MAX_NUDGES","box","domPos","baseLeft","baseTop","left","top","row","col","r","fromChain","toChain","bothVisible","from","to","rect","lastChipSync","syncAllChipPositionsThrottled","now","willRunClusterSeparationBurst","communityIds","c","params","nodeId","prevKeptNodeIds","prevKeptEdgeIds","applyKeep","keepNodes","keepEdges","nodeUpdates","edgeUpdates","hoveredId","neighbors","lastMinimapDraw","throttledDrawMinimap","prevFitTriggerRef","minimapToGraph","evt","handleMinimapClick","minimapDragging","handleMinimapMouseDown","move","_a","up","jsxs","jsx","registerNetworkGraphImpl","markVisReady"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"vis-setup.js","sources":["../src/lib/components/display/NetworkGraphView.vis.tsx","../src/vis-setup.ts"],"sourcesContent":["import { useEffect, useRef } from 'react';\nimport { DataSet } from 'vis-data/peer';\nimport { Network } from 'vis-network/peer';\nimport type { Edge as VisEdge, Node as VisNode, Options } from 'vis-network/peer';\nimport type { NetworkGraphViewProps, NetworkGraphNode } from './NetworkGraphView';\n\nconst DEFAULT_PALETTE = [\n '#4E79A7', '#F28E2B', '#E15759', '#76B7B2', '#59A14F',\n '#EDC948', '#B07AA1', '#FF9DA7', '#9C755F', '#BAB0AC',\n];\n\nconst DIMMED_OPACITY = 0.12;\n// Node/edge shadows and continuous-curve edge smoothing look nice but are\n// recomputed by vis-network's own canvas draw loop on every single frame —\n// above this size that cost (not anything in our own event handlers) is\n// what makes zooming/panning feel sluggish on a large graph.\nconst LARGE_GRAPH_NODE_THRESHOLD = 300;\n// Below this zoom the leaf chip labels collide into noise — hide them\n// wholesale and restore on zoom-in. Collapsed community/cluster nodes carry\n// their own always-legible member-count badge natively (see\n// clusterCommunity) instead of a DOM chip, so they're unaffected either way.\nconst LABEL_ZOOM_THRESHOLD = 0.45;\n// Label chips are RECYCLED, never created per graph element. The overlay\n// holds a small fixed pool of chip elements; each frame the pool is handed\n// to whichever nodes/edges are currently on screen and worth labelling.\n//\n// This is the whole performance story. Creating one DOM element per node\n// and per edge is what made a 3,000-element graph unusable: every chip had\n// to be re-positioned against the camera on each redraw, so the cost of a\n// pan or zoom scaled with the size of the entire graph rather than with\n// what you could actually see. Pooling caps that work at \"one screenful\"\n// forever — the graph can hold a million nodes and a frame still costs the\n// same. It also keeps the nice look: real DOM chips can be rounded, tinted\n// and padded like dui's ChipView, and stay a constant on-screen size at any\n// zoom, neither of which vis-network's canvas `font.background` can do (it\n// is a hard-edged rectangle that balloons as you zoom in).\n//\n// Budgets are per-kind and deliberately generous — they only bite when the\n// screen is genuinely crowded, at which point the most connected nodes win\n// (they are the useful landmarks; the rest would be an unreadable smear).\nconst NODE_LABEL_BUDGET = 220;\nconst EDGE_LABEL_BUDGET = 120;\n// Screen-space bucket size for the chip collision grid (see makeChipGrid).\n// Roughly one chip-width — big enough that a chip spans only 2-3 cells,\n// small enough that a cell holds very few chips.\nconst CHIP_GRID_CELL = 96;\n\n// Canvas can't resolve CSS custom properties (`var(--color-text-primary)`\n// etc. only resolve against a real DOM element's computed style), so the\n// resolved theme name is passed in as a prop and mapped to the same hex\n// values dui's own [data-theme] CSS blocks use — keeps the minimap and\n// edge-label chips visually consistent with the rest of the app in both\n// themes instead of always-white/black.\nconst THEME_TOKENS = {\n dark: {\n text: '#d4d4d4', chipBg: 'rgba(37, 37, 38, 0.85)', chipBorder: 'rgba(255,255,255,0.12)',\n minimapBg: 'rgba(17, 24, 39, 0.85)', minimapBorder: 'rgba(255,255,255,0.2)', viewportStroke: '#ffffff',\n },\n light: {\n text: '#1f2328', chipBg: 'rgba(255, 255, 255, 0.9)', chipBorder: 'rgba(0,0,0,0.12)',\n minimapBg: 'rgba(249, 250, 251, 0.92)', minimapBorder: 'rgba(0,0,0,0.15)', viewportStroke: '#1f2328',\n },\n} as const;\n\nfunction defaultColor(n: NetworkGraphNode): string {\n if (n.color) return n.color;\n if (n.communityId != null) return DEFAULT_PALETTE[n.communityId % DEFAULT_PALETTE.length];\n return '#6B7280';\n}\n\n/** Plain axis-aligned rect used for chip collision checks — deliberately\n * not `DOMRect` (no need for its read-only/class overhead here). */\ninterface Rect {\n left: number;\n top: number;\n right: number;\n bottom: number;\n}\n\nconst rectsOverlap = (a: Rect, b: Rect) =>\n a.left < b.right && a.right > b.left && a.top < b.bottom && a.bottom > b.top;\n\n/**\n * Screen-space uniform grid for chip collision tests.\n *\n * The obvious implementation — keep one flat array of placed rects and\n * `.some()` over it per candidate — is O(n²): every chip scans every chip\n * placed before it, and with a nudge search that inner scan runs several\n * times per chip. At a few thousand chips that is tens of millions of rect\n * tests per pass, which is precisely how a smooth graph turns into a stuck\n * one.\n *\n * Bucketing by screen cell makes it O(1) amortised instead: a candidate\n * only ever tests against rects sharing one of the 2-3 cells it covers, so\n * total work grows linearly with chip count and not at all with how\n * crowded the rest of the canvas is. Same structure Sigma.js uses for its\n * label grid.\n */\nfunction makeChipGrid() {\n const cells = new Map<number, Rect[]>();\n // Cantor-ish pairing into a single number key — cheaper than building a\n // `${cx},${cy}` string per cell per lookup in the hot path.\n const key = (cx: number, cy: number) => cx * 73856093 ^ cy * 19349663;\n const forEachCell = (r: Rect, fn: (k: number) => void) => {\n const cx0 = Math.floor(r.left / CHIP_GRID_CELL), cx1 = Math.floor(r.right / CHIP_GRID_CELL);\n const cy0 = Math.floor(r.top / CHIP_GRID_CELL), cy1 = Math.floor(r.bottom / CHIP_GRID_CELL);\n for (let cx = cx0; cx <= cx1; cx++) for (let cy = cy0; cy <= cy1; cy++) fn(key(cx, cy));\n };\n return {\n collides(r: Rect): boolean {\n let hit = false;\n forEachCell(r, k => {\n if (hit) return;\n const bucket = cells.get(k);\n if (bucket) for (let i = 0; i < bucket.length; i++) if (rectsOverlap(r, bucket[i])) { hit = true; return; }\n });\n return hit;\n },\n insert(r: Rect): void {\n forEachCell(r, k => {\n const bucket = cells.get(k);\n if (bucket) bucket.push(r); else cells.set(k, [r]);\n });\n },\n };\n}\n\ninterface ChipStyle {\n color: string;\n bg: string;\n border: string;\n fontSize: number;\n fontWeight: number;\n padding: string;\n}\n\n/** Creates one empty pooled chip element. Only the properties that never\n * change for the life of the chip are set here — text and palette are\n * assigned per frame by applyChip(), since a pooled chip is reused by\n * whichever node/edge currently needs it. */\nfunction createChipElement(overlay: HTMLDivElement | null): HTMLDivElement {\n const el = document.createElement('div');\n el.style.position = 'absolute';\n el.style.borderRadius = '9999px';\n el.style.letterSpacing = '0.01em';\n el.style.whiteSpace = 'nowrap';\n el.style.pointerEvents = 'none';\n el.style.display = 'none';\n overlay?.appendChild(el);\n return el;\n}\n\n/** Points a pooled chip at new content. Every write is guarded by a\n * comparison: during a pan the same chips usually keep the same text, and\n * assigning an identical string to `textContent` would still dirty layout. */\nfunction applyChip(el: HTMLDivElement, text: string, s: ChipStyle): void {\n if (el.textContent !== text) el.textContent = text;\n if (el.style.padding !== s.padding) el.style.padding = s.padding;\n const fontSize = `${s.fontSize}px`;\n if (el.style.fontSize !== fontSize) el.style.fontSize = fontSize;\n const fontWeight = String(s.fontWeight);\n if (el.style.fontWeight !== fontWeight) el.style.fontWeight = fontWeight;\n if (el.style.background !== s.bg) el.style.background = s.bg;\n const border = `1px solid ${s.border}`;\n if (el.style.border !== border) el.style.border = border;\n if (el.style.color !== s.color) el.style.color = s.color;\n}\n\nfunction nodeChipStyle(color: string): ChipStyle {\n return {\n color,\n bg: `color-mix(in srgb, ${color} 16%, transparent)`,\n border: `color-mix(in srgb, ${color} 40%, transparent)`,\n fontSize: 12,\n fontWeight: 600,\n padding: '3px 9px',\n };\n}\n\nfunction edgeChipStyle(tokens: typeof THEME_TOKENS[keyof typeof THEME_TOKENS]): ChipStyle {\n // Neutral, not colored by an endpoint — relationship labels (\"contains\",\n // \"calls\") are secondary to node identity, so they shouldn't visually\n // compete with (or be mistaken for) a node's own colorful chip.\n return {\n color: tokens.text,\n bg: tokens.chipBg,\n border: tokens.chipBorder,\n fontSize: 10,\n fontWeight: 500,\n padding: '2px 7px',\n };\n}\n\n/**\n * Cluster every node sharing communityId `cid` into one collapsed node.\n * No-op if <2 members.\n *\n * Design note (was: a floating DOM \"Community N (count)\" pill chip above\n * every cluster — see git history): with dozens of same-sized communities\n * on a 1000+ node graph, those pills piled into an unreadable stack the\n * instant several sat near each other on screen (no amount of collision\n * nudging fixes running out of room). Matches how graphify/most graph\n * tools handle this instead: the canvas only ever shows geometry (a\n * colored, count-sized circle) plus a compact member-count badge baked\n * into the node's own vis-network label; the full \"Community N (count)\"\n * name lives in the hover tooltip (`title`) and the always-available\n * CommunityLegendPanel sidebar list (ns9-ui), never as an always-on\n * floating label competing for space with its neighbors.\n */\nfunction clusterCommunity(network: Network, cid: number, allNodes: NetworkGraphNode[]): void {\n const memberIds = new Set(allNodes.filter(n => n.communityId === cid).map(n => n.id));\n if (memberIds.size < 2) return;\n // Already clustered (or partially) — skip, avoids vis-network's \"cluster already exists\" throw.\n for (const id of memberIds) {\n if (!network.findNode(id).length) return;\n if (network.isCluster(id)) return;\n }\n const baseColor = DEFAULT_PALETTE[cid % DEFAULT_PALETTE.length];\n const clusterId = `cluster:community:${cid}`;\n const label = `Community ${cid} (${memberIds.size})`;\n const size = Math.min(20 + memberIds.size * 2, 60);\n // vis-network's 'dot' shape always draws its label BELOW the node (an\n // \"external label\"), never inside — there's no built-in \"labelled circle\n // sized independently of its text\" mode. `vadjust` (a font option, added\n // to that external position) is the documented way to relocate it;\n // shifting up by ~(radius + half the badge's own text height) lands the\n // count roughly centered inside the circle instead of floating under it.\n const badgeFontSize = Math.max(11, Math.min(16, Math.round(size * 0.32)));\n const vadjust = -(size + badgeFontSize * 0.5);\n network.cluster({\n joinCondition: (nodeOptions) => memberIds.has(nodeOptions.id as string),\n // vis-network genuinely accepts `id` in clusterNodeProperties at runtime\n // (it's how you address the cluster later via openCluster/isCluster) —\n // the shipped .d.ts's NodeOptions type just doesn't declare it.\n clusterNodeProperties: {\n id: clusterId,\n // Member count only — the full name is tooltip + legend-panel only\n // (see the doc comment above). Not empty string: vis-network defaults\n // `label` to the literal \"cluster\" when it's `undefined`, which would\n // otherwise draw a stray caption under every collapsed community.\n label: String(memberIds.size),\n font: { color: '#ffffff', size: badgeFontSize, vadjust },\n title: label,\n shape: 'dot',\n // Larger + white-ringed so a collapsed community reads as a distinct\n // \"group\" at a glance, not just another same-sized leaf node.\n size,\n borderWidth: 3,\n color: { background: baseColor, border: '#ffffff', highlight: { background: baseColor, border: '#ffffff' } },\n // Soft colored glow (a hint of the community's own color) instead of a\n // flat plain circle — same \"elevated card\" depth language dui's own\n // panels use, just expressed on canvas.\n shadow: { enabled: true, color: `${baseColor}66`, size: 16, x: 0, y: 4 },\n } as VisNode & { id: string },\n });\n}\n\nexport function NetworkGraphViewImpl({\n nodes, edges, onNodeClick, selectedId, fitTrigger, onReady, colorBy, sizeBy, className = '', style,\n enableClustering = false, enableHoverDim = false, enableMinimap = false, theme = 'dark',\n}: NetworkGraphViewProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const minimapCanvasRef = useRef<HTMLCanvasElement>(null);\n const networkRef = useRef<Network | null>(null);\n const nodesDataRef = useRef<DataSet<VisNode> | null>(null);\n const edgesDataRef = useRef<DataSet<VisEdge> | null>(null);\n const nodesById = useRef<Map<string, NetworkGraphNode>>(new Map());\n const neighborMapRef = useRef<Map<string, Set<string>>>(new Map());\n // node id -> ids of every edge touching it. Lets hoverNode/blurNode look\n // up \"which edges does this node touch\" in O(degree) instead of scanning\n // every edge in the graph on every hover — see the hoverNode handler.\n const nodeEdgesRef = useRef<Map<string, Set<string | number>>>(new Map());\n // Every label (node, collapsed-community, edge) renders as a real DOM chip\n // styled like dui's own ChipView (rounded pill, color-mix background/\n // border, colored text) instead of vis-network's canvas-only\n // `font.background`, which is a plain rectangle with no border-radius, no\n // real padding, and — short of computing per-node canvas fill colors by\n // hand — no way to tint it from outside the library. DOM chips also fix\n // the \"huge overlapping text at high zoom\" complaint for free: unlike\n // vis's canvas labels (which scale with zoom, so they can balloon and\n // collide at high zoom), these stay a fixed on-screen size regardless of\n // zoom level.\n const labelsOverlayRef = useRef<HTMLDivElement>(null);\n // Recycled pools, grown lazily up to their budget — see the budget\n // constants. Index in the array is the only identity a chip has; which\n // node or edge it represents changes from frame to frame.\n const nodeChipPoolRef = useRef<HTMLDivElement[]>([]);\n const edgeChipPoolRef = useRef<HTMLDivElement[]>([]);\n // Chip dimensions cached per (kind, text) — the size of a chip depends\n // only on its string and its kind's fixed font/padding, so the same label\n // is measured exactly once for the life of the view no matter how many\n // times it is assigned to a pooled element.\n //\n // This cache is what keeps layout thrashing out of the hot path. Reading\n // `offsetWidth` right after writing `style.left/top` forces a synchronous\n // layout recalculation, and doing that per chip per redraw was the single\n // most expensive thing in this component.\n const chipTextSizeRef = useRef<Map<string, { w: number; h: number }>>(new Map());\n // Kept current every render (not via its own effect — just needs to be\n // readable-without-a-stale-closure from inside the settle callbacks\n // below, which are registered once per network instance via `.once()`).\n const selectedIdRef = useRef<string | null | undefined>(selectedId);\n selectedIdRef.current = selectedId;\n // Set while a network.focus()/moveTo() camera animation is in flight (see\n // the selectedId effect + the 'animationFinished' listener below). On a\n // graph with hundreds/thousands of chips, syncAllChipPositions and the\n // minimap redraw were still firing at their throttled ~10/sec cadence\n // DURING every one of vis-network's 1s click-to-focus animations — each\n // pass does a getBoundingBox/canvasToDOM + forced-reflow offsetWidth/\n // offsetHeight read PER CHIP, which is the actual \"slow motion\" jank on\n // large graphs (vis-network's own canvas redraw is comparatively cheap).\n // Skipping chip/minimap sync entirely while the camera is moving — and\n // doing exactly one full sync when it lands — removes ~10 of those\n // passes per click with no visible cost (chips settle the instant the\n // animation ends, same as they did after every throttled tick before).\n const isCameraAnimatingRef = useRef(false);\n\n useEffect(() => {\n if (!containerRef.current) return;\n\n // Declared here (not inside the enableHoverDim block below that uses\n // it) so this effect's own cleanup can clear a still-pending timer on\n // unmount — otherwise a debounced blur restore could fire after\n // network.destroy() and touch a torn-down dataset.\n let blurRestoreTimer: ReturnType<typeof setTimeout> | null = null;\n\n nodesById.current = new Map(nodes.map(n => [n.id, n]));\n\n const degree: Record<string, number> = {};\n const neighborMap = new Map<string, Set<string>>();\n const nodeEdges = new Map<string, Set<string | number>>();\n edges.forEach((e, i) => {\n degree[e.source] = (degree[e.source] ?? 0) + 1;\n degree[e.target] = (degree[e.target] ?? 0) + 1;\n if (!neighborMap.has(e.source)) neighborMap.set(e.source, new Set());\n if (!neighborMap.has(e.target)) neighborMap.set(e.target, new Set());\n neighborMap.get(e.source)!.add(e.target);\n neighborMap.get(e.target)!.add(e.source);\n // Same id derivation as visEdges below (e.id ?? i) — same array, same\n // iteration order, so the ids line up.\n const edgeId = e.id ?? i;\n if (!nodeEdges.has(e.source)) nodeEdges.set(e.source, new Set());\n if (!nodeEdges.has(e.target)) nodeEdges.set(e.target, new Set());\n nodeEdges.get(e.source)!.add(edgeId);\n nodeEdges.get(e.target)!.add(edgeId);\n });\n neighborMapRef.current = neighborMap;\n nodeEdgesRef.current = nodeEdges;\n const maxDeg = Math.max(1, ...Object.values(degree));\n\n const themeTokens = THEME_TOKENS[theme];\n const nodeColors = new Map<string, string>();\n\n const visNodes: VisNode[] = nodes.map(n => {\n const color = colorBy ? colorBy(n) : defaultColor(n);\n nodeColors.set(n.id, color);\n const deg = degree[n.id] ?? 1;\n const size = sizeBy ? sizeBy(n, deg, maxDeg) : 10 + 30 * (deg / maxDeg);\n return {\n id: n.id,\n title: n.label,\n // No `label` here on purpose: every label is a pooled DOM chip\n // positioned by syncAllChipPositions. Collapsed community nodes are\n // the one exception and set their own label natively — see\n // clusterCommunity.\n // Selection keeps the node's own fill and flags it with a themed\n // border instead — the old highlight swapped the fill to the theme\n // text color, which read as a jarring near-black circle in light\n // mode (and near-white in dark).\n color: { background: color, border: color, highlight: { background: color, border: themeTokens.text } },\n size: Math.round(size * 10) / 10,\n shape: 'dot',\n };\n });\n\n const visEdges: VisEdge[] = edges.map((e, i) => ({\n id: e.id ?? i,\n from: e.source,\n to: e.target,\n title: e.type,\n // Blends from the source node's color to the target's at the\n // midpoint instead of a flat gray line — a vis-network-native\n // stand-in for the gradient edges ck8t's ReactFlow canvas uses.\n color: { inherit: 'both', opacity: 0.55 },\n arrows: { to: { enabled: true, scaleFactor: 0.6 } },\n }));\n\n const nodesData = new DataSet(visNodes);\n const edgesData = new DataSet(visEdges);\n nodesDataRef.current = nodesData;\n edgesDataRef.current = edgesData;\n\n // Above LARGE_GRAPH_NODE_THRESHOLD, shadows + continuous-curve edges are\n // disabled — see the constant's comment. Small graphs keep the nicer look.\n const isLargeGraph = nodes.length > LARGE_GRAPH_NODE_THRESHOLD;\n\n const options: Options = {\n // vis-network defaults `layout.improvedLayout` to true, which runs a\n // Kamada-Kawai pre-positioning pass whenever the graph exceeds its\n // internal clusterThreshold (150 nodes). On graphs with many\n // uneven-sized communities that pass regularly fails to reduce the\n // graph within its own iteration budget: it logs \"This network could\n // not be positioned by this version of the improved layout\n // algorithm\" and falls back to leaving most nodes near their default\n // circular starting positions with a ±35px jitter — the stray ring of\n // dots around the outside that physics then can't pull in within its\n // stabilization budget. Skipping the pre-pass (vis's own documented\n // suggestion, printed in that same message) lets forceAtlas2Based\n // position everything from a clean random start instead.\n layout: { improvedLayout: false },\n physics: {\n enabled: true,\n solver: 'forceAtlas2Based',\n forceAtlas2Based: {\n gravitationalConstant: -60,\n centralGravity: 0.005,\n springLength: 120,\n springConstant: 0.08,\n damping: 0.4,\n avoidOverlap: 0.8,\n },\n stabilization: { iterations: 200, fit: true },\n },\n // hideEdgesOnZoom mirrors hideEdgesOnDrag below — edges (continuous\n // smooth curves + shadows) are by far the most expensive thing on\n // canvas to redraw every frame; hiding them for the duration of a\n // zoom gesture (mouse wheel or pinch) is what actually fixes \"zoom\n // feels like a turtle\" on a large graph, vs. anything in our own\n // event handlers (which only ever ran custom code on TOP of vis's\n // own native per-frame draw loop, never touched by throttling it).\n interaction: {\n hover: true, tooltipDelay: 100,\n hideEdgesOnDrag: true, hideEdgesOnZoom: true,\n navigationButtons: false,\n },\n // Soft drop shadow on every node — same elevated-card depth cue dui's\n // own panels use, expressed via canvas shadow instead of CSS box-shadow.\n // Shadows aren't covered by hideEdgesOnZoom (nodes stay visible while\n // zooming) and shadowBlur is one of canvas's most expensive per-shape\n // operations — skip them above LARGE_GRAPH_NODE_THRESHOLD.\n nodes: {\n shape: 'dot', borderWidth: 1.5, borderWidthSelected: 3,\n shadow: { enabled: !isLargeGraph, color: 'rgba(0,0,0,0.35)', size: 8, x: 0, y: 3 },\n },\n edges: {\n // 'continuous' recomputes bezier control points per edge per draw —\n // the most expensive of vis-network's smooth types. Straight lines\n // above the threshold; edges are hidden during zoom either way.\n smooth: isLargeGraph ? false : { enabled: true, type: 'continuous', roundness: 0.25 },\n shadow: { enabled: !isLargeGraph, color: 'rgba(0,0,0,0.12)', size: 3, x: 0, y: 1 },\n },\n };\n\n const network = new Network(containerRef.current, { nodes: nodesData, edges: edgesData }, options);\n networkRef.current = network;\n\n const drawMinimap = () => {\n const canvas = minimapCanvasRef.current;\n if (!canvas || !enableMinimap) return;\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n const positions = network.getPositions();\n const xs = Object.values(positions).map(p => p.x);\n const ys = Object.values(positions).map(p => p.y);\n if (xs.length === 0) return;\n const minX = Math.min(...xs), maxX = Math.max(...xs);\n const minY = Math.min(...ys), maxY = Math.max(...ys);\n const w = canvas.width, h = canvas.height;\n const spanX = Math.max(1, maxX - minX), spanY = Math.max(1, maxY - minY);\n const toCanvas = (x: number, y: number): [number, number] => [\n ((x - minX) / spanX) * (w - 10) + 5,\n ((y - minY) / spanY) * (h - 10) + 5,\n ];\n\n ctx.clearRect(0, 0, w, h);\n ctx.fillStyle = themeTokens.minimapBg;\n ctx.fillRect(0, 0, w, h);\n\n Object.entries(positions).forEach(([id, pos]) => {\n const [cx, cy] = toCanvas(pos.x, pos.y);\n const n = nodesById.current.get(id);\n ctx.fillStyle = n ? (colorBy ? colorBy(n) : defaultColor(n)) : '#6B7280';\n ctx.beginPath();\n // Degree-scaled dots so hubs read as landmarks when orienting.\n const deg = degree[id] ?? 0;\n ctx.arc(cx, cy, 1 + 1.5 * (deg / maxDeg), 0, Math.PI * 2);\n ctx.fill();\n });\n\n // Viewport rectangle\n const scale = network.getScale();\n const viewPos = network.getViewPosition();\n const canvasEl = containerRef.current;\n if (canvasEl && scale > 0) {\n const viewW = canvasEl.clientWidth / scale;\n const viewH = canvasEl.clientHeight / scale;\n const [rx1, ry1] = toCanvas(viewPos.x - viewW / 2, viewPos.y - viewH / 2);\n const [rx2, ry2] = toCanvas(viewPos.x + viewW / 2, viewPos.y + viewH / 2);\n ctx.strokeStyle = themeTokens.viewportStroke;\n ctx.lineWidth = 1;\n ctx.strokeRect(rx1, ry1, rx2 - rx1, ry2 - ry1);\n }\n };\n\n // ── Label chips (nodes, edges — NOT clusters, see clusterCommunity) ────\n // Nothing is created up front. Pools grow on demand, capped by the\n // budgets, and are handed out per frame by syncAllChipPositions.\n\n // Node positions live in canvas space, so panning and zooming don't\n // change them at all — only dragging a node or (de)clustering does.\n // Cached so the per-frame path never pays for a full getPositions().\n let canvasPositions: { [id: string]: { x: number; y: number } } | null = null;\n const invalidatePositions = () => { canvasPositions = null; };\n\n // Hover-dim state. A pooled chip has no fixed owner, so \"dim everything\n // that isn't a neighbour of the hovered node\" can't be applied to chips\n // by id up front — instead the hover handler records what should stay\n // lit and the sync applies it to whichever chip currently shows that\n // node. `null` means nothing is hovered and everything is lit.\n let dimKeepNodes: Set<string> | null = null;\n let dimKeepEdges: Set<string | number> | null = null;\n\n const edgeTypeById = new Map<string | number, string>();\n edges.forEach((e, i) => { if (e.type) edgeTypeById.set(e.id ?? i, e.type); });\n\n /** Pooled chip at `index`, created on first use. */\n const chipAt = (pool: HTMLDivElement[], index: number): HTMLDivElement => {\n let el = pool[index];\n if (!el) { el = createChipElement(labelsOverlayRef.current); pool[index] = el; }\n return el;\n };\n\n /** Size of `text` rendered as `kind`, measured at most once ever. The\n * single `offsetWidth` read here happens only on a cache miss, i.e. the\n * first time a given label is ever shown — never on a steady-state pan. */\n const chipSize = (el: HTMLDivElement, kind: 'n' | 'e', text: string) => {\n const key = `${kind}\u0000${text}`;\n let size = chipTextSizeRef.current.get(key);\n if (!size) {\n size = { w: el.offsetWidth, h: el.offsetHeight };\n chipTextSizeRef.current.set(key, size);\n }\n return size;\n };\n\n const hidePoolFrom = (pool: HTMLDivElement[], from: number) => {\n for (let i = from; i < pool.length; i++) {\n if (pool[i].style.display !== 'none') pool[i].style.display = 'none';\n }\n };\n\n /**\n * Positions every (non-cluster — see clusterCommunity) chip and decides\n * what's visible this frame:\n * - Node chips hide when their node is absorbed into a collapsed\n * cluster, or when fully zoomed out (declutter threshold).\n * - A visible node chip is never hidden for overlapping another —\n * instead it's nudged in a small grid search (a handful of pixels at\n * a time, capped) until it clears, the same \"stack the overlapping\n * pins\" trick map UIs use for crowded marker labels.\n * - Edge chips hide when either endpoint isn't currently visible\n * (absorbed into a cluster — the underlying edge itself isn't drawn\n * either), when fully zoomed out, OR when they'd visually overlap a\n * node chip (node identity wins over relationship labels).\n */\n const syncAllChipPositions = () => {\n const nodePool = nodeChipPoolRef.current;\n const edgePool = edgeChipPoolRef.current;\n const viewW = containerRef.current?.clientWidth ?? 0;\n const viewH = containerRef.current?.clientHeight ?? 0;\n\n // Fully zoomed out: chips would collide into noise. Collapsed\n // community nodes keep their own canvas-drawn member-count badge, so\n // nothing important disappears here.\n if (network.getScale() < LABEL_ZOOM_THRESHOLD || !viewW || !viewH) {\n hidePoolFrom(nodePool, 0);\n hidePoolFrom(edgePool, 0);\n return;\n }\n\n if (!canvasPositions) canvasPositions = network.getPositions();\n const tl = network.DOMtoCanvas({ x: 0, y: 0 });\n const br = network.DOMtoCanvas({ x: viewW, y: viewH });\n\n // Everything on screen is a candidate. Only if the screen is crowded\n // past the budget do we rank — and then by degree, so the landmarks\n // survive and the anonymous leaves are the ones dropped. Zooming in\n // shrinks the candidate set, which is what makes zoom the natural\n // \"reveal more detail\" gesture rather than a fixed global cutoff.\n const candidates: { id: string; deg: number }[] = [];\n for (const id in canvasPositions) {\n const p = canvasPositions[id];\n if (p.x < tl.x || p.x > br.x || p.y < tl.y || p.y > br.y) continue;\n // Cluster super-nodes carry their own badge — never chip them.\n if (!nodesById.current.has(id)) continue;\n candidates.push({ id, deg: degree[id] ?? 0 });\n }\n if (candidates.length > NODE_LABEL_BUDGET) {\n candidates.sort((a, b) => b.deg - a.deg);\n candidates.length = NODE_LABEL_BUDGET;\n }\n\n const grid = makeChipGrid();\n const MAX_NUDGES = 12; // 4 rows × 3 columns (center/left/right per row)\n let used = 0;\n\n for (const cand of candidates) {\n const node = nodesById.current.get(cand.id);\n if (!node?.label) continue;\n // Absorbed into a collapsed community since positions were cached.\n if (network.findNode(cand.id).length !== 1) continue;\n\n const box = network.getBoundingBox(cand.id);\n if (!box) continue;\n const domPos = network.canvasToDOM({ x: (box.left + box.right) / 2, y: box.bottom });\n\n const el = chipAt(nodePool, used);\n applyChip(el, node.label, nodeChipStyle(nodeColors.get(cand.id) ?? '#6B7280'));\n if (el.style.display !== '') el.style.display = '';\n const opacity = !dimKeepNodes || dimKeepNodes.has(cand.id) ? '1' : String(DIMMED_OPACITY);\n if (el.style.opacity !== opacity) el.style.opacity = opacity;\n const { w, h } = chipSize(el, 'n', node.label);\n\n // Grid search, not a straight-down stack: two overlapping chips are\n // just as often side-by-side (adjacent communities at similar\n // height) as stacked, and a pure vertical nudge never resolves a\n // horizontal collision. Each attempt tries center/left/right at a\n // given row before dropping to the next row down.\n const baseLeft = domPos.x - w / 2;\n const baseTop = domPos.y + 6;\n let left = baseLeft, top = baseTop;\n for (let n = 0; n < MAX_NUDGES; n++) {\n const row = Math.floor(n / 3);\n const col = (n % 3) - 1; // -1, 0, 1\n left = baseLeft + col * (w + 6);\n top = baseTop + row * (h + 3);\n if (!grid.collides({ left, top, right: left + w, bottom: top + h })) break;\n }\n el.style.left = `${left}px`;\n el.style.top = `${top}px`;\n grid.insert({ left, top, right: left + w, bottom: top + h });\n used++;\n }\n hidePoolFrom(nodePool, used);\n\n // Edge chips: same budget treatment, and they always lose a collision\n // against a node chip — relationship labels are secondary to identity.\n let edgeUsed = 0;\n for (const [edgeId, type] of edgeTypeById) {\n if (edgeUsed >= EDGE_LABEL_BUDGET) break;\n const e = edgesData.get(edgeId) as VisEdge | null;\n if (!e) continue;\n const from = canvasPositions[e.from as string];\n const to = canvasPositions[e.to as string];\n if (!from || !to) continue;\n const mid = { x: (from.x + to.x) / 2, y: (from.y + to.y) / 2 };\n if (mid.x < tl.x || mid.x > br.x || mid.y < tl.y || mid.y > br.y) continue;\n if (network.findNode(e.from as string).length !== 1) continue;\n if (network.findNode(e.to as string).length !== 1) continue;\n\n const domPos = network.canvasToDOM(mid);\n const el = chipAt(edgePool, edgeUsed);\n applyChip(el, type, edgeChipStyle(themeTokens));\n if (el.style.display !== '') el.style.display = '';\n const eOpacity = !dimKeepEdges || dimKeepEdges.has(edgeId) ? '1' : String(DIMMED_OPACITY);\n if (el.style.opacity !== eOpacity) el.style.opacity = eOpacity;\n const { w, h } = chipSize(el, 'e', type);\n const rect: Rect = { left: domPos.x - w / 2, top: domPos.y - h / 2, right: domPos.x + w / 2, bottom: domPos.y + h / 2 };\n if (grid.collides(rect)) { el.style.display = 'none'; continue; }\n el.style.left = `${rect.left}px`;\n el.style.top = `${rect.top}px`;\n grid.insert(rect);\n edgeUsed++;\n }\n hidePoolFrom(edgePool, edgeUsed);\n };\n\n const syncOverlays = syncAllChipPositions;\n\n // Coalesce to at most one sync per animation frame, and run it aligned\n // with paint rather than on a wall-clock timer. A fixed 100ms throttle\n // is both too slow (chips visibly lag the canvas mid-drag) and too fast\n // (a pass that takes longer than the interval just queues the next one\n // immediately, so the main thread never gets a gap). rAF self-limits:\n // if a pass is slow, frames drop and the sync rate drops with them\n // instead of piling up.\n let syncRafHandle: number | null = null;\n const syncOverlaysThrottled = () => {\n // A network.focus()/moveTo() camera animation is running — overlays\n // get one full, non-throttled sync the instant it lands (see the\n // 'animationFinished' listener below). Skipping them mid-flight is\n // what actually fixes the large-graph \"slow motion\" click-to-zoom.\n if (isCameraAnimatingRef.current) return;\n if (syncRafHandle !== null) return;\n syncRafHandle = requestAnimationFrame(() => {\n syncRafHandle = null;\n syncOverlays();\n });\n };\n\n network.once('stabilizationIterationsDone', () => {\n network.setOptions({ physics: { enabled: false } });\n let willRunClusterSeparationBurst = false;\n if (enableClustering) {\n const communityIds = new Set(nodes.map(n => n.communityId).filter((c): c is number => c != null));\n communityIds.forEach(cid => clusterCommunity(network, cid, nodes));\n // Collapsed community super-nodes inherit their members' centroid\n // position and can land overlapping each other. One short physics\n // burst separates them, then\n // physics goes back off so the layout stays stable.\n if (communityIds.size > 1) {\n willRunClusterSeparationBurst = true;\n network.once('stabilizationIterationsDone', () => {\n network.setOptions({ physics: { enabled: false } });\n drawMinimap();\n // Physics + clustering just moved everything — the cached\n // canvas positions are stale.\n invalidatePositions();\n syncOverlays();\n // A selectedId set before this rebuild's stabilization finished\n // (e.g. a filter change that just made the selected node visible\n // again) needs to be (re-)applied here — the standalone\n // selectedId effect below fires as soon as the network is\n // (re)created, well before physics has settled, so its focus()\n // call is invisibly overwritten by physics ticks still in\n // progress. This is the true final settle point; re-focus wins.\n if (selectedIdRef.current && network.findNode(selectedIdRef.current).length > 0) {\n network.selectNodes([selectedIdRef.current]);\n network.focus(selectedIdRef.current, { scale: 1.4, animation: true });\n isCameraAnimatingRef.current = true;\n }\n // Genuinely ready only now — calling onReady from the outer\n // callback too would reveal the canvas mid-cluster-separation\n // (nodes still visibly reflowing), the exact ugly transient\n // state onReady exists to hide.\n onReady?.();\n });\n network.setOptions({\n physics: { enabled: true, stabilization: { iterations: 80, fit: false } },\n });\n network.stabilize(80);\n }\n }\n // Slight zoom-out so labels/nodes at the layout's bounding box aren't\n // flush against (and clipped by) the viewport edges.\n const scale = network.getScale();\n if (scale > 0) network.moveTo({ scale: scale * 0.92 });\n drawMinimap();\n // Stabilization (and any clustering above) just moved every node.\n invalidatePositions();\n syncOverlays();\n if (!willRunClusterSeparationBurst) {\n // See the matching comment in the nested cluster-separation settle\n // callback above — re-apply a pre-set selectedId here since this is\n // the true final settle point when no cluster-separation burst is\n // coming.\n if (selectedIdRef.current && network.findNode(selectedIdRef.current).length > 0) {\n network.selectNodes([selectedIdRef.current]);\n network.focus(selectedIdRef.current, { scale: 1.4, animation: true });\n isCameraAnimatingRef.current = true;\n }\n onReady?.();\n }\n });\n\n network.on('afterDrawing', syncOverlaysThrottled);\n network.on('dragEnd', () => { invalidatePositions(); syncOverlays(); });\n\n // vis-network's own animation-driven redraws (focus()/moveTo() with\n // animation: true) are what 'animationFinished' marks the end of — see\n // isCameraAnimatingRef's declaration above for why chip/minimap sync is\n // skipped while one is in flight.\n network.on('animationFinished', () => {\n isCameraAnimatingRef.current = false;\n syncOverlays();\n if (enableMinimap) drawMinimap();\n });\n\n network.on('click', (params) => {\n if (params.nodes.length === 0) return;\n const nodeId = params.nodes[0];\n const n = nodesById.current.get(nodeId);\n if (n) {\n onNodeClick?.(n);\n return;\n }\n // Clicked node has no backing NetworkGraphNode — this is a collapsed\n // cluster's synthetic id (e.g. \"cluster:community:5\"), not a real node.\n // Still fire the callback with a lightweight synthetic node so the\n // caller's click handling (e.g. a side-panel summary) actually runs\n // instead of silently doing nothing, which is what a plain lookup\n // miss used to produce.\n if (network.isCluster(nodeId)) {\n onNodeClick?.({ id: nodeId, label: nodeId });\n }\n });\n\n if (enableClustering) {\n network.on('doubleClick', (params) => {\n if (params.nodes.length !== 1) return;\n const nodeId = params.nodes[0];\n if (network.isCluster(nodeId)) {\n network.openCluster(nodeId);\n } else {\n const n = nodesById.current.get(nodeId);\n if (n?.communityId != null) clusterCommunity(network, n.communityId, nodes);\n }\n drawMinimap();\n // Opening/collapsing a cluster changes which nodes exist.\n invalidatePositions();\n syncOverlays();\n });\n }\n\n if (enableHoverDim) {\n // Diff against the PREVIOUS \"kept\" (opacity-1) set instead of\n // rewriting every node/edge on every hoverNode. hoverNode fires on\n // every node the mouse passes over while simply moving across the\n // canvas (not just on click) — a full nodesData.update() +\n // edgesData.update() over the WHOLE graph (1200+/1500+ items, each a\n // real vis-data change event → canvas redraw) on every single one of\n // those was the actual \"everything feels slow\" cause on a large\n // graph. nodeEdgesRef (built above, once) makes \"which edges touch\n // this node\" O(degree) instead of an O(all edges) scan too.\n //\n // A previous version of this fix (see git history if curious) had\n // hoverNode and blurNode using DIFFERENT, asymmetric logic — blurNode\n // \"restored\" prevKeptNodeIds (the already-correct, already-opacity-1\n // set) instead of the actually-dimmed complement, which is backwards:\n // it left everything ELSE stuck dimmed forever, visible as \"hovering\n // anything makes the graph disappear and it never comes back.\"\n // applyKeep() below is now the ONE diff routine both events call,\n // just with a different target \"keepNodes\" set — hoverNode passes\n // {hoveredId, neighbors}, blurNode passes \"everyone\" (the correct\n // definition of \"nothing is dimmed\", not the empty set).\n let prevKeptNodeIds = new Set<string>(nodesData.getIds() as string[]);\n let prevKeptEdgeIds = new Set<string | number>(edgesData.getIds());\n // blurNode is debounced: it fires just as often as hoverNode (every\n // node the mouse LEAVES while moving), and moving between two\n // adjacent nodes fires blur(A) then hover(B) back-to-back — doing the\n // full \"restore everyone\" work synchronously in blur would mean B's\n // hover immediately re-dims most of it again, paying the O(n) cost\n // TWICE per transition instead of once. Deferring it a beat lets a\n // following hoverNode cancel the pending restore and build its own\n // (cheap, small-diff) transition directly on top of whatever's\n // currently dimmed — the full restore only actually runs once the\n // mouse has genuinely stopped hovering anything. (blurRestoreTimer\n // itself is declared at the top of this effect, not here — so this\n // effect's cleanup can also clear it.)\n\n const applyKeep = (keepNodes: Set<string>, keepEdges: Set<string | number>) => {\n const nodeUpdates: { id: string; opacity: number }[] = [];\n for (const id of keepNodes) if (!prevKeptNodeIds.has(id)) nodeUpdates.push({ id, opacity: 1 });\n for (const id of prevKeptNodeIds) if (!keepNodes.has(id)) nodeUpdates.push({ id, opacity: DIMMED_OPACITY });\n if (nodeUpdates.length) nodesData.update(nodeUpdates);\n\n const edgeUpdates: { id: string | number; color: { inherit: 'both'; opacity: number } }[] = [];\n // `color` is replaced wholesale on update (not deep-merged), so\n // `inherit` has to be repeated here — omitting it would silently\n // drop the gradient-edge effect the instant an edge changes state.\n for (const id of keepEdges) {\n if (!prevKeptEdgeIds.has(id)) edgeUpdates.push({ id, color: { inherit: 'both', opacity: 0.9 } });\n }\n for (const id of prevKeptEdgeIds) {\n if (!keepEdges.has(id)) edgeUpdates.push({ id, color: { inherit: 'both', opacity: DIMMED_OPACITY } });\n }\n if (edgeUpdates.length) edgesData.update(edgeUpdates);\n\n // Chips are pooled, so they can't be dimmed by id here — record the\n // lit sets and let the sync apply them to whichever chip currently\n // represents each node/edge.\n dimKeepNodes = keepNodes;\n dimKeepEdges = keepEdges;\n syncOverlays();\n\n prevKeptNodeIds = keepNodes;\n prevKeptEdgeIds = keepEdges;\n };\n\n network.on('hoverNode', (params) => {\n if (blurRestoreTimer !== null) {\n clearTimeout(blurRestoreTimer);\n blurRestoreTimer = null;\n }\n const hoveredId: string = params.node;\n const neighbors = neighborMapRef.current.get(hoveredId) ?? new Set();\n // Real ids only. hoveredId can be a SYNTHETIC cluster id\n // (\"cluster:community:N\") — vis-network fires hoverNode for\n // collapsed communities too, and neighborMapRef (built from the\n // original nodes/edges, pre-clustering) never contains one. Left\n // unfiltered, pushing a cluster id into nodesData.update() silently\n // INSERTS a phantom real DataSet node sharing that same id —\n // colliding with and hijacking vis-network's own internal virtual\n // rendering of that cluster (part of the \"community circles\n // disappear\" bug — verified live: this INSERT is real, confirmed\n // by inspecting network.body.data.nodes before/after a direct\n // hoverNode emit on a cluster id).\n const keepNodes = new Set<string>(\n [hoveredId, ...neighbors].filter((id) => nodesData.get(id) != null)\n );\n const keepEdges = nodeEdgesRef.current.get(hoveredId) ?? new Set<string | number>();\n applyKeep(keepNodes, keepEdges);\n });\n\n network.on('blurNode', () => {\n blurRestoreTimer = setTimeout(() => {\n blurRestoreTimer = null;\n applyKeep(new Set(nodesData.getIds() as string[]), new Set(edgesData.getIds()));\n }, 80);\n });\n }\n\n if (enableMinimap) {\n network.on('dragEnd', drawMinimap);\n // 'zoom' fires on every scale-level change — completely unthrottled —\n // and mouse-wheel zooming fires a burst of these per tick, each one\n // previously doing a full getPositions() + one arc per node redraw.\n // On a 1,242-node graph that's the \"turtle\" scroll-to-zoom lag: shares\n // the same ~10/sec throttle budget as afterDrawing below so a fast\n // scroll doesn't queue up dozens of full minimap repaints.\n let lastMinimapDraw = 0;\n const throttledDrawMinimap = () => {\n if (isCameraAnimatingRef.current) return;\n const now = Date.now();\n if (now - lastMinimapDraw < 100) return;\n lastMinimapDraw = now;\n drawMinimap();\n };\n network.on('zoom', throttledDrawMinimap);\n // afterDrawing fires on EVERY canvas render — during pane resizes or\n // physics ticks that meant a full minimap repaint (getPositions + one\n // arc per node) per frame, a visible drag-lag contributor. Gate it to\n // ~10 repaints/second; dragEnd above still repaints instantly.\n network.on('afterDrawing', throttledDrawMinimap);\n }\n\n return () => {\n if (blurRestoreTimer !== null) clearTimeout(blurRestoreTimer);\n // A queued sync would otherwise fire after destroy() and touch a\n // torn-down network — same hazard as blurRestoreTimer above.\n if (syncRafHandle !== null) cancelAnimationFrame(syncRafHandle);\n network.destroy();\n networkRef.current = null;\n nodesDataRef.current = null;\n edgesDataRef.current = null;\n for (const el of nodeChipPoolRef.current) el.remove();\n nodeChipPoolRef.current = [];\n for (const el of edgeChipPoolRef.current) el.remove();\n edgeChipPoolRef.current = [];\n chipTextSizeRef.current.clear();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [nodes, edges, enableClustering, enableHoverDim, enableMinimap, theme]);\n\n useEffect(() => {\n const network = networkRef.current;\n if (!network || !selectedId) return;\n // `nodesById` only indexes the original (pre-cluster) node list, so a\n // collapsed cluster's synthetic id (e.g. \"cluster:community:5\") always\n // missed this check and never got the focus/zoom treatment real nodes\n // get. `network.findNode` looks up vis-network's own live node index,\n // which includes cluster super-nodes, so it correctly covers both.\n if (network.findNode(selectedId).length > 0) {\n network.selectNodes([selectedId]);\n // Set AFTER focus(), not before: vis-network forcibly finishes any\n // still-running animation synchronously inside focus() itself (and\n // synchronously emits 'animationFinished' for THAT one) before it\n // starts this new one — rapidly clicking a second node mid-animation\n // would otherwise have our own 'animationFinished' handler for the\n // interrupted animation flip the flag back to false a moment after\n // we'd set it true, immediately un-gating sync for the new animation\n // that just started.\n network.focus(selectedId, { scale: 1.4, animation: true });\n isCameraAnimatingRef.current = true;\n }\n // `nodes` is included so a previously-set selectedId gets re-applied to\n // a freshly (re)created network — e.g. a caller relaxing a node-type\n // filter that was hiding the selected node rebuilds `network` (see the\n // `[nodes, edges, ...]` effect above) without `selectedId` itself ever\n // changing, and the fresh network otherwise never learns it should\n // focus on it.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [selectedId, nodes]);\n\n // \"Reset zoom\" — a genuine zoom-to-fit-all, recomputed fresh every time\n // (the same bounding-box calculation vis-network runs when you scroll the\n // mouse wheel all the way out), not a restore of some earlier captured\n // camera snapshot — a snapshot can go stale (e.g. once a cluster has been\n // expanded, or after a click-to-focus) and \"reset\" would silently stop\n // matching what \"totally zoomed out\" actually looks like right now.\n // Skips the initial mount (fitTrigger starts at 0/undefined) — only fires\n // on a genuine increment from the host app's Reset button.\n const prevFitTriggerRef = useRef(fitTrigger);\n useEffect(() => {\n const network = networkRef.current;\n if (!network || fitTrigger === undefined || fitTrigger === prevFitTriggerRef.current) {\n prevFitTriggerRef.current = fitTrigger;\n return;\n }\n prevFitTriggerRef.current = fitTrigger;\n network.unselectAll();\n network.fit({ animation: true });\n isCameraAnimatingRef.current = true;\n }, [fitTrigger]);\n\n const minimapToGraph = (evt: { clientX: number; clientY: number }): { x: number; y: number } | null => {\n const network = networkRef.current;\n const canvas = minimapCanvasRef.current;\n if (!network || !canvas) return null;\n const positions = network.getPositions();\n const xs = Object.values(positions).map(p => p.x);\n const ys = Object.values(positions).map(p => p.y);\n if (xs.length === 0) return null;\n const minX = Math.min(...xs), maxX = Math.max(...xs);\n const minY = Math.min(...ys), maxY = Math.max(...ys);\n const rect = canvas.getBoundingClientRect();\n const cx = evt.clientX - rect.left, cy = evt.clientY - rect.top;\n const spanX = Math.max(1, maxX - minX), spanY = Math.max(1, maxY - minY);\n return {\n x: ((cx - 5) / (canvas.width - 10)) * spanX + minX,\n y: ((cy - 5) / (canvas.height - 10)) * spanY + minY,\n };\n };\n\n const handleMinimapClick = (evt: React.MouseEvent<HTMLCanvasElement>) => {\n const pos = minimapToGraph(evt);\n if (pos) networkRef.current?.moveTo({ position: pos, animation: true });\n };\n\n // Drag-the-viewport panning (UIP-3): press-and-drag on the minimap pans\n // the main canvas continuously (no animation during drag — it would lag\n // behind the pointer). Click-to-center still works via handleMinimapClick.\n const minimapDragging = useRef(false);\n const handleMinimapMouseDown = (evt: React.MouseEvent<HTMLCanvasElement>) => {\n minimapDragging.current = true;\n const move = (e: MouseEvent) => {\n if (!minimapDragging.current) return;\n const pos = minimapToGraph(e);\n if (pos) networkRef.current?.moveTo({ position: pos });\n };\n const up = () => {\n minimapDragging.current = false;\n window.removeEventListener('mousemove', move);\n window.removeEventListener('mouseup', up);\n };\n window.addEventListener('mousemove', move);\n window.addEventListener('mouseup', up);\n evt.preventDefault();\n };\n\n return (\n <div className={className} style={{ width: '100%', height: '100%', position: 'relative', ...style }}>\n <div ref={containerRef} style={{ width: '100%', height: '100%' }} />\n <div ref={labelsOverlayRef} style={{ position: 'absolute', inset: 0, overflow: 'hidden', pointerEvents: 'none' }} />\n {enableMinimap && (\n <canvas\n ref={minimapCanvasRef}\n width={140}\n height={100}\n onClick={handleMinimapClick}\n onMouseDown={handleMinimapMouseDown}\n style={{\n // bottom 72 (not 8): host apps commonly float a chat/action\n // orb in the bottom-right corner — leave that spot free.\n position: 'absolute', bottom: 72, right: 8,\n borderRadius: 4, border: `1px solid ${THEME_TOKENS[theme].minimapBorder}`,\n cursor: 'grab',\n }}\n />\n )}\n </div>\n );\n}\n","/**\n * Opt-in setup for NetworkGraphView — import this once at app startup after\n * installing 'vis-network' + 'vis-data' (both peerDependenciesMeta.optional):\n *\n * import '@salilvnair/dui/vis-setup';\n *\n * Registers the real vis-network-backed implementation; NetworkGraphView\n * renders a static fallback until this has run.\n */\nimport { registerNetworkGraphImpl, markVisReady } from './lib/vis-runtime';\nimport { NetworkGraphViewImpl } from './lib/components/display/NetworkGraphView.vis';\n\nregisterNetworkGraphImpl(NetworkGraphViewImpl);\nmarkVisReady();\n"],"names":["DEFAULT_PALETTE","DIMMED_OPACITY","LARGE_GRAPH_NODE_THRESHOLD","LABEL_ZOOM_THRESHOLD","NODE_LABEL_BUDGET","EDGE_LABEL_BUDGET","CHIP_GRID_CELL","THEME_TOKENS","defaultColor","n","rectsOverlap","a","b","makeChipGrid","cells","key","cx","cy","forEachCell","r","fn","cx0","cx1","cy0","cy1","hit","k","bucket","i","createChipElement","overlay","el","applyChip","text","s","fontSize","fontWeight","border","nodeChipStyle","color","edgeChipStyle","tokens","clusterCommunity","network","cid","allNodes","memberIds","id","baseColor","clusterId","label","size","badgeFontSize","vadjust","nodeOptions","NetworkGraphViewImpl","nodes","edges","onNodeClick","selectedId","fitTrigger","onReady","colorBy","sizeBy","className","style","enableClustering","enableHoverDim","enableMinimap","theme","containerRef","useRef","minimapCanvasRef","networkRef","nodesDataRef","edgesDataRef","nodesById","neighborMapRef","nodeEdgesRef","labelsOverlayRef","nodeChipPoolRef","edgeChipPoolRef","chipTextSizeRef","selectedIdRef","isCameraAnimatingRef","useEffect","blurRestoreTimer","degree","neighborMap","nodeEdges","e","edgeId","maxDeg","themeTokens","nodeColors","visNodes","deg","visEdges","nodesData","DataSet","edgesData","isLargeGraph","options","Network","drawMinimap","canvas","ctx","positions","xs","p","ys","minX","maxX","minY","maxY","w","h","spanX","spanY","toCanvas","x","y","pos","scale","viewPos","canvasEl","viewW","viewH","rx1","ry1","rx2","ry2","canvasPositions","invalidatePositions","dimKeepNodes","dimKeepEdges","edgeTypeById","chipAt","pool","index","chipSize","kind","hidePoolFrom","from","syncOverlays","nodePool","edgePool","_a","_b","tl","br","candidates","grid","MAX_NUDGES","used","cand","node","box","domPos","opacity","baseLeft","baseTop","left","top","row","col","edgeUsed","type","to","mid","eOpacity","rect","syncRafHandle","syncOverlaysThrottled","willRunClusterSeparationBurst","communityIds","c","params","nodeId","prevKeptNodeIds","prevKeptEdgeIds","applyKeep","keepNodes","keepEdges","nodeUpdates","edgeUpdates","hoveredId","neighbors","lastMinimapDraw","throttledDrawMinimap","now","prevFitTriggerRef","minimapToGraph","evt","handleMinimapClick","minimapDragging","handleMinimapMouseDown","move","up","jsxs","jsx","registerNetworkGraphImpl","markVisReady"],"mappings":"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"}
|