@opendata-ai/openchart-vanilla 8.4.1 → 8.5.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/graph/renderer-registry.ts","../src/graph-mount.ts","../src/graph/camera.ts","../src/graph/zoom.ts","../src/graph/canvas-renderer.ts","../src/graph/entrance.ts","../src/graph/focus-transition.ts","../src/graph/highlight.ts","../src/graph/interaction.ts","../src/graph/keyboard.ts","../src/graph/legend.ts","../src/graph/search.ts","../src/graph/seed.ts","../src/graph/shell.ts","../src/graph/simulation.ts","../src/graph/spatial-index.ts","../src/graph/update-diff-config.ts","../src/graph/update-diff.ts"],"sourcesContent":["/**\n * Graph renderer registry.\n *\n * `createGraph()` builds the shared shell (wrapper, chrome, legend element,\n * tooltip manager, resize wiring) and then hands off to a renderer. The 2D\n * Canvas renderer is built in. The 3D WebGL renderer lives on the\n * `@opendata-ai/openchart-vanilla/graph-3d` subpath and registers itself here\n * as a side effect of being imported, so three.js never enters the default\n * bundle. `createGraph()` never dynamically imports a renderer: mount is\n * synchronous because every framework wrapper assumes it is.\n */\n\nimport type { GraphSpec } from '@opendata-ai/openchart-core';\nimport type { GraphCompilation } from '@opendata-ai/openchart-engine';\nimport type { GraphInstance, GraphMountOptions } from '../graph-mount';\nimport type { TooltipManager } from '../tooltip';\n\n/**\n * Everything a renderer needs from the shared mount shell. The shell owns the\n * DOM wrapper and its chrome/legend/tooltip children; the renderer owns the\n * surface it mounts between the chrome and the legend (a `<canvas>` for 2D,\n * the 3d-force-graph scene container for 3D) plus all simulation, camera and\n * interaction state.\n */\nexport interface GraphShell {\n /** The consumer's container element. */\n container: HTMLElement;\n /** `.oc-graph-wrapper` (carries `oc-dark` and the theme custom properties). */\n wrapper: HTMLElement;\n /** `.oc-graph-chrome` (title/subtitle HTML). */\n chromeEl: HTMLElement;\n /** `.oc-graph-legend`, or null when `options.legend === false`. */\n legendEl: HTMLElement | null;\n /** Shared DOM tooltip manager, or null when `options.tooltip === false`. */\n tooltipManager: TooltipManager | null;\n /** Resolved dark-mode flag used when the wrapper was built. */\n isDark: boolean;\n /**\n * Insert the renderer's surface element into the wrapper between the chrome\n * and the legend. Call exactly once.\n */\n mountSurface(el: HTMLElement): void;\n /** Re-render chrome HTML from the current compilation (after `update()`). */\n renderChrome(compilation: GraphCompilation): void;\n /** Re-measure the chrome and update the inset custom property. */\n syncChromeInset(): void;\n /** Current container size (width/height in CSS px, height floored at 200). */\n getSize(): { width: number; height: number };\n /**\n * Subscribe to container resizes. Honours `options.responsive === false`\n * (returns a no-op disconnect). The ResizeObserver fires once on first\n * layout, so renderers must tolerate an immediate callback.\n */\n observeResize(callback: () => void): () => void;\n /** Warn-once sink (`options.onWarn`, default `console.warn`). */\n warn(message: string): void;\n /** Remove the wrapper from the container and release the tooltip manager. */\n destroy(): void;\n}\n\nexport interface GraphRendererContext {\n shell: GraphShell;\n spec: GraphSpec;\n compilation: GraphCompilation;\n options: GraphMountOptions | undefined;\n /** Recompile a spec with the mount's compile options (theme, darkMode, onWarn). */\n compile(spec: GraphSpec): GraphCompilation;\n}\n\nexport type GraphRendererFactory = (ctx: GraphRendererContext) => GraphInstance;\n\nconst registry = new Map<2 | 3, GraphRendererFactory>();\n\n/** Register a renderer for a dimension count. Later registrations replace earlier ones. */\nexport function registerGraphRenderer(dimensions: 3, factory: GraphRendererFactory): void {\n registry.set(dimensions, factory);\n}\n\n/** The registered renderer for a dimension count, or undefined. */\nexport function getGraphRenderer(dimensions: 2 | 3): GraphRendererFactory | undefined {\n return registry.get(dimensions);\n}\n\n/** Test hook: drop all registrations. */\nexport function resetGraphRendererRegistry(): void {\n registry.clear();\n}\n\nexport const GRAPH_3D_NOT_REGISTERED_ERROR =\n 'createGraph: dimensions: 3 requires import \"@opendata-ai/openchart-vanilla/graph-3d\"';\n","/**\n * Graph mount API: the main entry point for vanilla JS graph usage.\n *\n * createGraph() takes a container, GraphSpec, and options, compiles the graph,\n * creates a force simulation, canvas renderer, spatial index, interaction\n * manager, and search manager, then runs an animation loop driven by\n * simulation ticks. Returns a GraphInstance with update/search/zoom/destroy.\n */\n\nimport type {\n CompileOptions,\n DarkMode,\n GraphSpec,\n ThemeConfig,\n TooltipContent,\n} from '@opendata-ai/openchart-core';\nimport type {\n CompiledGraphEdge,\n CompiledGraphNode,\n GraphCompilation,\n} from '@opendata-ai/openchart-engine';\nimport { buildEdgeTooltip, compileGraph } from '@opendata-ai/openchart-engine';\nimport {\n type CameraFlightOptions,\n clampK,\n createCameraFlight,\n createCameraFollow,\n} from './graph/camera';\nimport { GraphCanvasRenderer } from './graph/canvas-renderer';\nimport { ENTRANCE_STAGGER_MAX_NODES, entranceOffsets, entranceOrder } from './graph/entrance';\nimport {\n composeStandingFocus,\n type FocusSnapshot,\n FocusTransition,\n layerHoverFocus,\n} from './graph/focus-transition';\nimport {\n categoryHighlightSet as categoryHighlightIds,\n resolveHighlightTarget as resolveHighlightTargetIds,\n} from './graph/highlight';\nimport { GraphInteractionManager } from './graph/interaction';\nimport { attachGraphKeyboardNav } from './graph/keyboard';\nimport { createGraphLegend, type GraphLegendController } from './graph/legend';\nimport { createTween, prefersReducedMotion, resolveEase } from './graph/motion';\nimport {\n GRAPH_3D_NOT_REGISTERED_ERROR,\n type GraphShell,\n getGraphRenderer,\n} from './graph/renderer-registry';\nimport { AnimationScheduler, type GraphAnimation } from './graph/scheduler';\nimport { GraphSearchManager } from './graph/search';\nimport { seedNodePositions } from './graph/seed';\nimport { createGraphShell, getContainerDimensions as measureContainer } from './graph/shell';\nimport { SimulationManager } from './graph/simulation';\nimport { SpatialIndex } from './graph/spatial-index';\nimport type {\n GraphCamera,\n GraphFlyTarget,\n GraphHighlightTarget,\n GraphRenderState,\n PositionedEdge,\n PositionedNode,\n} from './graph/types';\nimport { diffGraphUpdate } from './graph/update-diff';\nimport type { SimEdge, SimNode } from './graph/worker-protocol';\nimport { ZoomTransform } from './graph/zoom';\nimport { resolveDarkMode } from './resolve-dark-mode';\nimport type { TooltipManager } from './tooltip';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n// Defined in `graph/types` (see the note there) and re-exported here so the\n// public surface stays exactly where consumers already import it from.\nexport type { GraphCamera, GraphFlyTarget, GraphHighlightTarget } from './graph/types';\n\n/** A hovered node or edge, passed to a tooltip formatter. */\nexport interface GraphTooltipItem {\n kind: 'node' | 'edge';\n /** The raw datum (node record, or edge record with source/target). */\n data: Record<string, unknown>;\n}\n\n/**\n * Custom tooltip content builder. Receives the hovered item and the library's\n * default {@link TooltipContent}, returns replacement content.\n *\n * Safety contract:\n * - A returned `TooltipContent` or `string` is escaped by the library (strings\n * are inserted via `textContent`, never `innerHTML`).\n * - A returned `HTMLElement` is trusted verbatim — the host owns sanitization.\n * - `null` suppresses the tooltip for that item.\n */\nexport type GraphTooltipFormatter = (\n item: GraphTooltipItem,\n defaults: TooltipContent,\n) => TooltipContent | string | HTMLElement | null;\n\n/** Built-in legend data (headless mirror of the rendered legend). */\nexport interface GraphLegendData {\n field: string | null;\n nodes: Array<{ label: string; color: string; count?: number; active: boolean }>;\n edges: Array<{ label: string; color: string; count?: number }>;\n}\n\nexport interface GraphMountOptions {\n theme?: ThemeConfig;\n darkMode?: DarkMode;\n /**\n * Sink for advisory spec warnings from the compiler (unknown `seedNode` id,\n * deprecations, and so on). Defaults to `console.warn`. Each distinct message\n * is emitted at most once per instance, so a warning that survives every\n * recompile doesn't spam the console on `update()`.\n */\n onWarn?: (message: string) => void;\n responsive?: boolean;\n /** Show the OpenData watermark. Defaults to true. */\n watermark?: boolean;\n /** Show the built-in tooltip; pass an object for a custom formatter. Defaults to true. */\n tooltip?: boolean | { formatter?: GraphTooltipFormatter };\n /**\n * Built-in legend. `true` (default) renders an interactive legend with counts.\n * `false` renders none (set this if you render your own legend). An object\n * toggles interactivity/counts.\n */\n legend?: boolean | { interactive?: boolean; counts?: boolean };\n onNodeClick?: (node: Record<string, unknown>) => void;\n onNodeDoubleClick?: (node: Record<string, unknown>) => void;\n onNodeHover?: (node: Record<string, unknown> | null) => void;\n onEdgeHover?: (edge: Record<string, unknown> | null) => void;\n onSelectionChange?: (nodeIds: string[]) => void;\n /** Fired when the user hovers a legend entry (null on leave). */\n onLegendHover?: (entry: { field: string; value: string } | null) => void;\n /** Fired when legend toggle state changes; `activeValues` is the active category set (empty = all). */\n onLegendToggle?: (activeValues: string[]) => void;\n /** Fired whenever the highlight set changes (programmatic or legend), null when cleared. */\n onHighlightChange?: (nodeIds: string[] | null) => void;\n /**\n * Fit the graph to the viewport on the first tick. Default true. Set false to\n * restore a saved camera (e.g. getCamera() + flyTo) without the initial fit.\n */\n fitOnLoad?: boolean;\n /** Camera change callback, rAF-coalesced (fires at most once per rendered frame). */\n onCameraChange?: (camera: GraphCamera) => void;\n /** Skip the entrance reveal/flight on mount (spec unchanged; used by wrappers when recreating for a theme/darkMode-only change so the entrance doesn't replay). Warmup still runs. */\n suppressEntrance?: boolean;\n}\n\nexport interface GraphInstance {\n update(spec: GraphSpec): void;\n /** Re-compile encoding/legend/chrome without restarting the simulation. Preserves node positions. */\n updateVisuals(spec: GraphSpec): void;\n search(query: string): void;\n clearSearch(): void;\n /** Fit all nodes into the viewport. Animated by default; `{ duration: 0 }` snaps. */\n zoomToFit(opts?: CameraFlightOptions & { padding?: number }): void;\n /** Fly to a node and zoom in (default scale 2). Tracks the node while it settles. */\n zoomToNode(nodeId: string, opts?: CameraFlightOptions & { scale?: number }): void;\n /** Fly the camera to a graph-space target. */\n flyTo(target: GraphFlyTarget, opts?: CameraFlightOptions): void;\n /** Center the camera on a graph-space point (keeps current zoom). */\n centerAt(x: number, y: number, opts?: CameraFlightOptions): void;\n /**\n * Current camera. In 2D this is the zoom transform; in 3D the same `x`/`y`/`k`\n * summary plus the `position`/`target` pose. See `GraphCamera`.\n */\n getCamera(): GraphCamera;\n /** Select a node; `{ fly: true }` also flies to it (default follows interaction.select.flyTo). */\n selectNode(nodeId: string, opts?: { fly?: boolean } & CameraFlightOptions): void;\n getSelectedNodes(): string[];\n /** Node ids currently matching the active search query. */\n getSearchMatches(): string[];\n /**\n * Emphasize a set of nodes; eased via the focus model. Layers over the\n * standing category filter (`setActiveCategories`) rather than replacing it:\n * the effective set is the intersection, or the highlight alone when the two\n * are disjoint. Legend toggle state is untouched.\n */\n highlight(target: GraphHighlightTarget, opts?: { dimOpacity?: number }): void;\n /**\n * Clear the transient highlight, returning to the standing category filter.\n * Does NOT clear the filter — use `setActiveCategories([])` for that.\n */\n clearHighlight(): void;\n /** The effective highlighted node ids, or null when nothing is highlighted. */\n getHighlight(): string[] | null;\n /** Headless snapshot of the legend (node categories + edge categories). */\n getLegend(): GraphLegendData;\n /** Set the active category filter declaratively (replaces legend toggle state). */\n setActiveCategories(values: string[]): void;\n /** Current active category values (empty = all active, no filter). */\n getActiveCategories(): string[];\n resize(): void;\n destroy(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Physics-feel gates (Phase 8)\n// ---------------------------------------------------------------------------\n\n/** Springy drag disables above this node count (warm-sim cost at scale). */\nconst SPRINGY_DRAG_MAX_NODES = 5000;\n/** Cursor-repulsion disables above this node count (mirrors the glow gate). */\nconst CURSOR_FORCE_MAX_NODES = 2000;\n/** Cursor pointer-feed throttle (~30Hz) so we don't post on every mousemove. */\nconst CURSOR_POINTER_THROTTLE_MS = 33;\n\n/** Post-flight camera follow stops once the sim alpha settles below this. */\nconst FOLLOW_SETTLE_ALPHA = 0.05;\n\n// ---------------------------------------------------------------------------\n// Main API\n// ---------------------------------------------------------------------------\n\n/**\n * Create a graph instance from a spec and mount it into a container.\n *\n * @param container - The DOM element to render into.\n * @param spec - The graph spec.\n * @param options - Mount options.\n * @returns A GraphInstance with update/search/zoom/destroy methods.\n */\nexport function createGraph(\n container: HTMLElement,\n spec: GraphSpec,\n options?: GraphMountOptions,\n): GraphInstance {\n let currentSpec = spec;\n let compilation: GraphCompilation;\n let destroyed = false;\n\n // DOM elements. The shell owns the wrapper/chrome/legend/tooltip scaffolding;\n // the fields below mirror it so the 2D path reads them directly.\n let shell: GraphShell | null = null;\n let canvas: HTMLCanvasElement | null = null;\n let chromeEl: HTMLElement | null = null;\n let legendEl: HTMLElement | null = null;\n let legendController: GraphLegendController | null = null;\n\n // Subsystems\n let renderer: GraphCanvasRenderer | null = null;\n let simulation: SimulationManager | null = null;\n const spatialIndex = new SpatialIndex();\n let interactionManager: GraphInteractionManager | null = null;\n const searchManager = new GraphSearchManager();\n let tooltipManager: TooltipManager | null = null;\n let cleanupKeyboard: (() => void) | null = null;\n let disconnectResize: (() => void) | null = null;\n\n // State\n let positionedNodes: PositionedNode[] = [];\n let positionedEdges: PositionedEdge[] = [];\n let adjacencyMap = new Map<string, Set<string>>();\n let nodeDataMap = new Map<string, Record<string, unknown>>();\n let edgeDataMap = new Map<string, Record<string, unknown>>();\n let hoveredNodeId: string | null = null;\n let hoveredEdgeId: string | null = null;\n let selectedNodeIds = new Set<string>();\n let animFrameId: number | null = null;\n let needsRender = false;\n let isGesturing = false;\n // Continuous-animation scheduler. Arms the first frame via scheduleRender on\n // the idle→active transition; the render loop ticks it each frame and re-arms\n // only while it stays active, so the base loop stays strictly dirty-flag.\n const scheduler = new AnimationScheduler(() => scheduleRender());\n let gestureTimeout: ReturnType<typeof setTimeout> | null = null;\n let lastEdgeHitTime = 0;\n // Cursor-repulsion pointer-feed throttle timestamp (Phase 8).\n let lastPointerFeedTime = 0;\n // Camera flight state.\n let activeFlight: GraphAnimation | null = null;\n // Post-flight follow for provider-form flights (tracks a still-settling node).\n let activeFollow: GraphAnimation | null = null;\n // Latest simulation alpha, fed by onTick; the follow stops below the threshold.\n let lastAlpha = 1;\n let cameraChangePending = false;\n\n // Focus / highlight state (Phase 5). Two independent layers compose into one\n // effective set:\n // - `activeCategories` is the STICKY category filter (empty = no filter),\n // written only by setActiveCategories / toggleLegendCategory /\n // applyInitialHighlight. Legend `active` flags track this and only this.\n // - `transientHighlight` is the TRANSIENT emphasis owned solely by\n // highlight() / clearHighlight(), along with `highlightDimOpacity`.\n // `transientTarget` keeps the unresolved target so a category-form\n // highlight can be re-resolved against new data on update().\n // `highlightSet` is the cached composition of the two (null = nothing\n // highlighted), assigned only by recomputeHighlight(). It is the cache\n // standingSnapshot() reads, so armFocus stays allocation-free per frame.\n let highlightSet: Set<string> | null = null;\n let highlightDimOpacity: number | null = null;\n // Ids exempt from highlight/filter dimming (the spec's `seedNode`). Kept OUT\n // of `highlightSet` on purpose: composeStandingFocus expands the core set to\n // `core ∪ neighbors(core)`, and a seed is by construction a hub, so unioning\n // it would light most of the graph and defeat the category filter. Keeping it\n // out also means getHighlight()/onHighlightChange never leak the seed id.\n // Re-derived wherever `compilation` is reassigned.\n let seedIds = new Set<string>();\n let activeCategories = new Set<string>();\n let transientHighlight: Set<string> | null = null;\n let transientTarget: GraphHighlightTarget | null = null;\n // Compiler warnings already emitted by this instance (see warnOnce).\n const seenWarnings = new Set<string>();\n // Node id → its legend-field category value, for category-based highlight.\n let nodeCategory = new Map<string, string>();\n // The live focus crossfade, driving eased dimming. Rebuilt on first render.\n let focusTransition: FocusTransition | null = null;\n // Scheduler animation that keeps frames dirty while the focus crossfade runs.\n let focusAnim: GraphAnimation | null = null;\n // Hovered-node radius tween (1 → 1.15), null when settled at 1.\n let hoverRadiusTween: { nodeId: string; scale: number } | null = null;\n\n // Entrance choreography state (Phase 6). `entranceProgress` is a mount-level\n // 0→1 value read by buildRenderState; < 1 means the reveal is mid-flight.\n // `entranceActive` gates the render-state `entrance` field. `entranceFitInFlight`\n // tracks the entrance camera flight so a resize can cancel just it (keeping the\n // reveal). `entranceReveal` is the scheduler tween driving `entranceProgress`.\n let entranceProgress = 1;\n let entranceActive = false;\n let entranceStagger = false;\n // Pop choreography inputs (staggered entrances only): hash-scattered stagger\n // rank and per-node convergence drift vectors, built once at entrance start.\n let entranceOrderMap: Map<string, number> | null = null;\n let entranceOffsetMap: Map<string, { x: number; y: number }> | null = null;\n let entranceFitInFlight = false;\n let entranceReveal: GraphAnimation | null = null;\n // Mount-level opt-out (Phase 9): when set, the FIRST entrance takes the instant\n // -fit branch (no 0.92 pullback, no reveal tween, no camera flight) even under\n // normal motion. Wrappers set this when recreating for a theme/darkMode-only\n // change so the entrance doesn't replay. Warmup still runs. One-shot.\n let suppressEntranceOnce = options?.suppressEntrance ?? false;\n\n // Data-update transition state (Phase 7). `enterAlphaMap` fades newly-added\n // nodes in over update.duration (null when no enter-fade is live). `exiting`\n // holds ghost marks (removed nodes/edges) fading out over exit.duration; both\n // are read by buildRenderState and drawn by the canvas renderer.\n let enterAlphaMap: Map<string, number> | null = null;\n let exitingGhosts: { nodes: PositionedNode[]; edges: PositionedEdge[]; alpha: number } | null =\n null;\n\n // ---------------------------------------------------------------------------\n // Helpers\n // ---------------------------------------------------------------------------\n\n function markGesture(): void {\n isGesturing = true;\n if (gestureTimeout !== null) clearTimeout(gestureTimeout);\n gestureTimeout = setTimeout(() => {\n isGesturing = false;\n gestureTimeout = null;\n needsRender = true;\n scheduleRender();\n }, 150);\n }\n\n function getContainerDimensions(): { width: number; height: number } {\n return measureContainer(container);\n }\n\n /**\n * Compiler warning sink. `update()` recompiles, so a warning about a standing\n * spec condition (an unknown `seedNode` id, say) would otherwise repeat on\n * every data change. Emit each distinct message once per instance.\n */\n function warnOnce(message: string): void {\n if (seenWarnings.has(message)) return;\n seenWarnings.add(message);\n if (options?.onWarn) options.onWarn(message);\n else console.warn(message);\n }\n\n function compile(specToCompile: GraphSpec = currentSpec): GraphCompilation {\n const { width, height } = getContainerDimensions();\n const darkMode = resolveDarkMode(options?.darkMode);\n\n const compileOpts: CompileOptions = {\n width,\n height,\n theme: options?.theme,\n darkMode,\n watermark: options?.watermark,\n onWarn: warnOnce,\n };\n\n return compileGraph(specToCompile, compileOpts);\n }\n\n function buildDataMaps(): void {\n nodeDataMap = new Map(compilation.nodes.map((n) => [n.id, n.data ?? {}]));\n edgeDataMap = new Map(compilation.edges.map((e) => [`${e.source}->${e.target}`, e.data ?? {}]));\n\n // Node → legend-field category value, for category hover/highlight. Empty\n // when the graph has no categorical color field.\n nodeCategory = new Map();\n const field = compilation.legendField;\n if (field) {\n for (const n of compilation.nodes) {\n const v = n.data?.[field];\n if (v != null) nodeCategory.set(n.id, String(v));\n }\n }\n }\n\n function buildAdjacencyMap(edges: CompiledGraphEdge[]): Map<string, Set<string>> {\n const map = new Map<string, Set<string>>();\n for (const edge of edges) {\n if (!map.has(edge.source)) map.set(edge.source, new Set());\n if (!map.has(edge.target)) map.set(edge.target, new Set());\n map.get(edge.source)!.add(edge.target);\n map.get(edge.target)!.add(edge.source);\n }\n return map;\n }\n\n function toSimNodes(nodes: CompiledGraphNode[]): SimNode[] {\n return nodes.map((n) => ({\n id: n.id,\n radius: n.radius,\n community: n.community,\n }));\n }\n\n function toSimEdges(edges: CompiledGraphEdge[]): SimEdge[] {\n return edges.map((e) => ({\n source: e.source,\n target: e.target,\n }));\n }\n\n /**\n * Look up a node's data from the compilation by id.\n * Falls back to an empty object if not found.\n */\n function nodeDataById(nodeId: string): Record<string, unknown> {\n return nodeDataMap.get(nodeId) ?? {};\n }\n\n /**\n * Point-to-line-segment distance for edge hit testing.\n * Returns the shortest distance from point (px, py) to the segment (ax, ay)-(bx, by).\n */\n function pointToSegmentDist(\n px: number,\n py: number,\n ax: number,\n ay: number,\n bx: number,\n by: number,\n ): number {\n const dx = bx - ax;\n const dy = by - ay;\n const lenSq = dx * dx + dy * dy;\n if (lenSq === 0) return Math.hypot(px - ax, py - ay);\n const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / lenSq));\n return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));\n }\n\n /**\n * Find the edge closest to a graph-space point, within a threshold.\n * Returns an edge key \"source->target\" or null.\n */\n function hitTestEdge(graphX: number, graphY: number, threshold: number): string | null {\n let bestDist = threshold;\n let bestEdgeId: string | null = null;\n\n for (const edge of positionedEdges) {\n const dist = pointToSegmentDist(\n graphX,\n graphY,\n edge.sourceX,\n edge.sourceY,\n edge.targetX,\n edge.targetY,\n );\n if (dist < bestDist) {\n bestDist = dist;\n bestEdgeId = `${edge.source}->${edge.target}`;\n }\n }\n\n return bestEdgeId;\n }\n\n /**\n * Look up edge data by edge id (\"source->target\").\n */\n function edgeDataById(edgeId: string): Record<string, unknown> | null {\n return edgeDataMap.get(edgeId) ?? null;\n }\n\n // ---------------------------------------------------------------------------\n // DOM creation\n // ---------------------------------------------------------------------------\n\n /**\n * Mount the 2D drawing surface into the shell. The wrapper, chrome band and\n * legend slot already exist; this only adds the canvas between them and\n * builds the renderer.\n */\n function createSurface(): void {\n const activeShell = shell;\n if (!activeShell) return;\n\n canvas = document.createElement('canvas');\n canvas.className = 'oc-graph-canvas';\n canvas.setAttribute('role', 'img');\n if (compilation.a11y?.altText) {\n canvas.setAttribute('aria-label', compilation.a11y.altText);\n }\n activeShell.mountSurface(canvas);\n\n renderLegend();\n activeShell.syncChromeInset();\n\n // Canvas uses the full container height; chrome overlays on top\n const { width, height } = activeShell.getSize();\n renderer = new GraphCanvasRenderer(canvas);\n renderer.resize(width, height);\n }\n\n function renderChrome(): void {\n shell?.renderChrome(compilation);\n }\n\n /**\n * The effective legend setting. `GraphSpec.legend` and the mount option say\n * the same thing at two levels; the mount option wins when both are set, so a\n * host can override a spec it doesn't own.\n */\n function legendSetting(): GraphMountOptions['legend'] {\n return options?.legend ?? currentSpec.legend;\n }\n\n /** Resolve legend interactive/counts flags from the legend setting. */\n function legendConfig(): { interactive: boolean; counts: boolean } {\n const l = legendSetting();\n if (l && typeof l === 'object') {\n return { interactive: l.interactive ?? true, counts: l.counts ?? true };\n }\n return { interactive: true, counts: true };\n }\n\n /** Build the legend view data from the compilation + current toggle state. */\n function legendViewData(): { nodes: GraphLegendData['nodes']; edges: GraphLegendData['edges'] } {\n return { nodes: getLegend().nodes, edges: getLegend().edges };\n }\n\n /** Create or refresh the interactive legend from current state. */\n function renderLegend(): void {\n if (!legendEl) return;\n const cfg = legendConfig();\n if (!legendController) {\n legendController = createGraphLegend(legendEl, legendViewData(), {\n interactive: cfg.interactive,\n counts: cfg.counts,\n onToggle: (value) => toggleLegendCategory(value),\n onHover: (value) => {\n const field = compilation.legendField;\n options?.onLegendHover?.(value !== null && field ? { field, value } : null);\n },\n });\n } else {\n legendController.update(legendViewData());\n }\n }\n\n /** Re-render the legend to reflect the current active-category state. */\n function syncLegendActiveState(): void {\n if (legendController) legendController.update(legendViewData());\n syncChromeInset();\n }\n\n function syncChromeInset(): void {\n shell?.syncChromeInset();\n }\n\n /**\n * Height of the chrome overlay band (title + subtitle) the camera fit should\n * reserve, so nodes never settle underneath the text. 0 when chrome is empty\n * or unmeasurable (happy-dom).\n */\n function chromeInsetTop(): number {\n if (!chromeEl || chromeEl.style.display === 'none') return 0;\n return chromeEl.offsetHeight;\n }\n\n // ---------------------------------------------------------------------------\n // Physics-feel gates (Phase 8)\n // ---------------------------------------------------------------------------\n\n /**\n * Springy drag on: config opts in AND the graph is small enough that holding\n * the sim warm during a drag is cheap. Off/above threshold → legacy pin/unpin.\n */\n function springyDragEnabled(): boolean {\n return (\n compilation.interaction.springyDrag && compilation.nodes.length <= SPRINGY_DRAG_MAX_NODES\n );\n }\n\n /**\n * Cursor repulsion on: config opts in, the graph is small enough (mirrors the\n * glow gate), and reduced motion is off (ambient pointer-driven motion is\n * exactly what reduced-motion suppresses).\n */\n function cursorForceEnabled(): boolean {\n return (\n compilation.interaction.cursorRepulsion !== null &&\n compilation.nodes.length <= CURSOR_FORCE_MAX_NODES &&\n !prefersReducedMotion()\n );\n }\n\n // ---------------------------------------------------------------------------\n // Simulation and animation\n // ---------------------------------------------------------------------------\n\n /**\n * Create the simulation for the current compilation.\n *\n * On the initial mount (no `opts`), every node is seeded deterministically and\n * a fresh warmup runs. On a data update, `opts` supplies pre-known positions\n * (survivors keep their prior x/y; enterers get spawn positions), suppresses\n * the center force (it snaps every node by the full centroid error on tick 1,\n * the global-jump artifact this phase eliminates), and overrides the initial\n * alpha to the local-reheat impulse. Update sims skip warmup — survivors are\n * already settled, so a headless warmup would just churn them.\n */\n function initSimulation(opts?: {\n positions?: Map<string, { x: number; y: number }>;\n suppressCenter?: boolean;\n initialAlpha?: number;\n skipWarmup?: boolean;\n skipEntrance?: boolean;\n }): void {\n const simNodes = toSimNodes(compilation.nodes);\n const simEdges = toSimEdges(compilation.edges);\n const config = compilation.simulationConfig;\n\n if (opts?.positions) {\n // Update path: place each node at its known (survivor or spawn) position.\n // Any node without a supplied position (shouldn't happen) falls back to the\n // seeded disc so it isn't stuck at the origin.\n seedNodePositions(simNodes, config.seed ?? 0);\n for (const n of simNodes) {\n const p = opts.positions.get(n.id);\n if (p) {\n n.x = p.x;\n n.y = p.y;\n }\n }\n } else {\n // Initial mount: seed deterministic positions BEFORE the simulation starts,\n // so the settled layout is reproducible for a given (spec, seed).\n seedNodePositions(simNodes, config.seed ?? 0);\n }\n\n simulation = SimulationManager.create(simNodes, simEdges, {\n chargeStrength: config.chargeStrength,\n linkDistance: config.linkDistance,\n clustering: config.clustering,\n alphaDecay: config.alphaDecay,\n velocityDecay: config.velocityDecay,\n collisionRadius: config.collisionRadius,\n collisionPadding: config.collisionPadding,\n linkStrength: config.linkStrength,\n // Update sims suppress the (non-alpha-scaled) center force to avoid a\n // global jump on tick 1; the alpha-scaled forceX/forceY gravity still\n // holds the layout centered.\n centerForce: opts?.suppressCenter ? false : config.centerForce,\n warmupTicks: opts?.skipWarmup ? 0 : config.warmupTicks,\n warmupBudgetMs: config.warmupBudgetMs,\n initialAlpha: opts?.initialAlpha ?? config.initialAlpha,\n // Cursor force radius/strength (null when disabled or gated off by node\n // count). The mount only feeds pointer positions when the same gate holds.\n cursorRepulsion: cursorForceEnabled() ? compilation.interaction.cursorRepulsion : null,\n });\n\n // A fresh sim starts hot; don't let a settled previous sim's alpha linger\n // (it would end a post-flight camera follow before the first tick lands).\n lastAlpha = opts?.initialAlpha ?? 1;\n\n let initialSettleDone = false;\n // Update sims keep the current camera and don't run the entrance reveal, so\n // pre-mark the fit as done — the first update tick just streams positions.\n let initialFitDone = opts?.skipEntrance ?? false;\n\n simulation.onTick((positions, alpha) => {\n if (destroyed) return;\n lastAlpha = alpha;\n\n // Build position lookup\n const posMap = new Map<string, { x: number; y: number }>();\n for (const p of positions) {\n posMap.set(p.id, { x: p.x, y: p.y });\n }\n\n // Build positioned nodes\n positionedNodes = compilation.nodes.map((node, index) => {\n const pos = posMap.get(node.id) ?? { x: 0, y: 0 };\n return { ...node, x: pos.x, y: pos.y, index };\n });\n\n // Build positioned edges\n positionedEdges = compilation.edges.map((edge) => {\n const src = posMap.get(edge.source) ?? { x: 0, y: 0 };\n const tgt = posMap.get(edge.target) ?? { x: 0, y: 0 };\n return {\n ...edge,\n sourceX: src.x,\n sourceY: src.y,\n targetX: tgt.x,\n targetY: tgt.y,\n };\n });\n\n // Rebuild spatial index\n spatialIndex.rebuild(positionedNodes);\n\n // Fit + entrance choreography on the FIRST post-warmup tick (the sim only\n // starts streaming ticks once warmup, if any, has completed). After that,\n // let the user interact freely while the simulation keeps settling.\n if (\n !initialFitDone &&\n positionedNodes.length > 0 &&\n interactionManager &&\n options?.fitOnLoad !== false\n ) {\n initialFitDone = true;\n startEntrance();\n } else if (!initialFitDone && options?.fitOnLoad === false) {\n // Skip the fit but mark it done so a saved camera (getCamera/flyTo) sticks.\n initialFitDone = true;\n }\n\n needsRender = true;\n scheduleRender();\n });\n\n simulation.onSettled(() => {\n if (initialSettleDone) return;\n initialSettleDone = true;\n });\n }\n\n /**\n * Compute the initial fit transform. Bypasses fitBounds' spread inflation when\n * warmup ran (warmed bounds are near-final; inflating them fits too small).\n */\n function computeInitialFit(): ZoomTransform {\n const { width: cw, height: ch } = getCanvasDimensions();\n const warmed = (compilation.simulationConfig.warmupTicks ?? 0) > 0;\n const { transform } = ZoomTransform.fitBounds(positionedNodes, cw, ch, undefined, {\n spread: !warmed,\n insetTop: chromeInsetTop(),\n });\n return transform;\n }\n\n /**\n * Fit + run the entrance reveal on the first post-warmup tick. Under an enabled\n * `enter` phase and normal motion, the camera starts pulled back to 0.92× the\n * fit and (optionally) flies in while a mount-level `entranceProgress` tween\n * ramps node/edge/label reveal. Under reduced motion or `animation: false`,\n * it's an instant fit (warmup still ran — it reduces motion, it isn't motion).\n */\n function startEntrance(): void {\n if (!interactionManager) return;\n const fit = computeInitialFit();\n const enter = compilation.animation?.enter ?? null;\n // One-shot mount-level suppression (theme/darkMode remount): take the same\n // instant-fit branch as reduced motion so the entrance doesn't replay.\n const suppressed = suppressEntranceOnce;\n suppressEntranceOnce = false;\n if (suppressed || !enter || prefersReducedMotion()) {\n interactionManager.setTransform(fit);\n cameraChangePending = true;\n entranceActive = false;\n entranceProgress = 1;\n return;\n }\n\n // Start pulled back so the reveal has somewhere to fly in from. 0.7 gives a\n // clearly readable pull-in on load (0.85 was too subtle to register as motion).\n const { width: cw, height: ch } = getCanvasDimensions();\n const pulledBack = fit.zoomAt(fit.k * 0.7, cw / 2, ch / 2);\n interactionManager.setTransform(pulledBack);\n cameraChangePending = true;\n\n entranceActive = true;\n entranceProgress = 0;\n // Stagger only when the spec asks for it AND the graph is small enough that\n // per-node start times still batch. Above the cap: a single global fade.\n entranceStagger = enter.stagger && positionedNodes.length <= ENTRANCE_STAGGER_MAX_NODES;\n // Pop choreography inputs, computed once against the warmed (near-final)\n // positions: hash-scattered stagger order + per-node convergence drift.\n if (entranceStagger) {\n entranceOrderMap = entranceOrder(positionedNodes);\n entranceOffsetMap = entranceOffsets(positionedNodes);\n } else {\n entranceOrderMap = null;\n entranceOffsetMap = null;\n }\n\n // Optional camera flight from the pulled-back framing to the true fit.\n if (enter.cameraFit) {\n entranceFitInFlight = true;\n flyCamera(fit, { duration: enter.duration + 100 }, () => {\n entranceFitInFlight = false;\n });\n }\n\n // Reveal tween drives entranceProgress 0→1; ends the entrance on completion.\n const ease = resolveEase(enter.ease);\n entranceReveal = createTween({\n duration: enter.duration,\n ease,\n apply: (t) => {\n entranceProgress = t;\n needsRender = true;\n },\n onDone: () => {\n entranceProgress = 1;\n entranceActive = false;\n entranceReveal = null;\n needsRender = true;\n },\n });\n scheduler.add(entranceReveal);\n }\n\n function getCanvasDimensions(): { width: number; height: number } {\n if (!canvas) return { width: 600, height: 400 };\n const rect = canvas.getBoundingClientRect();\n return {\n width: Math.max(rect.width || 600, 100),\n height: Math.max(rect.height || 400, 100),\n };\n }\n\n function scheduleRender(): void {\n if (animFrameId !== null || destroyed) return;\n animFrameId = requestAnimationFrame(renderFrame);\n }\n\n // -------------------------------------------------------------------------\n // Focus model (Phase 5): highlight ∩ search + hover neighborhood, eased.\n // -------------------------------------------------------------------------\n\n /** Neighborhood of the hovered node under the resolved hover mode. */\n function hoverConnectedSet(nodeId: string | null): Set<string> | null {\n if (nodeId === null) return null;\n if (compilation.interaction.hoverMode === 'category') {\n const cat = nodeCategory.get(nodeId);\n const set = new Set<string>([nodeId]);\n if (cat !== undefined) {\n for (const [id, c] of nodeCategory) if (c === cat) set.add(id);\n }\n return set;\n }\n // 'neighbors' (default): the node plus its adjacency.\n const set = new Set<string>([nodeId]);\n const neighbors = adjacencyMap.get(nodeId);\n if (neighbors) for (const nid of neighbors) set.add(nid);\n return set;\n }\n\n /** The standing (non-hover) focus from highlight + search + selection. */\n function standingSnapshot(): FocusSnapshot {\n return composeStandingFocus(\n highlightSet,\n searchManager.getMatches(),\n selectedNodeIds,\n adjacencyMap,\n );\n }\n\n /** The full target snapshot = standing state with the hover layer on top. */\n function targetSnapshot(): FocusSnapshot {\n return layerHoverFocus(standingSnapshot(), hoveredNodeId, hoverConnectedSet(hoveredNodeId));\n }\n\n /**\n * Point the focus crossfade at the current target and arm a scheduler\n * animation that keeps frames dirty until it settles. Snaps instantly under\n * reduced motion or when hover animation is disabled.\n */\n function armFocus(now: number): void {\n const target = targetSnapshot();\n const hoverCfg = compilation.animation?.hover ?? null;\n const duration = hoverCfg && !prefersReducedMotion() ? hoverCfg.duration : 0;\n const ease = resolveEase(hoverCfg?.ease ?? 'smooth');\n\n if (!focusTransition) {\n focusTransition = new FocusTransition(target, duration, ease, now);\n return;\n }\n focusTransition.retarget(target, now);\n\n // Keep the crossfade running via a scheduler animation until settled.\n if (focusAnim) scheduler.remove(focusAnim);\n focusAnim = {\n tick: (t: number): boolean => {\n const running = focusTransition !== null && !focusTransition.isSettled(t);\n if (!running) focusAnim = null;\n return running;\n },\n finish: (): void => {\n focusAnim = null;\n },\n cancel: (): void => {\n focusAnim = null;\n },\n };\n scheduler.add(focusAnim);\n }\n\n /** Recompute the highlight set and re-arm the focus crossfade. */\n function refreshFocus(): void {\n armFocus(performance.now());\n needsRender = true;\n scheduleRender();\n }\n\n /** Resolve a highlight target into a concrete node id set. */\n function resolveHighlightTarget(target: GraphHighlightTarget): Set<string> {\n return resolveHighlightTargetIds(target, compilation.nodes, adjacencyMap);\n }\n\n /** Node ids for the active legend categories (empty categories = no filter). */\n function categoryHighlightSet(): Set<string> | null {\n return categoryHighlightIds(activeCategories, nodeCategory);\n }\n\n /**\n * Recompose the effective highlight from the sticky category filter and the\n * transient highlight(). THE ONLY assigner of `highlightSet`.\n *\n * Composition rule: both present → their intersection, except when the\n * intersection is empty, in which case the transient wins (a host legend\n * hovering an out-of-filter row previews that category — the built-in legend\n * only fires `onLegendHover`, so this path is host-driven). Exactly one\n * present → that one. Neither → null. Same shape as the highlight ∩ search\n * rule in composeStandingFocus.\n *\n * An EMPTY transient is not a transient: `resolveHighlightTarget` returns an\n * empty set for `{ nodeIds: [] }` or a category matching zero nodes, and\n * treating that as a layer would silently wipe the standing filter's dimming.\n */\n function recomputeHighlight(): void {\n const filter = categoryHighlightSet();\n const transient =\n transientHighlight !== null && transientHighlight.size > 0 ? transientHighlight : null;\n if (filter !== null && transient !== null) {\n const inter = new Set<string>();\n for (const id of transient) if (filter.has(id)) inter.add(id);\n highlightSet = inter.size > 0 ? inter : transient;\n return;\n }\n highlightSet = filter ?? transient;\n }\n\n /**\n * Re-resolve the standing transient target against the current compilation\n * and drop ids that no longer exist. Category-form targets track data changes\n * this way; an unpruned id set would otherwise resurrect deleted nodes.\n */\n function refreshTransientHighlight(): void {\n if (transientTarget === null) {\n transientHighlight = null;\n return;\n }\n const nextIds = new Set(compilation.nodes.map((n) => n.id));\n const resolved = new Set(\n [...resolveHighlightTarget(transientTarget)].filter((id) => nextIds.has(id)),\n );\n transientHighlight = resolved.size > 0 ? resolved : null;\n }\n\n /** The custom `dimOpacity` applies only while a transient highlight is up. */\n function effectiveDimOpacity(): number {\n const custom = transientHighlight !== null ? highlightDimOpacity : null;\n return custom ?? compilation.interaction.dimOpacity;\n }\n\n /** Fire onHighlightChange with the current highlight set. */\n function emitHighlightChange(): void {\n options?.onHighlightChange?.(highlightSet ? [...highlightSet] : null);\n }\n\n /** Apply the resolved initialHighlight from the compilation, if any. */\n function applyInitialHighlight(): void {\n const init = compilation.initialHighlight;\n if (!init) return;\n activeCategories = new Set(init.values);\n recomputeHighlight();\n }\n\n /** Start (or cancel) the hovered node's 1 → 1.15 radius tween. */\n function startHoverRadiusTween(nodeId: string | null): void {\n const hoverCfg = compilation.animation?.hover ?? null;\n // No hover animation, reduced motion, or hover-off → snap (no tween).\n if (nodeId === null || !hoverCfg || prefersReducedMotion()) {\n hoverRadiusTween = nodeId ? { nodeId, scale: 1.15 } : null;\n return;\n }\n hoverRadiusTween = { nodeId, scale: 1 };\n const ease = resolveEase(hoverCfg.ease);\n const tween = createTween({\n duration: hoverCfg.duration,\n ease,\n apply: (t) => {\n // Guard against a newer hover having replaced the target mid-tween.\n if (hoverRadiusTween?.nodeId === nodeId) hoverRadiusTween.scale = 1 + 0.15 * t;\n needsRender = true;\n },\n onDone: () => {\n if (hoverRadiusTween?.nodeId === nodeId) hoverRadiusTween.scale = 1.15;\n },\n });\n scheduler.add(tween);\n }\n\n /** Resolve tooltip content for a node, applying a custom formatter if set. */\n function showNodeTooltip(nodeId: string): void {\n if (!tooltipManager || !interactionManager) return;\n const defaults = compilation.tooltipDescriptors.get(nodeId);\n if (!defaults) return;\n const node = positionedNodes.find((n) => n.id === nodeId);\n if (!node) return;\n const screen = interactionManager.getTransform().graphToScreen(node.x, node.y);\n\n const formatter = tooltipFormatter();\n if (!formatter) {\n tooltipManager.show(defaults, screen.x, screen.y);\n return;\n }\n const result = formatter({ kind: 'node', data: nodeDataById(nodeId) }, defaults);\n applyFormatterResult(result, screen.x, screen.y);\n }\n\n /** Resolve tooltip content for an edge (lazy default), applying a formatter. */\n function showEdgeTooltip(\n edgeId: string,\n data: Record<string, unknown>,\n screenX: number,\n screenY: number,\n ): void {\n if (!tooltipManager) return;\n const edge = compilation.edges.find((e) => `${e.source}->${e.target}` === edgeId);\n const defaults = edge ? buildEdgeTooltip(edge) : { title: edgeId, fields: [] };\n\n const formatter = tooltipFormatter();\n if (!formatter) {\n tooltipManager.show(defaults, screenX, screenY);\n return;\n }\n const result = formatter({ kind: 'edge', data }, defaults);\n applyFormatterResult(result, screenX, screenY);\n }\n\n /** The configured tooltip formatter, or null when tooltips are plain. */\n function tooltipFormatter(): GraphTooltipFormatter | null {\n const t = options?.tooltip;\n return t && typeof t === 'object' ? (t.formatter ?? null) : null;\n }\n\n /** Route a formatter's return value to the tooltip manager (or hide on null). */\n function applyFormatterResult(\n result: TooltipContent | string | HTMLElement | null,\n x: number,\n y: number,\n ): void {\n if (!tooltipManager) return;\n if (result === null) {\n tooltipManager.hide();\n } else if (typeof result === 'string') {\n tooltipManager.show({ text: result }, x, y);\n } else if (result instanceof HTMLElement) {\n tooltipManager.show({ element: result }, x, y);\n } else {\n tooltipManager.show(result, x, y);\n }\n }\n\n /** Build the immutable per-frame render state from current mount state. */\n function buildRenderState(now: number): GraphRenderState {\n const transform = interactionManager!.getTransform();\n\n // Ensure a focus transition exists (first render), pointed at current state.\n if (!focusTransition) armFocus(now);\n const ft = focusTransition as FocusTransition;\n const t = ft.progress(now);\n const focus = t < 1 ? { t, prev: ft.prev, next: ft.next } : undefined;\n // When settled we still pass the resting snapshot as `next` for the fast path.\n const settledNext = ft.next;\n\n const hoverRadiusScale = hoverRadiusTween\n ? new Map([[hoverRadiusTween.nodeId, hoverRadiusTween.scale]])\n : undefined;\n\n return {\n nodes: positionedNodes,\n edges: positionedEdges,\n transform: { x: transform.x, y: transform.y, k: transform.k },\n hoveredNodeId,\n hoveredEdgeId,\n selectedNodeIds,\n adjacencyMap,\n theme: compilation.theme,\n searchMatches: searchManager.getMatches(),\n exemptIds: seedIds,\n isGesturing,\n watermark: compilation.watermark,\n focus: focus ?? { t: 1, prev: settledNext, next: settledNext },\n hoverRadiusScale,\n dimOpacity: effectiveDimOpacity(),\n entrance:\n entranceActive && entranceProgress < 1\n ? {\n t: entranceProgress,\n stagger: entranceStagger,\n order: entranceOrderMap ?? undefined,\n offsets: entranceOffsetMap ?? undefined,\n }\n : undefined,\n enterAlpha: enterAlphaMap ?? undefined,\n exiting: exitingGhosts ?? undefined,\n };\n }\n\n function renderFrame(now: number): void {\n animFrameId = null;\n if (destroyed || !renderer || !interactionManager) return;\n\n // Tick animations first; a running animation dirties the frame. Animations\n // mutate mount state only — they never render or arm rAF themselves.\n if (scheduler.tick(now)) needsRender = true;\n\n if (needsRender) {\n needsRender = false;\n renderer.render(buildRenderState(now));\n }\n\n // Re-arm only while animations are active; otherwise the loop goes idle.\n if (scheduler.active) scheduleRender();\n\n // Emit a coalesced camera-change after the frame renders (at most once/frame).\n if (cameraChangePending) {\n cameraChangePending = false;\n const t = interactionManager.getTransform();\n options?.onCameraChange?.({ x: t.x, y: t.y, k: t.k });\n }\n }\n\n /**\n * Fly the camera to a target transform (or a provider that tracks a moving\n * target). Cancels any prior flight. Snaps instead of flying when animation is\n * disabled, reduced motion is active, or duration is 0.\n */\n function flyCamera(\n to: ZoomTransform | (() => ZoomTransform),\n opts?: CameraFlightOptions,\n onDone?: () => void,\n ): void {\n if (destroyed || !interactionManager) return;\n\n // Cancel any in-flight camera animation (and a lingering post-flight follow).\n cancelFlight();\n\n const cameraCfg = compilation.animation?.camera ?? null;\n const resolveTarget = () => (typeof to === 'function' ? to() : to);\n const snap = cameraCfg === null || prefersReducedMotion() || opts?.duration === 0;\n\n if (snap) {\n interactionManager.setTransform(resolveTarget());\n cameraChangePending = true;\n needsRender = true;\n scheduleRender();\n onDone?.();\n return;\n }\n\n const { width, height } = getCanvasDimensions();\n const duration = opts?.duration ?? cameraCfg?.duration ?? 'auto';\n const ease = opts?.ease ?? cameraCfg?.ease ?? 'smooth';\n // Large graphs skip labels/glow while flying; the final frame is full quality.\n const heavy = positionedNodes.length > 1000;\n\n const flight = createCameraFlight({\n from: interactionManager.getTransform(),\n to,\n viewport: { width, height },\n apply: (t) => {\n interactionManager!.setTransform(t);\n isGesturing = heavy;\n cameraChangePending = true;\n needsRender = true;\n },\n onDone: () => {\n activeFlight = null;\n isGesturing = false;\n needsRender = true;\n // Provider-form flights converge at t=1 while the tracked node may\n // still be settling — keep following it until the sim quiets down.\n if (typeof to === 'function') startFollow(to);\n scheduleRender();\n onDone?.();\n },\n opts: { duration, ease },\n });\n activeFlight = flight;\n scheduler.add(flight);\n }\n\n /** Snap the camera to a provider each frame until the sim settles. */\n function startFollow(target: () => ZoomTransform): void {\n const follow = createCameraFollow({\n target,\n apply: (t) => {\n interactionManager!.setTransform(t);\n cameraChangePending = true;\n needsRender = true;\n },\n isActive: () => !destroyed && lastAlpha >= FOLLOW_SETTLE_ALPHA,\n });\n activeFollow = follow;\n scheduler.add(follow);\n }\n\n /** Cancel any active camera flight/follow (called by user-initiated pan/zoom). */\n function cancelFlight(): void {\n if (activeFlight) {\n scheduler.remove(activeFlight);\n activeFlight = null;\n isGesturing = false;\n }\n if (activeFollow) {\n scheduler.remove(activeFollow);\n activeFollow = null;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Interaction wiring\n // ---------------------------------------------------------------------------\n\n function initInteraction(): void {\n if (!canvas) return;\n\n interactionManager = new GraphInteractionManager(canvas, spatialIndex, {\n onTransformChange(_transform) {\n // User-initiated pan/zoom cancels any camera flight. Programmatic\n // setTransform does NOT call this callback, so there's no feedback loop;\n // node-drag correctly doesn't reach here either.\n cancelFlight();\n markGesture();\n // User pan/zoom is a camera change too — zoom UIs and camera\n // persistence rely on the coalesced onCameraChange, not polling.\n cameraChangePending = true;\n needsRender = true;\n scheduleRender();\n },\n onHoverChange(nodeId) {\n // Skip redundant work (and a duplicate onNodeHover fire) when the hovered\n // node id is unchanged.\n if (nodeId === hoveredNodeId) return;\n hoveredNodeId = nodeId;\n // Re-arm the focus crossfade so the hover neighborhood eases in/out.\n armFocus(performance.now());\n startHoverRadiusTween(nodeId);\n needsRender = true;\n scheduleRender();\n\n // Race-safe ordering: clear any edge hover and fire onEdgeHover(null)\n // BEFORE onNodeHover(node), so an edge session never interleaves inside\n // a node hover session.\n if (nodeId && hoveredEdgeId) {\n hoveredEdgeId = null;\n options?.onEdgeHover?.(null);\n tooltipManager?.hide();\n }\n\n // Fire onNodeHover callback\n options?.onNodeHover?.(nodeId ? nodeDataById(nodeId) : null);\n\n // Show or hide tooltip\n if (nodeId && tooltipManager) {\n showNodeTooltip(nodeId);\n } else if (!nodeId) {\n // Tooltip hiding handled in onBackgroundHover (edge may show tooltip).\n tooltipManager?.hide();\n }\n },\n onBackgroundHover(graphX, graphY, screenX, screenY) {\n // A live node hover owns the tooltip; don't let edge hit-testing steal it.\n if (hoveredNodeId) return;\n // Throttle edge hit testing to avoid O(n) scan on every mousemove\n const now = performance.now();\n if (now - lastEdgeHitTime < 32) {\n // When throttled, clear edge hover so hover-off transitions stay snappy\n if (hoveredEdgeId) {\n hoveredEdgeId = null;\n needsRender = true;\n scheduleRender();\n options?.onEdgeHover?.(null);\n tooltipManager?.hide();\n }\n return;\n }\n lastEdgeHitTime = now;\n\n // Edge hit testing: check proximity to edge line segments\n const transform = interactionManager?.getTransform();\n const threshold = 5 / (transform?.k ?? 1); // 5px in screen space\n const edgeId = hitTestEdge(graphX, graphY, threshold);\n\n if (edgeId !== hoveredEdgeId) {\n hoveredEdgeId = edgeId;\n needsRender = true;\n scheduleRender();\n\n if (edgeId) {\n const data = edgeDataById(edgeId);\n options?.onEdgeHover?.(data);\n if (tooltipManager && data) showEdgeTooltip(edgeId, data, screenX, screenY);\n } else {\n options?.onEdgeHover?.(null);\n tooltipManager?.hide();\n }\n }\n },\n onSelectionChange(nodeIds) {\n selectedNodeIds = new Set(nodeIds);\n armFocus(performance.now());\n needsRender = true;\n scheduleRender();\n options?.onSelectionChange?.(nodeIds);\n\n // Fire onNodeClick for the most recently added node\n if (nodeIds.length > 0) {\n const lastId = nodeIds[nodeIds.length - 1];\n options?.onNodeClick?.(nodeDataById(lastId));\n }\n },\n onNodeDragStart(nodeId) {\n // Pin at the node's current position to avoid visual snap to origin\n const node = positionedNodes.find((n) => n.id === nodeId);\n const x = node?.x ?? 0;\n const y = node?.y ?? 0;\n // Springy: hold the sim warm (alphaTarget 0.3) so neighbors follow. Off\n // → legacy pin with no alphaTarget field (byte-identical message).\n simulation?.pinNode(nodeId, x, y, springyDragEnabled() ? 0.3 : undefined);\n canvas?.classList.add('oc-graph-canvas--dragging');\n },\n onNodeDrag(nodeId, x, y) {\n simulation?.dragNode(nodeId, x, y);\n },\n onNodeDragEnd(nodeId) {\n // Springy: cool the sim back down (alphaTarget 0). Off → legacy unpin\n // with no alphaTarget field (preserves the legacy reheat behavior).\n simulation?.unpinNode(nodeId, springyDragEnabled() ? 0 : undefined);\n canvas?.classList.remove('oc-graph-canvas--dragging');\n },\n onPointerMove(graphX, graphY) {\n if (!cursorForceEnabled()) return;\n // Throttle the pointer feed to ~30Hz so we don't post on every mousemove.\n const now = performance.now();\n if (now - lastPointerFeedTime < CURSOR_POINTER_THROTTLE_MS) return;\n lastPointerFeedTime = now;\n simulation?.setPointer(graphX, graphY, true);\n },\n onPointerLeave() {\n if (!cursorForceEnabled()) return;\n simulation?.setPointer(0, 0, false);\n },\n onDoubleClick(nodeId) {\n options?.onNodeDoubleClick?.(nodeDataById(nodeId));\n },\n });\n\n // Wire keyboard navigation\n cleanupKeyboard = attachGraphKeyboardNav({\n canvas,\n getNodes: () => positionedNodes,\n getSelectedIds: () => [...selectedNodeIds],\n getAdjacency: () => adjacencyMap,\n onSelect(nodeId) {\n selectedNodeIds = new Set([nodeId]);\n needsRender = true;\n scheduleRender();\n options?.onNodeClick?.(nodeDataById(nodeId));\n options?.onSelectionChange?.([nodeId]);\n },\n onDeselect() {\n selectedNodeIds.clear();\n needsRender = true;\n scheduleRender();\n options?.onSelectionChange?.([]);\n },\n onZoom(direction) {\n if (!interactionManager || !canvas) return;\n const t = interactionManager.getTransform();\n const { width: cw, height: ch } = getCanvasDimensions();\n const factor = direction === 'in' ? 1.2 : 0.8;\n const newK = t.k * factor;\n const newTransform = t.zoomAt(newK, cw / 2, ch / 2);\n flyCamera(newTransform, { duration: 200 });\n },\n onFitAll() {\n zoomToFit();\n },\n });\n\n // Handle node clicks (from interaction manager selection change wiring above)\n // We catch clicks via the interaction manager's onSelectionChange callback\n }\n\n // ---------------------------------------------------------------------------\n // Public API methods\n // ---------------------------------------------------------------------------\n\n function search(query: string): void {\n if (destroyed) return;\n searchManager.search(query, positionedNodes);\n needsRender = true;\n scheduleRender();\n }\n\n function clearSearch(): void {\n if (destroyed) return;\n searchManager.clearSearch();\n needsRender = true;\n scheduleRender();\n }\n\n function zoomToFit(opts?: CameraFlightOptions & { padding?: number }): void {\n if (destroyed || !interactionManager || positionedNodes.length === 0) return;\n const { width: cw, height: ch } = getCanvasDimensions();\n const { transform: fitTransform } = ZoomTransform.fitBounds(\n positionedNodes,\n cw,\n ch,\n opts?.padding,\n {\n insetTop: chromeInsetTop(),\n },\n );\n flyCamera(fitTransform, opts);\n }\n\n function zoomToNode(nodeId: string, opts?: CameraFlightOptions & { scale?: number }): void {\n if (destroyed || !interactionManager || !canvas) return;\n const node = positionedNodes.find((n) => n.id === nodeId);\n if (!node) return;\n\n const { width: cw, height: ch } = getCanvasDimensions();\n const k = clampK(opts?.scale ?? 2);\n // Provider form: re-read the node's live position each frame so the camera\n // tracks it while the simulation is still settling.\n const provider = (): ZoomTransform => {\n const live = positionedNodes.find((n) => n.id === nodeId) ?? node;\n return new ZoomTransform(cw / 2 - live.x * k, ch / 2 - live.y * k, k);\n };\n flyCamera(provider, opts);\n }\n\n // `target.position`/`target.target` are 3D-only pose fields; 2D ignores them\n // so a camera saved from a 3D mount still flies to a sane 2D point.\n function flyTo(target: GraphFlyTarget, opts?: CameraFlightOptions): void {\n if (destroyed || !interactionManager) return;\n const { width: cw, height: ch } = getCanvasDimensions();\n const k = clampK(target.k ?? interactionManager.getTransform().k);\n flyCamera(new ZoomTransform(cw / 2 - target.x * k, ch / 2 - target.y * k, k), opts);\n }\n\n function centerAt(x: number, y: number, opts?: CameraFlightOptions): void {\n flyTo({ x, y }, opts);\n }\n\n function getCamera(): GraphCamera {\n const t = interactionManager?.getTransform() ?? ZoomTransform.identity();\n return { x: t.x, y: t.y, k: t.k };\n }\n\n function selectNode(nodeId: string, opts?: { fly?: boolean } & CameraFlightOptions): void {\n if (destroyed) return;\n selectedNodeIds = new Set([nodeId]);\n needsRender = true;\n scheduleRender();\n options?.onSelectionChange?.([nodeId]);\n const shouldFly = opts?.fly ?? compilation.interaction.selectFlyTo;\n if (shouldFly) zoomToNode(nodeId, opts);\n }\n\n function getSelectedNodes(): string[] {\n return [...selectedNodeIds];\n }\n\n function getSearchMatches(): string[] {\n return [...(searchManager.getMatches() ?? [])];\n }\n\n // -------------------------------------------------------------------------\n // Highlight API (transient layer over the sticky category filter)\n // -------------------------------------------------------------------------\n\n /**\n * Emphasize a set of nodes on top of the standing category filter. Does not\n * touch the filter; legend `active` flags are unaffected. A target that\n * resolves to no nodes is a no-op layer: the standing filter keeps dimming.\n * The target is retained and re-resolved on `update()`, so a category-form\n * highlight tracks data changes.\n */\n function highlight(target: GraphHighlightTarget, opts?: { dimOpacity?: number }): void {\n if (destroyed) return;\n transientTarget = target;\n refreshTransientHighlight();\n highlightDimOpacity = opts?.dimOpacity ?? null;\n recomputeHighlight();\n refreshFocus();\n emitHighlightChange();\n }\n\n /**\n * Drop the transient highlight. The category filter stands — clearing that is\n * `setActiveCategories([])`.\n */\n function clearHighlight(): void {\n if (destroyed) return;\n transientTarget = null;\n transientHighlight = null;\n highlightDimOpacity = null;\n recomputeHighlight();\n refreshFocus();\n emitHighlightChange();\n }\n\n function getHighlight(): string[] | null {\n return highlightSet ? [...highlightSet] : null;\n }\n\n function getLegend(): GraphLegendData {\n const nodeEntries = 'entries' in compilation.legend ? compilation.legend.entries : [];\n return {\n field: compilation.legendField,\n nodes: nodeEntries\n .filter((e) => !e.overflow)\n .map((e) => ({\n label: e.label,\n color: e.color,\n count: e.count,\n active: activeCategories.size === 0 || activeCategories.has(e.label),\n })),\n edges: (compilation.edgeLegend ?? []).map((e) => ({\n label: e.label,\n color: e.color,\n count: e.count,\n })),\n };\n }\n\n /**\n * Set the sticky category filter (built-in legend + headless). A transient\n * highlight() layers over this rather than replacing it. Empty active set =\n * all categories shown (no filter, no dimming).\n */\n function setActiveCategories(values: string[]): void {\n if (destroyed) return;\n activeCategories = new Set(values);\n recomputeHighlight();\n syncLegendActiveState();\n refreshFocus();\n emitHighlightChange();\n }\n\n function getActiveCategories(): string[] {\n return [...activeCategories];\n }\n\n function toggleLegendCategory(value: string): void {\n if (activeCategories.has(value)) activeCategories.delete(value);\n else activeCategories.add(value);\n recomputeHighlight();\n syncLegendActiveState();\n refreshFocus();\n options?.onLegendToggle?.([...activeCategories]);\n emitHighlightChange();\n }\n\n function doResize(): void {\n if (destroyed || !canvas || !renderer || !shell) return;\n const { width, height } = shell.getSize();\n renderer.resize(width, height);\n // A width change can rewrap the title or move the legend; re-derive the\n // chrome/legend separation before any fit below measures the chrome band.\n syncChromeInset();\n\n // Mid-entrance: the in-flight camera fit targets the OLD viewport, so cancel\n // just that flight and snap to the new-viewport fit. The reveal tween (node\n // alpha/scale ramp) is orthogonal to the camera and keeps running.\n if (entranceFitInFlight && interactionManager) {\n cancelFlight();\n entranceFitInFlight = false;\n interactionManager.setTransform(computeInitialFit());\n cameraChangePending = true;\n }\n\n needsRender = true;\n scheduleRender();\n }\n\n /**\n * Unified data update.\n *\n * 1. Finish in-flight animations, then compile the new spec.\n * 2. Diff prev↔next. A `visualOnly` change (identical node AND edge id sets AND\n * equal simulationConfig) takes the position-preserving refresh — no sim\n * restart. Anything else (added/removed marks, or a physics change) is a\n * structural update.\n * 3. Structural update: tear down the SIM ONLY (interaction manager, camera\n * transform, and surviving selection are kept). Survivors keep their prior\n * x/y; enterers get spawn positions. A new sim is created with the center\n * force suppressed and a low reheat alpha, so survivors barely move and\n * enterers locally settle. Enter fades and exit ghosts animate the delta.\n */\n function update(newSpec: GraphSpec): void {\n if (destroyed) return;\n\n // Recompile first: a dimension change has no path back through the shell\n // (the renderer is chosen once, at mount), so it is a remount, not an\n // update. Bail before touching any state so the instance stays intact.\n const nextCompilation = compile(newSpec);\n if (nextCompilation.numDimensions !== compilation.numDimensions) {\n warnOnce('createGraph: update() cannot change dimensions; remount the graph');\n return;\n }\n\n currentSpec = newSpec;\n\n // Finish any in-flight animations (e.g. an entrance reveal or a prior\n // update's enter/exit fade) so they snap to their final state and fire\n // onDone rather than being hard-cancelled mid-flight.\n scheduler.finishAll();\n entranceActive = false;\n entranceProgress = 1;\n entranceFitInFlight = false;\n entranceReveal = null;\n // Clear any lingering update-transition state (finishAll ran their onDone).\n enterAlphaMap = null;\n exitingGhosts = null;\n\n // Capture prev state BEFORE recompiling.\n const prevNodes = positionedNodes;\n const prevEdges = positionedEdges;\n const prevConfig = compilation.simulationConfig;\n\n compilation = nextCompilation;\n seedIds = new Set(compilation.seedNodeIds);\n\n const diff = diffGraphUpdate(\n prevNodes,\n prevEdges,\n compilation,\n prevConfig,\n prevConfig.seed ?? 0,\n );\n\n if (diff.visualOnly) {\n runVisualOnlyUpdate();\n return;\n }\n\n runStructuralUpdate(diff);\n }\n\n /**\n * Position-preserving visual refresh: recompile changed encoding/chrome/legend\n * and transfer existing node positions. No simulation restart. (Assumes the\n * new `compilation` is already set and node/edge id sets are unchanged.)\n */\n function runVisualOnlyUpdate(): void {\n adjacencyMap = buildAdjacencyMap(compilation.edges);\n buildDataMaps();\n\n // Build a position lookup from the current positioned nodes.\n const posMap = new Map<string, { x: number; y: number }>();\n for (const node of positionedNodes) {\n posMap.set(node.id, { x: node.x, y: node.y });\n }\n\n // Transfer positions to the newly compiled nodes.\n positionedNodes = compilation.nodes.map((node, index) => {\n const pos = posMap.get(node.id) ?? { x: 0, y: 0 };\n return { ...node, x: pos.x, y: pos.y, index };\n });\n\n positionedEdges = compilation.edges.map((edge) => {\n const src = posMap.get(edge.source) ?? { x: 0, y: 0 };\n const tgt = posMap.get(edge.target) ?? { x: 0, y: 0 };\n return { ...edge, sourceX: src.x, sourceY: src.y, targetX: tgt.x, targetY: tgt.y };\n });\n\n spatialIndex.rebuild(positionedNodes);\n\n // Highlight persists: re-resolve both layers against the new nodes. Node\n // ids are identical on a visual-only update, but a category-form transient\n // can still change membership if the underlying field values changed.\n refreshTransientHighlight();\n recomputeHighlight();\n\n // Search survives: re-run the stored query against the new nodes.\n reRunSearch();\n\n renderChrome();\n renderLegend();\n syncLegendActiveState();\n\n needsRender = true;\n scheduleRender();\n }\n\n /**\n * Structural update: nodes/edges added/removed or physics changed. Tears down\n * only the simulation, recreates it with survivor/spawn positions and a local\n * reheat, and animates the enter/exit delta.\n */\n function runStructuralUpdate(diff: ReturnType<typeof diffGraphUpdate>): void {\n // Tear down the SIM ONLY. Interaction manager, camera transform, and\n // selection all survive (the plan's key divergence from the old full update).\n teardownSimOnly();\n\n adjacencyMap = buildAdjacencyMap(compilation.edges);\n buildDataMaps();\n\n // Known positions for the new sim: survivors keep prior x/y, enterers spawn.\n const positions = new Map<string, { x: number; y: number }>();\n for (const [id, p] of diff.survivingPositions) positions.set(id, p);\n for (const [id, p] of diff.spawnPositions) positions.set(id, p);\n\n // changeRatio = max(node churn, edge churn). Node churn is\n // (|entering| + |exiting nodes|) / max(prevNodeCount, nextNodeCount); the\n // edge analog uses the same shape. Low alpha IS the local reheat.\n const prevNodeCount = diff.survivingPositions.size + diff.exitingNodes.length;\n const nextNodeCount = diff.survivingPositions.size + diff.enteringIds.length;\n const nodeRatio = ratio(\n diff.enteringIds.length + diff.exitingNodes.length,\n Math.max(prevNodeCount, nextNodeCount),\n );\n const prevEdgeCount =\n compilation.edges.length - diff.enteringEdgeCount + diff.exitingEdges.length;\n const nextEdgeCount = compilation.edges.length;\n const edgeRatio = ratio(\n diff.enteringEdgeCount + diff.exitingEdges.length,\n Math.max(prevEdgeCount, nextEdgeCount),\n );\n const changeRatio = Math.max(nodeRatio, edgeRatio);\n const initialAlpha = Math.min(1, 0.3 + 0.7 * changeRatio);\n\n // Seed positioned nodes/edges immediately so the first frame (before the sim\n // streams its first tick) draws survivors at their prior spots and enterers\n // at their spawn spots — no flash at the origin.\n positionedNodes = compilation.nodes.map((node, index) => {\n const pos = positions.get(node.id) ?? { x: 0, y: 0 };\n return { ...node, x: pos.x, y: pos.y, index };\n });\n positionedEdges = compilation.edges.map((edge) => {\n const src = positions.get(edge.source) ?? { x: 0, y: 0 };\n const tgt = positions.get(edge.target) ?? { x: 0, y: 0 };\n return { ...edge, sourceX: src.x, sourceY: src.y, targetX: tgt.x, targetY: tgt.y };\n });\n spatialIndex.rebuild(positionedNodes);\n\n initSimulation({\n positions,\n suppressCenter: true,\n initialAlpha,\n skipWarmup: true,\n skipEntrance: true,\n });\n\n // Update DOM chrome/legend for the new graph.\n renderChrome();\n renderLegend();\n\n // Reconcile interaction/highlight/search state against the new node set.\n reconcileStateAfterUpdate();\n\n // Wire the enter-fade and exit-ghost transitions.\n startUpdateTransitions(diff);\n\n needsRender = true;\n scheduleRender();\n }\n\n /** Safe ratio (0 when the denominator is 0). */\n function ratio(numerator: number, denominator: number): number {\n return denominator > 0 ? numerator / denominator : 0;\n }\n\n /**\n * Prune stale interaction state after a structural update: drop hovered\n * node/edge ids that no longer exist, intersect the selection with the new\n * node set (pushing the pruned set into the interaction manager so a later\n * shift-click can't resurrect deleted ids), re-resolve highlight, and re-run\n * any active search.\n */\n function reconcileStateAfterUpdate(): void {\n const nextIds = new Set(compilation.nodes.map((n) => n.id));\n\n // Hovered node: clear if gone.\n if (hoveredNodeId && !nextIds.has(hoveredNodeId)) hoveredNodeId = null;\n // Hovered edge: clear if either endpoint is gone.\n if (hoveredEdgeId) {\n const [src, tgt] = hoveredEdgeId.split('->');\n if (!nextIds.has(src) || !nextIds.has(tgt)) {\n hoveredEdgeId = null;\n // A listener that opened something on hover would otherwise never get\n // the close.\n options?.onEdgeHover?.(null);\n }\n }\n\n // Selection: intersect with survivors, then push into the interaction manager.\n const survivingSelection = [...selectedNodeIds].filter((id) => nextIds.has(id));\n selectedNodeIds = new Set(survivingSelection);\n interactionManager?.setSelection(survivingSelection);\n\n // Highlight persists. Re-resolve the transient layer against the new nodes\n // first (this both tracks category membership changes and drops deleted\n // ids, which an unpruned set would resurrect), then recompose.\n refreshTransientHighlight();\n recomputeHighlight();\n syncLegendActiveState();\n\n // Search survives: re-run the stored query against the new nodes.\n reRunSearch();\n\n // Re-arm the focus crossfade against the reconciled state (a fresh transition\n // so it doesn't blend from stale prev/next snapshots).\n focusTransition = null;\n armFocus(performance.now());\n }\n\n /** Re-run the active search query (if any) against the current positioned nodes. */\n function reRunSearch(): void {\n const q = searchManager.getQuery();\n if (q !== null) searchManager.search(q, positionedNodes);\n }\n\n /**\n * Start the enter-fade (new nodes 0→1 over update.duration, quantized for\n * batching) and the exit-ghost fade (removed marks 1→0 over exit.duration).\n * Snaps instantly under reduced motion or when the phase is disabled.\n */\n function startUpdateTransitions(diff: ReturnType<typeof diffGraphUpdate>): void {\n const updateCfg = compilation.animation?.update ?? null;\n const exitCfg = compilation.animation?.exit ?? null;\n const reduced = prefersReducedMotion();\n\n // -- Enter fade --\n if (diff.enteringIds.length > 0 && updateCfg && !reduced) {\n const entering = diff.enteringIds;\n enterAlphaMap = new Map(entering.map((id) => [id, 0]));\n const ease = resolveEase(updateCfg.ease);\n const tween = createTween({\n duration: updateCfg.duration,\n ease,\n apply: (t) => {\n // Quantize to 8 buckets so per-node alpha keeps fill-batching bounded.\n const q = Math.round(t * 8) / 8;\n if (enterAlphaMap) for (const id of entering) enterAlphaMap.set(id, q);\n needsRender = true;\n },\n onDone: () => {\n enterAlphaMap = null;\n needsRender = true;\n },\n });\n scheduler.add(tween);\n }\n\n // -- Exit ghosts --\n if ((diff.exitingNodes.length > 0 || diff.exitingEdges.length > 0) && exitCfg && !reduced) {\n exitingGhosts = { nodes: diff.exitingNodes, edges: diff.exitingEdges, alpha: 1 };\n const ease = resolveEase(exitCfg.ease);\n const tween = createTween({\n duration: exitCfg.duration,\n ease,\n apply: (t) => {\n if (exitingGhosts) exitingGhosts.alpha = 1 - t;\n needsRender = true;\n },\n onDone: () => {\n exitingGhosts = null;\n needsRender = true;\n },\n });\n scheduler.add(tween);\n }\n }\n\n /**\n * @deprecated Use {@link GraphInstance.update} instead. `update` now handles\n * both visual-only and structural changes (diffed automatically) and preserves\n * node positions when nothing structural changed. Kept as an alias for backward\n * compatibility.\n */\n function updateVisuals(newSpec: GraphSpec): void {\n update(newSpec);\n }\n\n /** Tear down ONLY the simulation, keeping interaction/transform/selection. */\n function teardownSimOnly(): void {\n simulation?.destroy();\n simulation = null;\n }\n\n function teardownSubsystems(): void {\n // Cancel animations BEFORE tearing down the sim/DOM, so no in-flight tick\n // writes to removed state.\n scheduler.cancelAll();\n activeFlight = null;\n activeFollow = null;\n // Reset entrance state so a remount (React StrictMode) starts clean.\n entranceReveal = null;\n entranceActive = false;\n entranceProgress = 1;\n entranceFitInFlight = false;\n // Reset update-transition state too (cancelAll already stopped the tweens).\n enterAlphaMap = null;\n exitingGhosts = null;\n if (animFrameId !== null) {\n cancelAnimationFrame(animFrameId);\n animFrameId = null;\n }\n if (cleanupKeyboard) {\n cleanupKeyboard();\n cleanupKeyboard = null;\n }\n interactionManager?.destroy();\n interactionManager = null;\n simulation?.destroy();\n simulation = null;\n }\n\n function destroy(): void {\n if (destroyed) return;\n destroyed = true;\n\n if (gestureTimeout !== null) {\n clearTimeout(gestureTimeout);\n gestureTimeout = null;\n }\n\n teardownSubsystems();\n\n if (disconnectResize) {\n disconnectResize();\n disconnectResize = null;\n }\n\n legendController?.destroy();\n legendController = null;\n\n shell?.destroy();\n shell = null;\n tooltipManager = null;\n canvas = null;\n chromeEl = null;\n legendEl = null;\n renderer = null;\n }\n\n // ---------------------------------------------------------------------------\n // Initialize\n // ---------------------------------------------------------------------------\n\n try {\n compilation = compile();\n shell = createGraphShell(container, currentSpec, compilation, options, warnOnce);\n shell.renderChrome(compilation);\n } catch (err) {\n // Same failure semantics as createChart and createSankey: a spec that\n // cannot compile or mount is a caller bug, and a throw is the only signal\n // that reaches them. (A silent no-op instance used to be returned here.)\n console.error('[viz] Graph mount failed:', err);\n throw err;\n }\n\n // 3D hands off to the registered WebGL renderer. The subpath registers itself\n // as an import side effect; createGraph never dynamically imports it, because\n // mount is synchronous and every framework wrapper assumes it is.\n if (compilation.numDimensions === 3) {\n const factory = getGraphRenderer(3);\n if (!factory) {\n // Leave no half-built DOM behind for a caller who forgot the import.\n shell.destroy();\n shell = null;\n throw new Error(GRAPH_3D_NOT_REGISTERED_ERROR);\n }\n const ctx = {\n shell,\n spec: currentSpec,\n compilation,\n options,\n compile: (next: GraphSpec) => compile(next),\n };\n try {\n return factory(ctx);\n } catch (err) {\n shell.destroy();\n shell = null;\n console.error('[viz] Graph mount failed:', err);\n throw err;\n }\n }\n\n chromeEl = shell.chromeEl;\n legendEl = shell.legendEl;\n tooltipManager = shell.tooltipManager;\n\n try {\n seedIds = new Set(compilation.seedNodeIds);\n adjacencyMap = buildAdjacencyMap(compilation.edges);\n buildDataMaps();\n applyInitialHighlight();\n createSurface();\n initSimulation();\n initInteraction();\n } catch (err) {\n console.error('[viz] Graph mount failed:', err);\n throw err;\n }\n\n // Responsive resize (the shell no-ops when `responsive: false`).\n disconnectResize = shell.observeResize(() => {\n doResize();\n });\n\n return {\n update,\n updateVisuals,\n search,\n clearSearch,\n zoomToFit,\n zoomToNode,\n flyTo,\n centerAt,\n getCamera,\n selectNode,\n getSelectedNodes,\n getSearchMatches,\n highlight,\n clearHighlight,\n getHighlight,\n getLegend,\n setActiveCategories,\n getActiveCategories,\n resize: doResize,\n destroy,\n };\n}\n","/**\n * Animated camera for the graph.\n *\n * Flights interpolate between two `ZoomTransform`s along d3's geodesic\n * `interpolateZoom` path (smooth zoom-and-pan that pulls back to see both\n * endpoints before diving in). A flight is a {@link GraphAnimation}, driven by\n * the mount's AnimationScheduler.\n *\n * `interpolateZoom` operates on a `[cx, cy, width]` \"view\" — the graph-space\n * center and the graph-space width visible across the viewport. We convert the\n * ZoomTransform ⇄ view around a fixed viewport size.\n */\n\nimport type { AnimationEase } from '@opendata-ai/openchart-core';\nimport { interpolateZoom } from 'd3-interpolate';\nimport { resolveEase } from './motion';\nimport type { GraphAnimation } from './scheduler';\nimport { ZoomTransform } from './zoom';\n\n/** Minimum/maximum zoom scale a flight may resolve to. */\nconst K_MIN = 0.05;\nconst K_MAX = 15;\n\n/** Auto-duration bounds (ms) and the interpolateZoom.duration scale factor. */\nconst AUTO_MIN_MS = 400;\nconst AUTO_MAX_MS = 600;\nconst AUTO_SCALE = 0.6;\n\n/** A d3 zoom \"view\": graph-space center x/y and the graph-space width in view. */\nexport type ZoomView = [number, number, number];\n\n/** Viewport size a flight is framed against. */\nexport interface Viewport {\n width: number;\n height: number;\n}\n\n/**\n * Convert a ZoomTransform to a d3 zoom view against a viewport.\n *\n * The view width is the graph-space span visible across `viewport.width`\n * (= viewport.width / k); the center is the graph point under the viewport\n * center.\n */\nexport function transformToView(t: ZoomTransform, viewport: Viewport): ZoomView {\n const cx = (viewport.width / 2 - t.x) / t.k;\n const cy = (viewport.height / 2 - t.y) / t.k;\n const width = viewport.width / t.k;\n return [cx, cy, width];\n}\n\n/** Inverse of {@link transformToView}: a zoom view back to a ZoomTransform. */\nexport function viewToTransform(view: ZoomView, viewport: Viewport): ZoomTransform {\n const [cx, cy, width] = view;\n const k = clampK(viewport.width / width);\n const x = viewport.width / 2 - cx * k;\n const y = viewport.height / 2 - cy * k;\n return new ZoomTransform(x, y, k);\n}\n\n/** Clamp a zoom scale into the allowed range. */\nexport function clampK(k: number): number {\n if (!Number.isFinite(k) || k <= 0) return K_MIN;\n return Math.min(K_MAX, Math.max(K_MIN, k));\n}\n\n/** Options controlling a camera flight. */\nexport interface CameraFlightOptions {\n /** Duration in ms, or `'auto'` to derive from the zoom distance. */\n duration?: number | 'auto';\n /** Easing preset. Default `'smooth'`. */\n ease?: AnimationEase;\n}\n\n/** Inputs to {@link createCameraFlight}. */\nexport interface CameraFlightInputs {\n /** Starting transform. */\n from: ZoomTransform;\n /**\n * Target transform, or a provider that returns the current target each frame\n * (used to track a node that is still settling). When a provider is passed,\n * the interpolator is rebuilt each frame from the ORIGINAL `from` to the\n * current target, so a moving target stays smooth.\n */\n to: ZoomTransform | (() => ZoomTransform);\n /** Viewport the flight is framed against. */\n viewport: Viewport;\n /** Applies an interpolated transform to the scene each frame. */\n apply: (t: ZoomTransform) => void;\n /** Fired once on natural completion (not on cancel). */\n onDone?: () => void;\n /** Flight options. */\n opts?: CameraFlightOptions;\n}\n\n/**\n * Create a camera flight as a {@link GraphAnimation}.\n *\n * Degenerate from≈to is guarded: duration is forced to at least 1ms and NaN\n * interpolation results fall back to the target. The start time locks on the\n * first tick for deterministic tests.\n */\nexport function createCameraFlight(inputs: CameraFlightInputs): GraphAnimation {\n const { from, viewport, apply, onDone, opts } = inputs;\n const ease = resolveEase(opts?.ease ?? 'smooth');\n const isProvider = typeof inputs.to === 'function';\n\n const fromView = transformToView(from, viewport);\n\n function targetView(): ZoomView {\n const to = isProvider ? (inputs.to as () => ZoomTransform)() : (inputs.to as ZoomTransform);\n return transformToView(to, viewport);\n }\n\n // Build the interpolator (rebuilt each frame in provider mode).\n let interp = interpolateZoom(fromView, targetView());\n const resolvedDuration = resolveDuration(opts?.duration, interp.duration);\n\n let startTime: number | null = null;\n let finished = false;\n\n function applyAt(t: number): void {\n if (isProvider) interp = interpolateZoom(fromView, targetView());\n const view = interp(t) as ZoomView;\n if (view.some((v) => !Number.isFinite(v))) {\n apply(viewToTransform(targetView(), viewport));\n return;\n }\n apply(viewToTransform(view, viewport));\n }\n\n return {\n tick(now: number): boolean {\n if (finished) return false;\n if (startTime === null) startTime = now;\n const raw = resolvedDuration <= 0 ? 1 : Math.min(1, (now - startTime) / resolvedDuration);\n applyAt(ease(raw));\n if (raw >= 1) {\n finished = true;\n onDone?.();\n return false;\n }\n return true;\n },\n finish(): void {\n if (finished) return;\n finished = true;\n applyAt(1);\n onDone?.();\n },\n cancel(): void {\n finished = true;\n },\n };\n}\n\n/** Inputs to {@link createCameraFollow}. */\nexport interface CameraFollowInputs {\n /** Provider returning the current target transform each frame. */\n target: () => ZoomTransform;\n /** Applies the followed transform to the scene each frame. */\n apply: (t: ZoomTransform) => void;\n /** True while the tracked target may still move (e.g. sim alpha ≥ threshold). */\n isActive: () => boolean;\n}\n\n/**\n * Post-flight follow for provider-form flights: the flight converges at t=1\n * while the tracked node may still be settling, so this cheap animation snaps\n * the camera to the provider each frame until `isActive()` reports the sim has\n * settled. User input or a new flight cancels it via the scheduler.\n */\nexport function createCameraFollow(inputs: CameraFollowInputs): GraphAnimation {\n let finished = false;\n return {\n tick(): boolean {\n if (finished) return false;\n if (!inputs.isActive()) {\n finished = true;\n return false;\n }\n inputs.apply(inputs.target());\n return true;\n },\n finish(): void {\n finished = true;\n },\n cancel(): void {\n finished = true;\n },\n };\n}\n\n/**\n * Resolve a flight duration. `'auto'` (or undefined) derives from d3's\n * interpolateZoom.duration (a perceptual distance), scaled and clamped. An\n * explicit number is honored but floored at 1ms so degenerate flights still tick.\n */\nexport function resolveDuration(\n duration: number | 'auto' | undefined,\n interpDuration: number,\n): number {\n if (typeof duration === 'number') return Math.max(1, duration);\n const scaled = interpDuration * AUTO_SCALE;\n return Math.max(AUTO_MIN_MS, Math.min(AUTO_MAX_MS, Math.max(1, scaled)));\n}\n","/**\n * Immutable zoom transform for graph pan/zoom.\n *\n * Provides coordinate conversion between screen space (canvas pixels)\n * and graph space (simulation coordinates). All mutations return new\n * instances rather than modifying in place.\n */\n\nimport type { PositionedNode } from './types';\n\nexport class ZoomTransform {\n constructor(\n readonly x: number,\n readonly y: number,\n readonly k: number,\n ) {}\n\n /** Convert screen coordinates to graph coordinates. */\n screenToGraph(sx: number, sy: number): { x: number; y: number } {\n return {\n x: (sx - this.x) / this.k,\n y: (sy - this.y) / this.k,\n };\n }\n\n /** Convert graph coordinates to screen coordinates. */\n graphToScreen(gx: number, gy: number): { x: number; y: number } {\n return {\n x: gx * this.k + this.x,\n y: gy * this.k + this.y,\n };\n }\n\n /**\n * Zoom to a target scale, keeping the given screen-space pivot\n * point fixed (content under the cursor stays under the cursor).\n */\n zoomAt(targetK: number, pivotX: number, pivotY: number): ZoomTransform {\n // The graph point under the pivot should remain at the same screen position.\n // Before: pivotX = gx * k + x => gx = (pivotX - x) / k\n // After: pivotX = gx * targetK + newX => newX = pivotX - gx * targetK\n const gx = (pivotX - this.x) / this.k;\n const gy = (pivotY - this.y) / this.k;\n return new ZoomTransform(pivotX - gx * targetK, pivotY - gy * targetK, targetK);\n }\n\n /** Pan by a screen-space delta. */\n pan(dx: number, dy: number): ZoomTransform {\n return new ZoomTransform(this.x + dx, this.y + dy, this.k);\n }\n\n /**\n * Compute a transform that fits all nodes within the given canvas\n * dimensions with the specified padding.\n *\n * Returns the transform and the ideal content height (in screen pixels)\n * so callers can shrink the canvas to eliminate dead space.\n */\n static fitBounds(\n nodes: PositionedNode[],\n canvasW: number,\n canvasH: number,\n padding: number = 40,\n opts?: { spread?: boolean; insetTop?: number },\n ): { transform: ZoomTransform; contentHeight: number } {\n if (nodes.length === 0) {\n return { transform: ZoomTransform.identity(), contentHeight: canvasH };\n }\n\n // Reserved band at the top of the canvas (the HTML chrome overlay). The fit\n // centers within the remaining area so nodes don't sit under the title.\n // Clamped so a degenerate measurement can't consume the whole viewport.\n const insetTop = Math.min(Math.max(0, opts?.insetTop ?? 0), canvasH * 0.4);\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n for (const n of nodes) {\n const r = n.radius;\n if (n.x - r < minX) minX = n.x - r;\n if (n.y - r < minY) minY = n.y - r;\n if (n.x + r > maxX) maxX = n.x + r;\n if (n.y + r > maxY) maxY = n.y + r;\n }\n\n let graphW = maxX - minX;\n let graphH = maxY - minY;\n\n if (graphW === 0 && graphH === 0) {\n // All nodes at the same point; center within the area below the inset\n return {\n transform: new ZoomTransform(\n canvasW / 2 - minX,\n insetTop + (canvasH - insetTop) / 2 - minY,\n 1,\n ),\n contentHeight: padding * 2 + insetTop,\n };\n }\n\n // When called early in the simulation (first tick), the bounding box\n // underestimates the final spread. Apply a spread multiplier based on\n // node count: larger graphs expand more as charge forces push nodes\n // apart over subsequent ticks. The sqrt scaling mirrors how d3-force\n // charge repulsion grows with node count.\n //\n // Warmup (Phase 6) settles the layout headlessly before the first fit, so\n // the bounds are already near-final; inflating them then fits at roughly\n // half the correct zoom at 10k nodes. Callers pass `spread: false` after a\n // warmup ran to skip the inflation. Default true = the original behavior.\n if (opts?.spread !== false && nodes.length > 50) {\n const spread = 1 + Math.sqrt(nodes.length) / 120;\n const cx = (minX + maxX) / 2;\n const cy = (minY + maxY) / 2;\n graphW *= spread;\n graphH *= spread;\n minX = cx - graphW / 2;\n maxX = cx + graphW / 2;\n minY = cy - graphH / 2;\n maxY = cy + graphH / 2;\n }\n\n const availW = canvasW - padding * 2;\n const availH = canvasH - insetTop - padding * 2;\n // Cap at 1 so the graph never renders larger than its natural size\n const k = Math.min(1, availW / graphW, availH / graphH);\n\n // Center horizontally; center vertically within the area below the inset.\n const cx = (minX + maxX) / 2;\n const cy = (minY + maxY) / 2;\n const tx = canvasW / 2 - cx * k;\n const ty = insetTop + (canvasH - insetTop) / 2 - cy * k;\n\n // Content height = scaled graph extent + top and bottom padding + inset\n const contentHeight = graphH * k + padding * 2 + insetTop;\n\n return {\n transform: new ZoomTransform(tx, ty, k),\n contentHeight,\n };\n }\n\n /** Identity transform (no pan, no zoom). */\n static identity(): ZoomTransform {\n return new ZoomTransform(0, 0, 1);\n }\n}\n","/**\n * Canvas 2D renderer for force-directed graph visualization.\n *\n * Stateless renderer: receives a GraphRenderState each frame and draws it.\n * Handles DPR scaling, viewport culling, LOD labels, dark mode glow effects,\n * and batched drawing for performance at 10k+ nodes.\n *\n * Performance strategy:\n * - Edges batched by (stroke, strokeWidth, dash) key → one stroke() per group\n * - Nodes batched by fill color → one fill() per color group\n * - Node strokes batched by stroke color\n * - Labels and glow skipped during active pan/zoom gestures\n */\n\nimport { BRAND_FONT_SIZE, BRAND_MIN_WIDTH } from '@opendata-ai/openchart-core';\nimport { driftFactor, nodeEnterProgress, popAlpha, popScale } from './entrance';\nimport type { FocusSnapshot } from './focus-transition';\nimport type { GraphRenderState, PositionedEdge, PositionedNode } from './types';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst LABEL_FONT_MIN = 9;\nconst LABEL_FONT_MAX = 12;\n/**\n * Resting edge alpha, per mode. Edges are structure, not data: they sit under\n * the label layer, so dark canvases take the quieter value (light strokes gain\n * apparent weight against a dark ground).\n */\nconst EDGE_ALPHA_DEFAULT_LIGHT = 0.3;\nconst EDGE_ALPHA_DEFAULT_DARK = 0.25;\nconst EDGE_ALPHA_CONNECTED = 1.0;\nconst SEARCH_NON_MATCH_ALPHA = 0.25;\n/** Default node dim tier — the ratio the edge dim tier derives against. */\nconst DEFAULT_DIM_OPACITY = 0.3;\n/** Maximum labels drawn at once, before the declutter pass. */\nconst LABEL_BUDGET_MIN = 12;\nconst LABEL_BUDGET_MAX = 80;\nconst LABEL_BUDGET_PER_ZOOM = 30;\n/** Above this visible-edge count a focus crossfade snaps (no per-frame blend). */\nconst CROSSFADE_MAX_EDGES = 20000;\n\n/**\n * The three emphasis tiers an edge/node can fall into for a given focus state.\n * `default` is the resting alpha (nothing emphasized); `connected` is fully lit;\n * `dimmed` is de-emphasized while something else is in focus.\n */\ntype FocusTier = 'default' | 'connected' | 'dimmed';\n\n/** Classify an edge under a focus snapshot into its emphasis tier. */\nfunction edgeTier(edge: PositionedEdge, focus: FocusSnapshot): FocusTier {\n if (!focus.hasActive) return 'default';\n return focus.connected.has(edge.source) && focus.connected.has(edge.target)\n ? 'connected'\n : 'dimmed';\n}\n\n/**\n * Classify a node under a focus snapshot into its emphasis tier.\n *\n * Ids in `exemptIds` (the graph's seed node) never dim: they classify as\n * `connected` under any focus state — highlight, category filter, hover, or\n * selection. (Search dimming is a separate alpha multiplier keyed off\n * `searchMatches` and is deliberately not exempted: a seed that doesn't match\n * the query shouldn't pretend to.) The exemption lives here rather than in the highlight set on\n * purpose -- `composeStandingFocus` expands the core set to\n * `core ∪ neighbors(core)`, and a seed is by construction a hub, so unioning it\n * into the highlight would light most of the graph and defeat the category\n * filter. `edgeTier` is deliberately NOT exempted: the seed stays lit while its\n * edges dim with everything else, which is exactly \"lit without lighting its\n * neighborhood\". Exempt nodes land in the existing connected-alpha bucket, so\n * the node fill/stroke batching keys are unaffected.\n */\nfunction nodeTier(\n node: PositionedNode,\n focus: FocusSnapshot,\n exemptIds: Set<string> | undefined,\n): FocusTier {\n if (!focus.hasActive) return 'default';\n if (exemptIds?.has(node.id)) return 'connected';\n return focus.connected.has(node.id) ? 'connected' : 'dimmed';\n}\n\n/**\n * Resolve a tier to its edge alpha. The dimmed tier derives from the node dim\n * knob (`dimOpacity / 3`), preserving the deliberate node-to-edge dim ratio\n * that keeps dense hairballs quiet during hover.\n */\nfunction edgeTierAlpha(tier: FocusTier, dimOpacity: number, defaultAlpha: number): number {\n switch (tier) {\n case 'connected':\n return EDGE_ALPHA_CONNECTED;\n case 'dimmed':\n return dimOpacity / 3;\n default:\n return defaultAlpha;\n }\n}\n\n/** Resolve a tier to its node alpha. The dimmed tier is the raw dim knob. */\nfunction nodeTierAlpha(tier: FocusTier, dimOpacity: number): number {\n switch (tier) {\n case 'dimmed':\n return dimOpacity;\n default:\n return 1;\n }\n}\n\nfunction lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n}\n\n/**\n * Per-frame entrance reveal helper, built from the mount's `entrance` state.\n *\n * Staggered (≤ ENTRANCE_STAGGER_MAX_NODES): each node POPS — scale runs 0→~1.1→1\n * (back-out overshoot), alpha 0→1 over the first 60% of its window, and the node\n * converges from a small centroid-outward drift offset to its final position.\n * `nodeT` is a per-node staggered+quantized progress ordered by `entrance.order`\n * (hash-scattered rank), so nodes reveal at independent, scattered times.\n *\n * Unstaggered (large graphs): the legacy single global fade — alpha and scale\n * both ramp `0.6 + 0.4·g`, no drift.\n *\n * Edges lag 30% behind the global progress; labels fade with the raw global\n * progress. `total` is fixed at build time so the stagger window is stable.\n */\ninterface EntranceReveal {\n nodeAlpha(node: PositionedNode): number;\n nodeScale(node: PositionedNode): number;\n /** Drift offset for a node id at the current frame (graph-space px). */\n shift(id: string): { x: number; y: number };\n edgeAlpha: number;\n labelAlpha: number;\n}\n\nconst ZERO_SHIFT = { x: 0, y: 0 };\n\nfunction makeEntranceReveal(\n entrance: GraphRenderState['entrance'] & { t: number },\n total: number,\n): EntranceReveal {\n const g = entrance.t;\n const { stagger, order, offsets } = entrance;\n const rankOf = (node: PositionedNode) => order?.get(node.id) ?? node.index;\n const nodeT = (node: PositionedNode) => (stagger ? nodeEnterProgress(g, rankOf(node), total) : g);\n const edgeAlpha = Math.max(0, (g - 0.3) / 0.7); // lag 30%\n const globalRamp = 0.6 + 0.4 * g;\n return {\n nodeAlpha: (node) => (stagger ? popAlpha(nodeT(node)) : globalRamp),\n nodeScale: (node) => (stagger ? popScale(nodeT(node)) : globalRamp),\n shift: (id) => {\n if (!stagger || !offsets || !order) return ZERO_SHIFT;\n const off = offsets.get(id);\n if (!off) return ZERO_SHIFT;\n const f = driftFactor(nodeEnterProgress(g, order.get(id) ?? 0, total));\n return f > 0 ? { x: off.x * f, y: off.y * f } : ZERO_SHIFT;\n },\n edgeAlpha,\n labelAlpha: g,\n };\n}\n\n/**\n * Derive a focus snapshot from hovered/selected nodes when the mount doesn't\n * supply an explicit crossfade. Preserves the legacy \"hover/select dims the\n * rest\" behavior for callers (and tests) that pass raw render state.\n */\nfunction deriveFocus(\n hoveredNodeId: string | null,\n selectedNodeIds: Set<string>,\n adjacencyMap: Map<string, Set<string>>,\n): FocusSnapshot {\n const hasActive = hoveredNodeId !== null || selectedNodeIds.size > 0;\n const connected = new Set<string>();\n if (hasActive) {\n const active = new Set<string>();\n if (hoveredNodeId) active.add(hoveredNodeId);\n for (const id of selectedNodeIds) active.add(id);\n for (const id of active) {\n connected.add(id);\n const neighbors = adjacencyMap.get(id);\n if (neighbors) for (const nid of neighbors) connected.add(nid);\n }\n }\n return { hasActive, connected, searchMatches: null, selected: selectedNodeIds };\n}\nconst GLOW_NODE_THRESHOLD = 2000;\nconst GLOW_RADIUS_MULTIPLIER = 1.3;\nconst GLOW_ALPHA = 0.1;\nconst CULL_MARGIN = 50;\nconst TWO_PI = Math.PI * 2;\n\n/** Minimum node radius in screen pixels. Keeps nodes visible when zoomed out. */\nconst MIN_SCREEN_RADIUS = 2.5;\n\n// ---------------------------------------------------------------------------\n// Helpers (exported for testing)\n// ---------------------------------------------------------------------------\n\n/**\n * How many non-forced labels may be drawn at the current zoom.\n *\n * A budget, not a priority threshold: a threshold either shows every node above\n * a cutoff (a wall of text on a dense graph) or nothing at all. The budget draws\n * the top-priority labels that still fit, and the declutter pass in `drawLabels`\n * drops the ones that would collide.\n */\nexport function labelBudget(zoom: number): number {\n const raw = Math.round(zoom * LABEL_BUDGET_PER_ZOOM);\n return Math.max(LABEL_BUDGET_MIN, Math.min(LABEL_BUDGET_MAX, raw));\n}\n\n/** Axis-aligned box used by the label declutter pass. */\ninterface LabelBox {\n x0: number;\n y0: number;\n x1: number;\n y1: number;\n}\n\nfunction boxesOverlap(a: LabelBox, b: LabelBox): boolean {\n return a.x0 < b.x1 && b.x0 < a.x1 && a.y0 < b.y1 && b.y0 < a.y1;\n}\n\n/** Compute visible rect in graph coordinates from canvas size + transform. */\nexport function visibleRect(\n canvasWidth: number,\n canvasHeight: number,\n transform: { x: number; y: number; k: number },\n margin: number = CULL_MARGIN,\n): { minX: number; minY: number; maxX: number; maxY: number } {\n const { x, y, k } = transform;\n return {\n minX: (-x - margin) / k,\n minY: (-y - margin) / k,\n maxX: (canvasWidth - x + margin) / k,\n maxY: (canvasHeight - y + margin) / k,\n };\n}\n\n/** Check if a node falls within the visible rect. */\nfunction nodeInView(\n node: PositionedNode,\n rect: { minX: number; minY: number; maxX: number; maxY: number },\n): boolean {\n return (\n node.x + node.radius >= rect.minX &&\n node.x - node.radius <= rect.maxX &&\n node.y + node.radius >= rect.minY &&\n node.y - node.radius <= rect.maxY\n );\n}\n\n/** Check if an edge has at least one endpoint in view. */\nfunction edgeInView(\n edge: PositionedEdge,\n rect: { minX: number; minY: number; maxX: number; maxY: number },\n): boolean {\n return (\n (edge.sourceX >= rect.minX &&\n edge.sourceX <= rect.maxX &&\n edge.sourceY >= rect.minY &&\n edge.sourceY <= rect.maxY) ||\n (edge.targetX >= rect.minX &&\n edge.targetX <= rect.maxX &&\n edge.targetY >= rect.minY &&\n edge.targetY <= rect.maxY)\n );\n}\n\n// ---------------------------------------------------------------------------\n// Dash patterns for edge styles\n// ---------------------------------------------------------------------------\n\nconst DASH_PATTERNS: Record<string, number[]> = {\n solid: [],\n dashed: [6, 4],\n dotted: [2, 3],\n};\n\n// ---------------------------------------------------------------------------\n// GraphCanvasRenderer\n// ---------------------------------------------------------------------------\n\nexport class GraphCanvasRenderer {\n private canvas: HTMLCanvasElement;\n // biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via this-destructuring\n private ctx: CanvasRenderingContext2D;\n private dpr: number;\n // biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via this-destructuring\n private cssWidth = 0;\n // biome-ignore lint/correctness/noUnusedPrivateClassMembers: accessed via this-destructuring\n private cssHeight = 0;\n\n constructor(canvas: HTMLCanvasElement) {\n this.canvas = canvas;\n this.ctx = canvas.getContext('2d')!;\n this.dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n }\n\n /** Update canvas dimensions with DPR scaling. CSS size stays at css values. */\n resize(width: number, height: number): void {\n this.cssWidth = width;\n this.cssHeight = height;\n this.canvas.width = width * this.dpr;\n this.canvas.height = height * this.dpr;\n }\n\n /** Clear canvas and render the full graph state. */\n render(state: GraphRenderState): void {\n const { ctx, dpr, cssWidth, cssHeight } = this;\n const {\n nodes,\n edges,\n transform,\n hoveredNodeId,\n hoveredEdgeId,\n selectedNodeIds,\n adjacencyMap,\n theme,\n searchMatches,\n isGesturing,\n } = state;\n const dimOpacity = state.dimOpacity ?? DEFAULT_DIM_OPACITY;\n\n // Resolve the current (next) focus snapshot. When the mount supplies a\n // crossfade in `state.focus`, use its endpoints; otherwise derive a snapshot\n // from hovered/selected nodes (backward-compatible, no crossfade).\n const nextFocus: FocusSnapshot =\n state.focus?.next ?? deriveFocus(hoveredNodeId, selectedNodeIds, adjacencyMap);\n // A crossfade is live only when focus is present, mid-flight, and (cheaply)\n // the graph isn't gesturing (during gestures we snap for perf).\n const crossfade = state.focus && state.focus.t < 1 && !isGesturing ? state.focus : null;\n\n // Entrance reveal (Phase 6): present only while the mount is mid-entrance.\n const entrance =\n state.entrance && state.entrance.t < 1\n ? makeEntranceReveal(state.entrance, nodes.length)\n : null;\n\n // Data-update enter fade (Phase 7): per-node alpha for newly-added nodes,\n // multiplied into node/edge/label alpha. Absent id → full alpha (1).\n const enterAlpha = state.enterAlpha ?? null;\n const enterAlphaFor = (id: string): number => enterAlpha?.get(id) ?? 1;\n\n // Viewport culling\n const rect = visibleRect(cssWidth, cssHeight, transform);\n const visibleNodes = nodes.filter((n) => nodeInView(n, rect));\n const visibleEdges = edges.filter((e) => edgeInView(e, rect));\n\n const isDark = theme.isDark;\n const edgeAlphaDefault = isDark ? EDGE_ALPHA_DEFAULT_DARK : EDGE_ALPHA_DEFAULT_LIGHT;\n const showGlow = isDark && !isGesturing && visibleNodes.length < GLOW_NODE_THRESHOLD;\n // Minimum radius in graph coordinates so nodes stay visible when zoomed out\n const minRadius = MIN_SCREEN_RADIUS / transform.k;\n\n // -- Clear and apply transform --\n ctx.save();\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.clearRect(0, 0, cssWidth, cssHeight);\n\n // Fill background (skip if transparent to let page background show through)\n if (theme.colors.background !== 'transparent') {\n ctx.fillStyle = theme.colors.background;\n ctx.fillRect(0, 0, cssWidth, cssHeight);\n }\n\n ctx.translate(transform.x, transform.y);\n ctx.scale(transform.k, transform.k);\n\n // -- Draw exit ghosts FIRST/UNDER the live marks (Phase 7) --\n // Removed nodes/edges fade out beneath the live graph. Not hit-tested (the\n // mount never rebuilds the spatial index with them), just painted.\n if (state.exiting && state.exiting.alpha > 0) {\n this.drawGhosts(ctx, state.exiting, rect, edgeAlphaDefault);\n }\n\n // -- Draw edges (batched) -- crossfade path only mid-transition, else the\n // fast 3-bucket steady-state path. Degrade to snap for huge edge sets.\n if (crossfade && visibleEdges.length <= CROSSFADE_MAX_EDGES) {\n this.drawEdgesCrossfade(\n ctx,\n visibleEdges,\n crossfade.prev,\n crossfade.next,\n crossfade.t,\n dimOpacity,\n edgeAlphaDefault,\n isGesturing ? null : searchMatches,\n hoveredEdgeId,\n entrance,\n enterAlphaFor,\n );\n } else {\n this.drawEdgesBatched(\n ctx,\n visibleEdges,\n nextFocus,\n dimOpacity,\n edgeAlphaDefault,\n isGesturing ? null : searchMatches,\n hoveredEdgeId,\n entrance,\n enterAlphaFor,\n );\n }\n\n // -- Draw nodes (batched by fill color) --\n this.drawNodesBatched(\n ctx,\n visibleNodes,\n hoveredNodeId,\n selectedNodeIds,\n isGesturing ? null : searchMatches,\n showGlow,\n theme,\n minRadius,\n nextFocus,\n dimOpacity,\n state.exemptIds,\n crossfade,\n state.hoverRadiusScale,\n entrance,\n enterAlphaFor,\n );\n\n // -- Draw labels (skipped during gestures) --\n if (!isGesturing) {\n this.drawLabels(\n ctx,\n visibleNodes,\n hoveredNodeId,\n selectedNodeIds,\n searchMatches,\n transform.k,\n theme,\n entrance,\n enterAlphaFor,\n );\n }\n\n ctx.restore();\n\n // Brand watermark in screen coordinates (unaffected by pan/zoom)\n if (state.watermark) {\n this.drawBrand(ctx, cssWidth, cssHeight, theme);\n }\n }\n\n // -------------------------------------------------------------------------\n // Brand rendering\n // -------------------------------------------------------------------------\n\n private drawBrand(\n ctx: CanvasRenderingContext2D,\n w: number,\n h: number,\n theme: GraphRenderState['theme'],\n ): void {\n if (w < BRAND_MIN_WIDTH) return;\n const { dpr } = this;\n ctx.save();\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n const padding = theme.spacing.padding;\n const x = w - padding;\n const y = h - padding;\n ctx.font = `600 ${BRAND_FONT_SIZE}px ${theme.fonts.family}`;\n ctx.fillStyle = theme.colors.axis;\n ctx.globalAlpha = 0.55;\n ctx.textAlign = 'right';\n ctx.textBaseline = 'alphabetic';\n ctx.fillText('OpenData', x, y);\n ctx.restore();\n }\n\n // -------------------------------------------------------------------------\n // Batched edge drawing\n // -------------------------------------------------------------------------\n\n private drawEdgesBatched(\n ctx: CanvasRenderingContext2D,\n edges: PositionedEdge[],\n focus: FocusSnapshot,\n dimOpacity: number,\n edgeAlphaDefault: number,\n searchMatches: Set<string> | null,\n hoveredEdgeId: string | null,\n entrance: EntranceReveal | null,\n enterAlphaFor: (id: string) => number,\n ): void {\n // Settled fast path: classify each edge into one of 3 tiers, batch within.\n const buckets: Record<FocusTier, PositionedEdge[]> = {\n dimmed: [],\n default: [],\n connected: [],\n };\n let hoveredEdge: PositionedEdge | null = null;\n\n for (const edge of edges) {\n const edgeId = `${edge.source}->${edge.target}`;\n if (edgeId === hoveredEdgeId) {\n hoveredEdge = edge;\n continue; // Draw hovered edge last, on top\n }\n buckets[edgeTier(edge, focus)].push(edge);\n }\n\n // Entrance: edges lag 30% behind the reveal, so scale every tier's alpha.\n const ea = entrance ? entrance.edgeAlpha : 1;\n\n // Draw dimmed first, then default, then connected (on top)\n this.drawEdgeGroupBatched(\n ctx,\n buckets.dimmed,\n edgeTierAlpha('dimmed', dimOpacity, edgeAlphaDefault) * ea,\n searchMatches,\n enterAlphaFor,\n );\n this.drawEdgeGroupBatched(\n ctx,\n buckets.default,\n edgeAlphaDefault * ea,\n searchMatches,\n enterAlphaFor,\n );\n this.drawEdgeGroupBatched(\n ctx,\n buckets.connected,\n EDGE_ALPHA_CONNECTED * ea,\n searchMatches,\n enterAlphaFor,\n );\n\n this.drawHoveredEdge(ctx, hoveredEdge);\n }\n\n /**\n * Crossfade edges between a prev and next focus state. Each edge is classified\n * under BOTH snapshots → at most 9 (prevTier × nextTier) buckets, each drawn\n * batched at `lerp(edgeTierAlpha[prev], edgeTierAlpha[next], t)`. Preserves the\n * per-group style batching within each bucket.\n */\n private drawEdgesCrossfade(\n ctx: CanvasRenderingContext2D,\n edges: PositionedEdge[],\n prev: FocusSnapshot,\n next: FocusSnapshot,\n t: number,\n dimOpacity: number,\n edgeAlphaDefault: number,\n searchMatches: Set<string> | null,\n hoveredEdgeId: string | null,\n entrance: EntranceReveal | null,\n enterAlphaFor: (id: string) => number,\n ): void {\n const ea = entrance ? entrance.edgeAlpha : 1;\n // Bucket by (prevTier, nextTier). Key encodes both tiers.\n const buckets = new Map<string, PositionedEdge[]>();\n let hoveredEdge: PositionedEdge | null = null;\n\n for (const edge of edges) {\n const edgeId = `${edge.source}->${edge.target}`;\n if (edgeId === hoveredEdgeId) {\n hoveredEdge = edge;\n continue;\n }\n const key = `${edgeTier(edge, prev)}|${edgeTier(edge, next)}`;\n let bucket = buckets.get(key);\n if (!bucket) {\n bucket = [];\n buckets.set(key, bucket);\n }\n bucket.push(edge);\n }\n\n // Draw dim→dim first (lowest alpha) up to connected→connected, so brighter\n // buckets paint over dimmer ones. Sort by blended alpha ascending.\n const ordered = [...buckets.entries()]\n .map(([key, bucket]) => {\n const [prevTier, nextTier] = key.split('|') as [FocusTier, FocusTier];\n const alpha =\n lerp(\n edgeTierAlpha(prevTier, dimOpacity, edgeAlphaDefault),\n edgeTierAlpha(nextTier, dimOpacity, edgeAlphaDefault),\n t,\n ) * ea;\n return { alpha, bucket };\n })\n .sort((a, b) => a.alpha - b.alpha);\n\n for (const { alpha, bucket } of ordered) {\n this.drawEdgeGroupBatched(ctx, bucket, alpha, searchMatches, enterAlphaFor);\n }\n\n this.drawHoveredEdge(ctx, hoveredEdge);\n }\n\n /** Draw the hovered edge on top with a thickened highlight stroke. */\n private drawHoveredEdge(ctx: CanvasRenderingContext2D, hoveredEdge: PositionedEdge | null): void {\n if (!hoveredEdge) return;\n const dash = DASH_PATTERNS[hoveredEdge.style] ?? DASH_PATTERNS.solid;\n ctx.setLineDash(dash);\n ctx.strokeStyle = hoveredEdge.stroke;\n ctx.lineWidth = hoveredEdge.strokeWidth * 2;\n ctx.globalAlpha = EDGE_ALPHA_CONNECTED;\n ctx.beginPath();\n ctx.moveTo(hoveredEdge.sourceX, hoveredEdge.sourceY);\n ctx.lineTo(hoveredEdge.targetX, hoveredEdge.targetY);\n ctx.stroke();\n ctx.setLineDash([]);\n ctx.globalAlpha = 1;\n }\n\n /**\n * Draw a group of edges at a given alpha, batched by (stroke, strokeWidth, style).\n * When search is inactive, all edges of the same style are drawn in a single path.\n * When search is active, edges split by search-match status for alpha dimming.\n */\n private drawEdgeGroupBatched(\n ctx: CanvasRenderingContext2D,\n edges: PositionedEdge[],\n alpha: number,\n searchMatches: Set<string> | null,\n enterAlphaFor: (id: string) => number,\n ): void {\n if (edges.length === 0) return;\n\n // Group by visual key: stroke + strokeWidth + style + quantized edge-enter\n // alpha. An edge touching a newly-added node fades with the min of its\n // endpoints' enter alpha (8-bucket quantized so batching stays bounded). When\n // no enter fade is active every edge quantizes to 1 → a single batch (no\n // overhead in the common case).\n const groups = new Map<string, { edges: PositionedEdge[]; enter: number }>();\n for (const edge of edges) {\n const rawEnter = Math.min(enterAlphaFor(edge.source), enterAlphaFor(edge.target));\n const enter = Math.round(rawEnter * 8) / 8;\n const key = `${edge.stroke}|${edge.strokeWidth}|${edge.style}|${enter}`;\n let group = groups.get(key);\n if (!group) {\n group = { edges: [], enter };\n groups.set(key, group);\n }\n group.edges.push(edge);\n }\n\n for (const [, { edges: group, enter }] of groups) {\n const sample = group[0];\n const dash = DASH_PATTERNS[sample.style] ?? DASH_PATTERNS.solid;\n ctx.setLineDash(dash);\n ctx.strokeStyle = sample.stroke;\n ctx.lineWidth = sample.strokeWidth;\n const groupAlpha = alpha * enter;\n\n if (!searchMatches) {\n // Fast path: single batched path for all edges in this group\n ctx.globalAlpha = groupAlpha;\n ctx.beginPath();\n for (const edge of group) {\n ctx.moveTo(edge.sourceX, edge.sourceY);\n ctx.lineTo(edge.targetX, edge.targetY);\n }\n ctx.stroke();\n } else {\n // Search active: split into matched and non-matched batches\n ctx.globalAlpha = groupAlpha;\n ctx.beginPath();\n let hasMatched = false;\n\n const nonMatchPath: PositionedEdge[] = [];\n\n for (const edge of group) {\n const srcMatch = searchMatches.has(edge.source);\n const tgtMatch = searchMatches.has(edge.target);\n if (srcMatch || tgtMatch) {\n ctx.moveTo(edge.sourceX, edge.sourceY);\n ctx.lineTo(edge.targetX, edge.targetY);\n hasMatched = true;\n } else {\n nonMatchPath.push(edge);\n }\n }\n if (hasMatched) ctx.stroke();\n\n // Draw non-matching edges dimmed\n if (nonMatchPath.length > 0) {\n ctx.globalAlpha = SEARCH_NON_MATCH_ALPHA * groupAlpha;\n ctx.beginPath();\n for (const edge of nonMatchPath) {\n ctx.moveTo(edge.sourceX, edge.sourceY);\n ctx.lineTo(edge.targetX, edge.targetY);\n }\n ctx.stroke();\n }\n }\n }\n\n ctx.setLineDash([]);\n ctx.globalAlpha = 1;\n }\n\n // -------------------------------------------------------------------------\n // Batched node drawing\n // -------------------------------------------------------------------------\n\n private drawNodesBatched(\n ctx: CanvasRenderingContext2D,\n nodes: PositionedNode[],\n hoveredNodeId: string | null,\n selectedNodeIds: Set<string>,\n searchMatches: Set<string> | null,\n showGlow: boolean,\n theme: GraphRenderState['theme'],\n minRadius: number,\n nextFocus: FocusSnapshot,\n dimOpacity: number,\n exemptIds: Set<string> | undefined,\n crossfade: { t: number; prev: FocusSnapshot; next: FocusSnapshot } | null,\n hoverRadiusScale: Map<string, number> | undefined,\n entrance: EntranceReveal | null,\n enterAlphaFor: (id: string) => number,\n ): void {\n // Effective per-node alpha = focus dim × search dim. Focus dim crossfades\n // between prev/next tiers when a transition is live; otherwise it's the\n // settled next-tier alpha. Batching is preserved by grouping nodes that\n // share (fill, quantized-alpha) — at most 2 focus tiers × 2 search tiers.\n const focusAlpha = (node: PositionedNode): number => {\n if (crossfade) {\n return lerp(\n nodeTierAlpha(nodeTier(node, crossfade.prev, exemptIds), dimOpacity),\n nodeTierAlpha(nodeTier(node, crossfade.next, exemptIds), dimOpacity),\n crossfade.t,\n );\n }\n return nodeTierAlpha(nodeTier(node, nextFocus, exemptIds), dimOpacity);\n };\n const searchAlpha = (node: PositionedNode): number =>\n searchMatches !== null && !searchMatches.has(node.id) ? SEARCH_NON_MATCH_ALPHA : 1;\n // Entrance pop: alpha and scale run separate curves (alpha 0→1, scale\n // 0→overshoot→1). Both derive from the same quantized per-node progress, so\n // the fill/stroke batching keys stay bounded during the reveal.\n const entranceAlpha = (node: PositionedNode): number =>\n entrance ? entrance.nodeAlpha(node) : 1;\n const entranceScale = (node: PositionedNode): number =>\n entrance ? entrance.nodeScale(node) : 1;\n // Convergence drift: nodes pop in slightly outside their final spot and\n // slide home. Zero when the entrance is settled or unstaggered.\n const entranceShift = (node: PositionedNode): { x: number; y: number } =>\n entrance ? entrance.shift(node.id) : ZERO_SHIFT;\n // Data-update enter fade: newly-added nodes ramp 0→1 (already bucket-quantized\n // by the mount), preserving fill/stroke batching keys.\n const effectiveAlpha = (node: PositionedNode): number =>\n focusAlpha(node) * searchAlpha(node) * entranceAlpha(node) * enterAlphaFor(node.id);\n\n // Separate special nodes (hovered/selected, or mid radius-tween) from bulk.\n const bulkNodes: PositionedNode[] = [];\n const specialNodes: PositionedNode[] = [];\n\n for (const node of nodes) {\n if (\n node.id === hoveredNodeId ||\n selectedNodeIds.has(node.id) ||\n (hoverRadiusScale?.has(node.id) ?? false)\n ) {\n specialNodes.push(node);\n } else {\n bulkNodes.push(node);\n }\n }\n\n // Helper: effective radius clamped to minimum screen size, then scaled by\n // the entrance pop curve.\n const r = (node: PositionedNode) => Math.max(node.radius, minRadius) * entranceScale(node);\n\n // --- Glow pass (dark mode only, before fills) ---\n // Skipped mid-entrance: the glow draws at final positions and would visibly\n // detach from nodes still on their convergence drift.\n if (showGlow && !entrance) {\n this.drawGlowBatched(ctx, bulkNodes, searchMatches, minRadius);\n }\n\n // --- Bulk fill pass: batch by (fill color, quantized alpha) ---\n const fillGroups = new Map<string, { fill: string; alpha: number; nodes: PositionedNode[] }>();\n for (const node of bulkNodes) {\n const alpha = effectiveAlpha(node);\n const key = `${node.fill}|${alpha.toFixed(3)}`;\n let group = fillGroups.get(key);\n if (!group) {\n group = { fill: node.fill, alpha, nodes: [] };\n fillGroups.set(key, group);\n }\n group.nodes.push(node);\n }\n\n for (const { fill, alpha, nodes: group } of fillGroups.values()) {\n ctx.fillStyle = fill;\n ctx.globalAlpha = alpha;\n ctx.beginPath();\n for (const node of group) {\n const nr = r(node);\n if (nr <= 0) continue;\n const s = entranceShift(node);\n ctx.moveTo(node.x + s.x + nr, node.y + s.y);\n ctx.arc(node.x + s.x, node.y + s.y, nr, 0, TWO_PI);\n }\n ctx.fill();\n }\n\n // --- Bulk stroke pass: batch by (stroke color+width, quantized alpha) ---\n const strokeGroups = new Map<\n string,\n { stroke: string; width: number; alpha: number; nodes: PositionedNode[] }\n >();\n for (const node of bulkNodes) {\n const alpha = effectiveAlpha(node);\n const key = `${node.stroke}|${node.strokeWidth}|${alpha.toFixed(3)}`;\n let group = strokeGroups.get(key);\n if (!group) {\n group = { stroke: node.stroke, width: node.strokeWidth, alpha, nodes: [] };\n strokeGroups.set(key, group);\n }\n group.nodes.push(node);\n }\n\n for (const { stroke, width, alpha, nodes: group } of strokeGroups.values()) {\n ctx.strokeStyle = stroke;\n ctx.lineWidth = width;\n ctx.globalAlpha = alpha;\n ctx.beginPath();\n for (const node of group) {\n const nr = r(node);\n if (nr <= 0) continue;\n const s = entranceShift(node);\n ctx.moveTo(node.x + s.x + nr, node.y + s.y);\n ctx.arc(node.x + s.x, node.y + s.y, nr, 0, TWO_PI);\n }\n ctx.stroke();\n }\n\n // --- Special nodes (hovered/selected) drawn individually ---\n for (const node of specialNodes) {\n const isHovered = node.id === hoveredNodeId;\n const isSelected = selectedNodeIds.has(node.id);\n const dimmed = searchMatches !== null && !searchMatches.has(node.id);\n const baseRadius = Math.max(node.radius, minRadius);\n // Hover radius tween: scale routes through here until it returns to 1.\n const hoverScale = hoverRadiusScale?.get(node.id) ?? (isHovered ? 1.15 : 1);\n const radius = baseRadius * hoverScale;\n // brighten() switches at the scale midpoint (>1.075 of the 1→1.15 range).\n const brightened = isHovered && hoverScale >= 1.075;\n const s = entranceShift(node);\n const nx = node.x + s.x;\n const ny = node.y + s.y;\n\n ctx.globalAlpha = dimmed ? SEARCH_NON_MATCH_ALPHA : 1;\n\n // Glow for special nodes\n if (showGlow && !dimmed) {\n ctx.beginPath();\n ctx.arc(nx, ny, radius * GLOW_RADIUS_MULTIPLIER, 0, TWO_PI);\n ctx.fillStyle = node.fill;\n ctx.globalAlpha = GLOW_ALPHA;\n ctx.fill();\n ctx.globalAlpha = dimmed ? SEARCH_NON_MATCH_ALPHA : 1;\n }\n\n // Fill\n ctx.beginPath();\n ctx.arc(nx, ny, radius, 0, TWO_PI);\n ctx.fillStyle = brightened ? brighten(node.fill) : node.fill;\n ctx.fill();\n\n // Stroke\n ctx.strokeStyle = node.stroke;\n ctx.lineWidth = node.strokeWidth;\n ctx.stroke();\n\n // Selection ring\n if (isSelected) {\n ctx.beginPath();\n ctx.arc(nx, ny, radius + 3, 0, TWO_PI);\n ctx.strokeStyle = theme.colors.categorical[0] ?? '#3b82f6';\n ctx.lineWidth = 2;\n ctx.stroke();\n }\n }\n\n ctx.globalAlpha = 1;\n }\n\n /** Batch glow circles by fill color. */\n private drawGlowBatched(\n ctx: CanvasRenderingContext2D,\n nodes: PositionedNode[],\n searchMatches: Set<string> | null,\n minRadius: number,\n ): void {\n const glowGroups = new Map<string, PositionedNode[]>();\n for (const node of nodes) {\n if (searchMatches && !searchMatches.has(node.id)) continue;\n let group = glowGroups.get(node.fill);\n if (!group) {\n group = [];\n glowGroups.set(node.fill, group);\n }\n group.push(node);\n }\n\n ctx.globalAlpha = GLOW_ALPHA;\n for (const [fill, group] of glowGroups) {\n ctx.fillStyle = fill;\n ctx.beginPath();\n for (const node of group) {\n const gr = Math.max(node.radius, minRadius) * GLOW_RADIUS_MULTIPLIER;\n ctx.moveTo(node.x + gr, node.y);\n ctx.arc(node.x, node.y, gr, 0, TWO_PI);\n }\n ctx.fill();\n }\n ctx.globalAlpha = 1;\n }\n\n // -------------------------------------------------------------------------\n // Labels (drawn individually, skipped during gestures)\n // -------------------------------------------------------------------------\n\n private drawLabels(\n ctx: CanvasRenderingContext2D,\n nodes: PositionedNode[],\n hoveredNodeId: string | null,\n selectedNodeIds: Set<string>,\n searchMatches: Set<string> | null,\n zoom: number,\n theme: GraphRenderState['theme'],\n entrance: EntranceReveal | null,\n enterAlphaFor: (id: string) => number,\n ): void {\n // Labels fade in with the raw entrance progress (x on top of dim alpha).\n const la = entrance ? entrance.labelAlpha : 1;\n // Font size inversely scaled by zoom, clamped to readable range\n const rawSize = 10 / zoom;\n const fontSize = Math.max(LABEL_FONT_MIN, Math.min(LABEL_FONT_MAX, rawSize));\n\n ctx.font = `${fontSize}px ${theme.fonts.family}`;\n ctx.textAlign = 'center';\n ctx.textBaseline = 'top';\n\n // Halo color: the canvas the text is cut out of. A transparent bg inherits\n // its mode from the theme's darkMode flag, not from text luminance.\n const haloColor =\n theme.colors.background !== 'transparent'\n ? theme.colors.background\n : theme.isDark\n ? 'rgba(0, 0, 0, 0.7)'\n : 'rgba(255, 255, 255, 0.85)';\n\n // Forced labels (hovered, selected, alwaysShowLabel, search match) always\n // draw and reserve their box first; everything else competes for the budget.\n const forced: PositionedNode[] = [];\n const rest: PositionedNode[] = [];\n for (const node of nodes) {\n if (!node.label) continue;\n const isForced =\n node.id === hoveredNodeId ||\n selectedNodeIds.has(node.id) ||\n node.labelPriority === Infinity ||\n (searchMatches?.has(node.id) ?? false);\n if (isForced) forced.push(node);\n else rest.push(node);\n }\n rest.sort((a, b) => b.labelPriority - a.labelPriority);\n\n const budget = labelBudget(zoom);\n // Measuring every candidate would cost more than the labels we can draw, so\n // only the top slice by priority is considered for the remaining slots.\n const candidates = rest.slice(0, budget * 4);\n const placed: LabelBox[] = [];\n const pad = fontSize * 0.15;\n const lineHeight = fontSize * 1.2;\n\n // Headless canvases (jsdom/happy-dom stubs) can be missing measureText;\n // an average-glyph estimate keeps the declutter pass honest there.\n const textWidth = (text: string): number =>\n ctx.measureText?.(text)?.width ?? text.length * fontSize * 0.55;\n\n const boxFor = (node: PositionedNode): LabelBox => {\n const w = textWidth(node.label as string);\n const y0 = node.y + node.radius + 3;\n return {\n x0: node.x - w / 2 - pad,\n x1: node.x + w / 2 + pad,\n y0: y0 - pad,\n y1: y0 + lineHeight + pad,\n };\n };\n\n const drawOne = (node: PositionedNode, isForced: boolean): void => {\n const dimmed = searchMatches !== null && !searchMatches.has(node.id);\n ctx.globalAlpha = (dimmed ? SEARCH_NON_MATCH_ALPHA : 1) * la * enterAlphaFor(node.id);\n const labelY = node.y + node.radius + 3;\n\n // Halo is reserved for the labels that must win over whatever they cross:\n // painting one behind every label thickens the whole type layer into a\n // gray mat on a dense graph.\n if (isForced) {\n ctx.strokeStyle = haloColor;\n ctx.lineWidth = 3;\n ctx.lineJoin = 'round';\n ctx.miterLimit = 2;\n ctx.strokeText(node.label as string, node.x, labelY);\n }\n\n ctx.fillStyle = isForced ? theme.colors.text : theme.colors.axis;\n ctx.fillText(node.label as string, node.x, labelY);\n };\n\n // Forced labels reserve space, and paint last so they sit on top.\n for (const node of forced) placed.push(boxFor(node));\n\n let drawn = 0;\n for (const node of candidates) {\n if (drawn >= budget) break;\n const box = boxFor(node);\n let collides = false;\n for (const other of placed) {\n if (boxesOverlap(box, other)) {\n collides = true;\n break;\n }\n }\n if (collides) continue;\n placed.push(box);\n drawOne(node, false);\n drawn++;\n }\n\n for (const node of forced) drawOne(node, true);\n\n ctx.globalAlpha = 1;\n }\n\n // -------------------------------------------------------------------------\n // Exit ghosts (Phase 7)\n // -------------------------------------------------------------------------\n\n /**\n * Draw exit ghosts (removed nodes/edges) UNDER the live marks at a global fade\n * alpha. Neutral rendering: no focus dim, no search dim, no selection ring —\n * they're on their way out. Batched by (stroke/style) for edges and (fill) for\n * nodes, culled to the visible rect.\n */\n private drawGhosts(\n ctx: CanvasRenderingContext2D,\n exiting: NonNullable<GraphRenderState['exiting']>,\n rect: { minX: number; minY: number; maxX: number; maxY: number },\n edgeAlphaDefault: number,\n ): void {\n const alpha = exiting.alpha;\n\n // Ghost edges: batch by (stroke, strokeWidth, style).\n const edgeGroups = new Map<string, PositionedEdge[]>();\n for (const edge of exiting.edges) {\n if (!edgeInView(edge, rect)) continue;\n const key = `${edge.stroke}|${edge.strokeWidth}|${edge.style}`;\n const group = edgeGroups.get(key);\n if (group) group.push(edge);\n else edgeGroups.set(key, [edge]);\n }\n for (const [, group] of edgeGroups) {\n const sample = group[0];\n const dash = DASH_PATTERNS[sample.style] ?? DASH_PATTERNS.solid;\n ctx.setLineDash(dash);\n ctx.strokeStyle = sample.stroke;\n ctx.lineWidth = sample.strokeWidth;\n ctx.globalAlpha = edgeAlphaDefault * alpha;\n ctx.beginPath();\n for (const edge of group) {\n ctx.moveTo(edge.sourceX, edge.sourceY);\n ctx.lineTo(edge.targetX, edge.targetY);\n }\n ctx.stroke();\n }\n ctx.setLineDash([]);\n\n // Ghost nodes: batch by fill color.\n const nodeGroups = new Map<string, PositionedNode[]>();\n for (const node of exiting.nodes) {\n if (!nodeInView(node, rect)) continue;\n const group = nodeGroups.get(node.fill);\n if (group) group.push(node);\n else nodeGroups.set(node.fill, [node]);\n }\n for (const [fill, group] of nodeGroups) {\n ctx.fillStyle = fill;\n ctx.globalAlpha = alpha;\n ctx.beginPath();\n for (const node of group) {\n ctx.moveTo(node.x + node.radius, node.y);\n ctx.arc(node.x, node.y, node.radius, 0, TWO_PI);\n }\n ctx.fill();\n }\n\n ctx.globalAlpha = 1;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Color helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Brighten a hex/rgb color by ~20% for hover effect.\n * Quick and dirty approach: parse hex, lighten each channel.\n */\nfunction brighten(color: string): string {\n // Handle rgb(r,g,b) or rgb(r, g, b)\n const rgbMatch = color.match(/^rgb\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)$/);\n if (rgbMatch) {\n const r = Math.min(255, parseInt(rgbMatch[1], 10) + 40);\n const g = Math.min(255, parseInt(rgbMatch[2], 10) + 40);\n const b = Math.min(255, parseInt(rgbMatch[3], 10) + 40);\n return `rgb(${r},${g},${b})`;\n }\n\n // Handle hex colors (#rgb and #rrggbb)\n const hex = color.replace('#', '');\n const full =\n hex.length === 3\n ? hex\n .split('')\n .map((c) => c + c)\n .join('')\n : hex;\n\n if (full.length === 6) {\n const r = Math.min(255, parseInt(full.slice(0, 2), 16) + 40);\n const g = Math.min(255, parseInt(full.slice(2, 4), 16) + 40);\n const b = Math.min(255, parseInt(full.slice(4, 6), 16) + 40);\n return `rgb(${r},${g},${b})`;\n }\n\n return color;\n}\n","/**\n * Entrance choreography math for the graph reveal.\n *\n * The mount drives a single 0→1 `entranceProgress`; this module turns it into a\n * per-node reveal alpha with a staggered start. The per-node alpha is quantized\n * to a small number of levels so the canvas renderer can still batch fills by\n * (color, alpha) — an unquantized stagger would force one fill() per node.\n *\n * Above ENTRANCE_STAGGER_MAX_NODES the mount uses a single global fade (no\n * per-node stagger) because thousands of distinct start times defeat batching\n * and read as noise anyway.\n */\n\n/**\n * Per-node reveal progress at global progress `globalT`.\n *\n * Node `index` starts revealing at `(index/total)·0.4` and ramps over the next\n * `0.6` of the timeline, so the first node is fully in at t=0.6 and the last at\n * t=1.0. The result is quantized to `buckets` (≤8) levels to preserve batching.\n */\nexport function nodeEnterProgress(\n globalT: number,\n index: number,\n total: number,\n buckets = 8,\n): number {\n const g = globalT <= 0 ? 0 : globalT >= 1 ? 1 : globalT;\n const start = total > 0 ? (index / total) * 0.4 : 0;\n const local = (g - start) / 0.6;\n const clamped = local <= 0 ? 0 : local >= 1 ? 1 : local;\n // Quantize to `buckets` levels (0, 1/b, ..., 1) so fills stay batchable.\n const b = Math.max(1, Math.floor(buckets));\n return Math.round(clamped * b) / b;\n}\n\n/**\n * Above this node count the entrance uses a single global fade with no per-node\n * stagger — thousands of start times kill batching and read as noise.\n */\nexport const ENTRANCE_STAGGER_MAX_NODES = 3000;\n\n// ---------------------------------------------------------------------------\n// Organic pop: order, scale curve, and convergence drift\n// ---------------------------------------------------------------------------\n\n/** How far (px, graph space) a node starts from its final spot during the pop. */\nexport const ENTRANCE_DRIFT_PX = 16;\n\ntype XYNode = { id: string; x: number; y: number };\n\nfunction centroid(nodes: XYNode[]): { cx: number; cy: number } {\n let cx = 0;\n let cy = 0;\n for (const n of nodes) {\n cx += n.x;\n cy += n.y;\n }\n cx /= nodes.length;\n cy /= nodes.length;\n return { cx, cy };\n}\n\n/**\n * Deterministic hash of a node id to a well-distributed 32-bit unsigned int\n * (FNV-1a). Used to scatter entrance timing: same id → same slot every render,\n * so the reveal stays stable and testable without a spatial or index bias.\n */\nfunction hashId(id: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < id.length; i++) {\n h ^= id.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n\n/**\n * Stagger rank for each node: a deterministic hash-shuffle of the node ids, so\n * each node reveals at an independent, scattered time rather than in a uniform\n * center-outward ripple. The scatter reads as many independent elements popping\n * in; a spatial (centroid-radial) or index order reads as one coordinated sweep.\n * Hashing the id keeps the order stable per render (pure function of ids), so it\n * survives re-renders and stays testable.\n */\nexport function entranceOrder(nodes: XYNode[]): Map<string, number> {\n const rank = new Map<string, number>();\n if (nodes.length === 0) return rank;\n const sorted = [...nodes].sort((a, b) => {\n const ha = hashId(a.id);\n const hb = hashId(b.id);\n // Tie-break on id so equal hashes (rare) still give a total, stable order.\n return ha - hb || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);\n });\n for (let i = 0; i < sorted.length; i++) rank.set(sorted[i].id, i);\n return rank;\n}\n\n/**\n * Per-node drift offset: a unit vector pointing away from the layout centroid,\n * scaled to ENTRANCE_DRIFT_PX. Nodes pop in slightly outside their final spot\n * and converge inward. Deterministic (pure function of positions), zero-safe\n * at the centroid.\n */\nexport function entranceOffsets(\n nodes: XYNode[],\n dist: number = ENTRANCE_DRIFT_PX,\n): Map<string, { x: number; y: number }> {\n const offsets = new Map<string, { x: number; y: number }>();\n if (nodes.length === 0) return offsets;\n const { cx, cy } = centroid(nodes);\n for (const n of nodes) {\n const dx = n.x - cx;\n const dy = n.y - cy;\n const len = Math.sqrt(dx * dx + dy * dy);\n if (len < 1e-6) {\n offsets.set(n.id, { x: 0, y: -dist });\n } else {\n offsets.set(n.id, { x: (dx / len) * dist, y: (dy / len) * dist });\n }\n }\n return offsets;\n}\n\n/**\n * Pop scale curve: 0 at t=0, overshoots to ~1.1 around t≈0.7, settles at 1.\n * Standard back-out easing applied to scale, so nodes pop rather than fade.\n */\nexport function popScale(t: number): number {\n if (t <= 0) return 0;\n if (t >= 1) return 1;\n const c1 = 1.70158;\n const c3 = c1 + 1;\n const u = t - 1;\n return 1 + c3 * u * u * u + c1 * u * u;\n}\n\n/**\n * Pop alpha curve: reaches full opacity by 60% of the node's window so the\n * overshoot phase of the scale pop is fully visible, not half-faded.\n */\nexport function popAlpha(t: number): number {\n const a = t / 0.6;\n return a >= 1 ? 1 : a <= 0 ? 0 : a;\n}\n\n/**\n * Drift factor: 1 at t=0 (full offset from final position) easing quadratically\n * to 0 (node at rest). Multiplied into the per-node entrance offset.\n */\nexport function driftFactor(t: number): number {\n const u = 1 - (t <= 0 ? 0 : t >= 1 ? 1 : t);\n return u * u;\n}\n","/**\n * Per-frame focus model for the graph: unifies the three emphasis sources\n * (programmatic highlight, search matches, hover-neighborhood) into a single\n * snapshot pair that the renderer crossfades between.\n *\n * Composition, not strict precedence (see plan §5a):\n * - Standing state = highlight ∩ search when both active; if that intersection\n * is empty, search matches win (fresher user intent). This preserves the\n * \"filter by topic, then search within it\" workflow.\n * - Hover-neighborhood is the transient top layer over whatever the standing\n * state is.\n *\n * The transition tweens between two discrete FocusSnapshots. Rapid hover sweeps\n * retarget mid-flight: `retarget(next, now)` snapshots the endpoint CLOSEST to\n * the current display (`prev = p < 0.5 ? old prev : old next`), so a low-p\n * retarget keeps the old prev — no forward snap, worst-case visual jump halved\n * vs. a naive \"always start from current next\" rule. Exact per-edge capture\n * would destroy the renderer's tier batching, so this discrete approximation is\n * deliberate.\n */\n\n/** The set of emphasis relationships in effect for one steady state. */\nexport interface FocusSnapshot {\n /** True when any emphasis source is active (something is dimmed). */\n hasActive: boolean;\n /** Nodes connected to the active/hovered set (includes the active nodes). */\n connected: Set<string>;\n /** Search matches, or null when search is inactive. */\n searchMatches: Set<string> | null;\n /** Selected nodes (selection rings; always emphasized). */\n selected: Set<string>;\n}\n\n/** An empty (nothing emphasized) snapshot. */\nexport function emptyFocusSnapshot(): FocusSnapshot {\n return { hasActive: false, connected: new Set(), searchMatches: null, selected: new Set() };\n}\n\n/** Whether two snapshots describe the same emphasis state (skip re-tween). */\nexport function focusSnapshotsEqual(a: FocusSnapshot, b: FocusSnapshot): boolean {\n return (\n a.hasActive === b.hasActive &&\n setsEqual(a.connected, b.connected) &&\n nullableSetsEqual(a.searchMatches, b.searchMatches) &&\n setsEqual(a.selected, b.selected)\n );\n}\n\nfunction setsEqual(a: Set<string>, b: Set<string>): boolean {\n if (a.size !== b.size) return false;\n for (const v of a) if (!b.has(v)) return false;\n return true;\n}\n\nfunction nullableSetsEqual(a: Set<string> | null, b: Set<string> | null): boolean {\n if (a === null || b === null) return a === b;\n return setsEqual(a, b);\n}\n\n/**\n * A crossfade between two focus snapshots, eased over `duration` ms.\n *\n * Time is injected (`now` passed to `retarget`/`progress`) so the mount's\n * scheduler drives it deterministically and tests use a controllable clock.\n */\nexport class FocusTransition {\n prev: FocusSnapshot;\n next: FocusSnapshot;\n private startTime: number;\n private duration: number;\n private ease: (t: number) => number;\n\n constructor(initial: FocusSnapshot, duration: number, ease: (t: number) => number, now: number) {\n this.prev = initial;\n this.next = initial;\n this.startTime = now;\n this.duration = Math.max(0, duration);\n this.ease = ease;\n }\n\n /**\n * Point a fresh transition at `target`, capturing the endpoint closest to the\n * current display as the new `prev` (the `p < 0.5` rule). A no-op when\n * `target` already equals `next`.\n */\n retarget(target: FocusSnapshot, now: number): void {\n if (focusSnapshotsEqual(target, this.next)) return;\n const p = this.rawProgress(now);\n // Below the midpoint the display is still nearer the old prev; keep it to\n // avoid a forward snap. At/after the midpoint the old next is nearer.\n this.prev = p < 0.5 ? this.prev : this.next;\n this.next = target;\n this.startTime = now;\n }\n\n /** Raw (un-eased) 0..1 progress. */\n private rawProgress(now: number): number {\n if (this.duration <= 0) return 1;\n return Math.min(1, Math.max(0, (now - this.startTime) / this.duration));\n }\n\n /** Eased 0..1 progress toward `next`. */\n progress(now: number): number {\n return this.ease(this.rawProgress(now));\n }\n\n /** True once the transition has fully settled onto `next`. */\n isSettled(now: number): boolean {\n return this.rawProgress(now) >= 1;\n }\n}\n\n/**\n * Build the standing (non-hover) focus snapshot from highlight + search sets.\n *\n * Composition rule: emphasized = highlight ∩ search when both non-empty; if the\n * intersection is empty, search wins. Selection is always carried through.\n */\nexport function composeStandingFocus(\n highlight: Set<string> | null,\n searchMatches: Set<string> | null,\n selected: Set<string>,\n adjacency: Map<string, Set<string>>,\n): FocusSnapshot {\n const hasHighlight = highlight !== null && highlight.size > 0;\n const hasSearch = searchMatches !== null && searchMatches.size > 0;\n\n // Determine the emphasized \"core\" set that drives connected-neighborhood dim.\n let core: Set<string> | null = null;\n if (highlight !== null && hasHighlight && searchMatches !== null && hasSearch) {\n const inter = intersect(highlight, searchMatches);\n core = inter.size > 0 ? inter : searchMatches;\n } else if (hasHighlight) {\n core = highlight;\n } else if (hasSearch) {\n // Search dims via searchMatches directly (renderer's search path), not the\n // connected-neighborhood path — leave core null so hover still works.\n core = null;\n }\n\n const connected = new Set<string>();\n if (core) {\n for (const id of core) {\n connected.add(id);\n const neighbors = adjacency.get(id);\n if (neighbors) for (const nid of neighbors) connected.add(nid);\n }\n }\n\n const hasActive = (core !== null && core.size > 0) || hasSearch || selected.size > 0;\n\n return {\n hasActive,\n connected,\n searchMatches: hasSearch ? searchMatches : null,\n selected,\n };\n}\n\n/**\n * Layer a hover neighborhood on top of a standing snapshot. The hovered node\n * and its neighbors become the connected set; the standing search/selection\n * carry through so search dimming and selection rings persist under hover.\n */\nexport function layerHoverFocus(\n standing: FocusSnapshot,\n hoveredId: string | null,\n hoverConnected: Set<string> | null,\n): FocusSnapshot {\n if (hoveredId === null || hoverConnected === null) return standing;\n return {\n hasActive: true,\n connected: hoverConnected,\n searchMatches: standing.searchMatches,\n selected: standing.selected,\n };\n}\n\nfunction intersect(a: Set<string>, b: Set<string>): Set<string> {\n const [small, large] = a.size <= b.size ? [a, b] : [b, a];\n const out = new Set<string>();\n for (const v of small) if (large.has(v)) out.add(v);\n return out;\n}\n","/**\n * Highlight-target resolution, shared by the 2D and 3D graph renderers.\n *\n * Both mounts compose the same two-layer highlight model (a sticky legend\n * category filter under a transient `highlight()` target), and both resolved\n * targets the same way. The resolution is pure — target plus compiled nodes and\n * adjacency in, node id set out — so it lives here rather than being written\n * twice and drifting.\n */\n\nimport type { GraphHighlightTarget } from './types';\n\n/** The compiled-node fields the resolution actually reads. */\nexport interface HighlightNode {\n id: string;\n data?: Record<string, unknown>;\n}\n\n/**\n * Resolve a highlight target into a concrete node id set.\n *\n * @param target - The target passed to `highlight()`.\n * @param nodes - The current compiled nodes (for the category form).\n * @param adjacency - Node id to neighbour ids (for the `neighborsOf` form).\n */\nexport function resolveHighlightTarget(\n target: GraphHighlightTarget,\n nodes: readonly HighlightNode[],\n adjacency: ReadonlyMap<string, ReadonlySet<string>>,\n): Set<string> {\n if ('nodeIds' in target) return new Set(target.nodeIds);\n if ('neighborsOf' in target) {\n const set = new Set<string>();\n if (target.includeSelf !== false) set.add(target.neighborsOf);\n const neighbors = adjacency.get(target.neighborsOf);\n if (neighbors) for (const nid of neighbors) set.add(nid);\n return set;\n }\n // Category form: match nodes whose `field` value is in `value`.\n const values = new Set(\n Array.isArray(target.category.value) ? target.category.value : [target.category.value],\n );\n const field = target.category.field;\n const set = new Set<string>();\n for (const n of nodes) {\n const v = n.data?.[field];\n if (v != null && values.has(String(v))) set.add(n.id);\n }\n return set;\n}\n\n/**\n * Node ids for the active legend categories, or null when no filter is up\n * (an empty active set means \"everything\", not \"nothing\").\n */\nexport function categoryHighlightSet(\n activeCategories: ReadonlySet<string>,\n nodeCategory: ReadonlyMap<string, string>,\n): Set<string> | null {\n if (activeCategories.size === 0) return null;\n const set = new Set<string>();\n for (const [id, cat] of nodeCategory) if (activeCategories.has(cat)) set.add(id);\n return set;\n}\n","/**\n * Graph interaction manager.\n *\n * Handles mouse/touch events on the canvas and translates them into\n * high-level graph interactions: pan, zoom, hover, select, drag nodes.\n * Uses the spatial index for hit testing and ZoomTransform for coordinate\n * conversion.\n */\n\nimport type { SpatialIndex } from './spatial-index';\nimport { ZoomTransform } from './zoom';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst ZOOM_MIN = 0.05;\nconst ZOOM_MAX = 15;\nconst ZOOM_STEP = -0.001;\nconst HIT_DISTANCE = 5;\n\n// ---------------------------------------------------------------------------\n// Callback interface\n// ---------------------------------------------------------------------------\n\nexport interface InteractionCallbacks {\n onTransformChange(transform: ZoomTransform): void;\n onHoverChange(nodeId: string | null): void;\n /** Called during mouse move when no node is hit, with graph-space coordinates for edge hit testing. */\n onBackgroundHover?(graphX: number, graphY: number, screenX: number, screenY: number): void;\n onSelectionChange(nodeIds: string[]): void;\n onNodeDragStart(nodeId: string): void;\n onNodeDrag(nodeId: string, x: number, y: number): void;\n onNodeDragEnd(nodeId: string): void;\n onDoubleClick(nodeId: string): void;\n /** Pointer moved over the canvas (graph-space coords). Used by cursor repulsion. */\n onPointerMove?(graphX: number, graphY: number): void;\n /** Pointer left the canvas. Used to deactivate cursor repulsion. */\n onPointerLeave?(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Internal state\n// ---------------------------------------------------------------------------\n\ninterface DragState {\n nodeId: string;\n started: boolean;\n}\n\ninterface PanState {\n startX: number;\n startY: number;\n}\n\n// ---------------------------------------------------------------------------\n// GraphInteractionManager\n// ---------------------------------------------------------------------------\n\nexport class GraphInteractionManager {\n private canvas: HTMLCanvasElement;\n private spatialIndex: SpatialIndex;\n private callbacks: InteractionCallbacks;\n private transform = ZoomTransform.identity();\n\n private dragState: DragState | null = null;\n private panState: PanState | null = null;\n private mousedownNodeId: string | null = null;\n private selectedIds: Set<string> = new Set();\n\n // Touch state\n private lastTouchDist: number | null = null;\n private lastTouchCenter: { x: number; y: number } | null = null;\n\n // Bound handlers for cleanup\n private boundWheel: (e: WheelEvent) => void;\n private boundMouseDown: (e: MouseEvent) => void;\n private boundMouseMove: (e: MouseEvent) => void;\n private boundMouseUp: (e: MouseEvent) => void;\n private boundDblClick: (e: MouseEvent) => void;\n private boundTouchStart: (e: TouchEvent) => void;\n private boundTouchMove: (e: TouchEvent) => void;\n private boundTouchEnd: (e: TouchEvent) => void;\n private boundMouseLeave: (e: MouseEvent) => void;\n\n constructor(\n canvas: HTMLCanvasElement,\n spatialIndex: SpatialIndex,\n callbacks: InteractionCallbacks,\n ) {\n this.canvas = canvas;\n this.spatialIndex = spatialIndex;\n this.callbacks = callbacks;\n\n // Bind handlers\n this.boundWheel = this.onWheel.bind(this);\n this.boundMouseDown = this.onMouseDown.bind(this);\n this.boundMouseMove = this.onMouseMove.bind(this);\n this.boundMouseUp = this.onMouseUp.bind(this);\n this.boundMouseLeave = this.onMouseLeave.bind(this);\n this.boundDblClick = this.onDblClick.bind(this);\n this.boundTouchStart = this.onTouchStart.bind(this);\n this.boundTouchMove = this.onTouchMove.bind(this);\n this.boundTouchEnd = this.onTouchEnd.bind(this);\n\n // Attach event listeners\n canvas.addEventListener('wheel', this.boundWheel, { passive: false });\n canvas.addEventListener('mousedown', this.boundMouseDown);\n canvas.addEventListener('mousemove', this.boundMouseMove);\n canvas.addEventListener('mouseup', this.boundMouseUp);\n canvas.addEventListener('mouseleave', this.boundMouseLeave);\n canvas.addEventListener('dblclick', this.boundDblClick);\n canvas.addEventListener('touchstart', this.boundTouchStart, {\n passive: false,\n });\n canvas.addEventListener('touchmove', this.boundTouchMove, {\n passive: false,\n });\n canvas.addEventListener('touchend', this.boundTouchEnd);\n }\n\n setTransform(transform: ZoomTransform): void {\n this.transform = transform;\n }\n\n getTransform(): ZoomTransform {\n return this.transform;\n }\n\n /**\n * Replace the internal selection set. Used by the mount to prune deleted ids\n * after a data update so a later shift-click can't resurrect them through\n * `onSelectionChange`. Does NOT fire `onSelectionChange` — the caller owns any\n * downstream sync (the mount already holds the pruned set).\n */\n setSelection(ids: string[]): void {\n this.selectedIds = new Set(ids);\n }\n\n destroy(): void {\n this.canvas.removeEventListener('wheel', this.boundWheel);\n this.canvas.removeEventListener('mousedown', this.boundMouseDown);\n this.canvas.removeEventListener('mousemove', this.boundMouseMove);\n this.canvas.removeEventListener('mouseup', this.boundMouseUp);\n this.canvas.removeEventListener('mouseleave', this.boundMouseLeave);\n this.canvas.removeEventListener('dblclick', this.boundDblClick);\n this.canvas.removeEventListener('touchstart', this.boundTouchStart);\n this.canvas.removeEventListener('touchmove', this.boundTouchMove);\n this.canvas.removeEventListener('touchend', this.boundTouchEnd);\n }\n\n // -------------------------------------------------------------------------\n // Mouse handlers\n // -------------------------------------------------------------------------\n\n private canvasXY(e: MouseEvent): { x: number; y: number } {\n const rect = this.canvas.getBoundingClientRect();\n return { x: e.clientX - rect.left, y: e.clientY - rect.top };\n }\n\n private hitTest(screenX: number, screenY: number): string | null {\n const graph = this.transform.screenToGraph(screenX, screenY);\n const node = this.spatialIndex.findNearest(graph.x, graph.y, HIT_DISTANCE / this.transform.k);\n return node?.id ?? null;\n }\n\n private onWheel(e: WheelEvent): void {\n e.preventDefault();\n const { x, y } = this.canvasXY(e);\n const factor = e.deltaY * ZOOM_STEP;\n const newK = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, this.transform.k * (1 + factor)));\n this.transform = this.transform.zoomAt(newK, x, y);\n this.callbacks.onTransformChange(this.transform);\n }\n\n private onMouseDown(e: MouseEvent): void {\n const { x, y } = this.canvasXY(e);\n const hitId = this.hitTest(x, y);\n\n if (hitId) {\n // Start potential node drag\n this.dragState = { nodeId: hitId, started: false };\n this.mousedownNodeId = hitId;\n } else {\n // Start pan\n this.panState = { startX: x, startY: y };\n this.mousedownNodeId = null;\n }\n }\n\n private onMouseMove(e: MouseEvent): void {\n const { x, y } = this.canvasXY(e);\n\n // Feed the pointer position (graph-space) to cursor repulsion, if wired.\n // Fires on every move; the mount owns throttling and the node-count gate.\n if (this.callbacks.onPointerMove) {\n const gp = this.transform.screenToGraph(x, y);\n this.callbacks.onPointerMove(gp.x, gp.y);\n }\n\n if (this.dragState) {\n const graph = this.transform.screenToGraph(x, y);\n if (!this.dragState.started) {\n this.dragState.started = true;\n this.callbacks.onNodeDragStart(this.dragState.nodeId);\n }\n this.callbacks.onNodeDrag(this.dragState.nodeId, graph.x, graph.y);\n return;\n }\n\n if (this.panState) {\n const dx = x - this.panState.startX;\n const dy = y - this.panState.startY;\n this.transform = this.transform.pan(dx, dy);\n this.panState = { startX: x, startY: y };\n this.callbacks.onTransformChange(this.transform);\n return;\n }\n\n // Hover detection\n const hitId = this.hitTest(x, y);\n this.callbacks.onHoverChange(hitId);\n\n // If no node hit, check edges via callback\n if (!hitId) {\n const graph = this.transform.screenToGraph(x, y);\n this.callbacks.onBackgroundHover?.(graph.x, graph.y, x, y);\n }\n\n // Update cursor\n this.canvas.style.cursor = hitId ? 'pointer' : 'default';\n }\n\n private onMouseUp(e: MouseEvent): void {\n const { x, y } = this.canvasXY(e);\n\n if (this.dragState) {\n if (this.dragState.started) {\n this.callbacks.onNodeDragEnd(this.dragState.nodeId);\n } else {\n // Was a click on a node (no drag movement)\n this.handleNodeClick(this.dragState.nodeId, e.shiftKey);\n }\n this.dragState = null;\n return;\n }\n\n if (this.panState) {\n this.panState = null;\n\n // If mouse up is on background (no node), treat as background click\n if (!this.mousedownNodeId) {\n const hitId = this.hitTest(x, y);\n if (!hitId) {\n // Background click: clear selection\n this.selectedIds.clear();\n this.callbacks.onSelectionChange([]);\n }\n }\n return;\n }\n }\n\n private onDblClick(e: MouseEvent): void {\n const { x, y } = this.canvasXY(e);\n const hitId = this.hitTest(x, y);\n if (hitId) {\n this.callbacks.onDoubleClick(hitId);\n }\n }\n\n private onMouseLeave(_e: MouseEvent): void {\n this.callbacks.onHoverChange(null);\n this.canvas.style.cursor = 'default';\n // Deactivate cursor repulsion when the pointer leaves the canvas.\n this.callbacks.onPointerLeave?.();\n\n // Cancel any in-progress pan\n if (this.panState) {\n this.panState = null;\n }\n }\n\n private handleNodeClick(nodeId: string, shiftKey: boolean): void {\n if (shiftKey) {\n // Toggle node in multi-select\n if (this.selectedIds.has(nodeId)) {\n this.selectedIds.delete(nodeId);\n } else {\n this.selectedIds.add(nodeId);\n }\n } else {\n // Single select\n this.selectedIds.clear();\n this.selectedIds.add(nodeId);\n }\n\n this.callbacks.onSelectionChange([...this.selectedIds]);\n }\n\n // -------------------------------------------------------------------------\n // Touch handlers\n // -------------------------------------------------------------------------\n\n private onTouchStart(e: TouchEvent): void {\n e.preventDefault();\n\n if (e.touches.length === 2) {\n // Pinch-zoom start\n const [t0, t1] = [e.touches[0], e.touches[1]];\n this.lastTouchDist = Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY);\n this.lastTouchCenter = {\n x: (t0.clientX + t1.clientX) / 2,\n y: (t0.clientY + t1.clientY) / 2,\n };\n } else if (e.touches.length === 1) {\n const touch = e.touches[0];\n const rect = this.canvas.getBoundingClientRect();\n const x = touch.clientX - rect.left;\n const y = touch.clientY - rect.top;\n\n const hitId = this.hitTest(x, y);\n if (hitId) {\n this.mousedownNodeId = hitId;\n } else {\n this.panState = { startX: x, startY: y };\n this.mousedownNodeId = null;\n }\n }\n }\n\n private onTouchMove(e: TouchEvent): void {\n e.preventDefault();\n\n if (e.touches.length === 2 && this.lastTouchDist !== null) {\n const [t0, t1] = [e.touches[0], e.touches[1]];\n const newDist = Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY);\n const rect = this.canvas.getBoundingClientRect();\n const centerX = (t0.clientX + t1.clientX) / 2 - rect.left;\n const centerY = (t0.clientY + t1.clientY) / 2 - rect.top;\n\n const scale = newDist / this.lastTouchDist;\n const newK = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, this.transform.k * scale));\n this.transform = this.transform.zoomAt(newK, centerX, centerY);\n\n // Pan from center movement\n if (this.lastTouchCenter) {\n const dx = centerX - (this.lastTouchCenter.x - rect.left);\n const dy = centerY - (this.lastTouchCenter.y - rect.top);\n this.transform = this.transform.pan(dx, dy);\n }\n\n this.lastTouchDist = newDist;\n this.lastTouchCenter = {\n x: (t0.clientX + t1.clientX) / 2,\n y: (t0.clientY + t1.clientY) / 2,\n };\n this.callbacks.onTransformChange(this.transform);\n } else if (e.touches.length === 1 && this.panState) {\n const touch = e.touches[0];\n const rect = this.canvas.getBoundingClientRect();\n const x = touch.clientX - rect.left;\n const y = touch.clientY - rect.top;\n\n const dx = x - this.panState.startX;\n const dy = y - this.panState.startY;\n this.transform = this.transform.pan(dx, dy);\n this.panState = { startX: x, startY: y };\n this.callbacks.onTransformChange(this.transform);\n }\n }\n\n private onTouchEnd(e: TouchEvent): void {\n if (e.touches.length === 0) {\n // Tap-select\n if (this.mousedownNodeId && !this.panState) {\n this.handleNodeClick(this.mousedownNodeId, false);\n } else if (!this.mousedownNodeId && this.panState) {\n // Background tap: clear selection\n this.selectedIds.clear();\n this.callbacks.onSelectionChange([]);\n }\n\n this.panState = null;\n this.mousedownNodeId = null;\n this.lastTouchDist = null;\n this.lastTouchCenter = null;\n }\n }\n}\n","/**\n * Keyboard navigation for the graph canvas.\n *\n * Provides accessible keyboard control: Tab to focus, arrow keys to\n * navigate between adjacent nodes (following edges), Enter to select,\n * Escape to clear, +/- to zoom, Home to fit all, / to focus search.\n */\n\nimport type { PositionedNode } from './types';\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface KeyboardNavOptions {\n canvas: HTMLCanvasElement;\n getNodes(): PositionedNode[];\n getSelectedIds(): string[];\n getAdjacency(): Map<string, Set<string>>;\n onSelect(nodeId: string): void;\n onDeselect(): void;\n onZoom(direction: 'in' | 'out'): void;\n onFitAll(): void;\n onFocusSearch?(): void;\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\n/**\n * Attach keyboard navigation to a graph canvas.\n * Returns a cleanup function that removes all listeners.\n */\nexport function attachGraphKeyboardNav(options: KeyboardNavOptions): () => void {\n const {\n canvas,\n getNodes,\n getSelectedIds,\n getAdjacency,\n onSelect,\n onDeselect,\n onZoom,\n onFitAll,\n onFocusSearch,\n } = options;\n\n let focusedNodeId: string | null = null;\n\n // Make canvas focusable\n if (!canvas.hasAttribute('tabindex')) {\n canvas.setAttribute('tabindex', '0');\n }\n\n function findNodeById(id: string): PositionedNode | undefined {\n return getNodes().find((n) => n.id === id);\n }\n\n /**\n * Given a set of neighbor node ids, pick the one that best matches\n * the arrow key direction relative to the current focused node.\n */\n function pickDirectionalNeighbor(\n fromNode: PositionedNode,\n neighborIds: Set<string>,\n direction: 'up' | 'down' | 'left' | 'right',\n ): string | null {\n const nodes = getNodes();\n const candidates = nodes.filter((n) => neighborIds.has(n.id));\n if (candidates.length === 0) return null;\n\n // Score each candidate by how well it matches the desired direction\n let best: PositionedNode | null = null;\n let bestScore = -Infinity;\n\n for (const c of candidates) {\n const dx = c.x - fromNode.x;\n const dy = c.y - fromNode.y;\n let score: number;\n\n switch (direction) {\n case 'right':\n score = dx - Math.abs(dy) * 0.5;\n break;\n case 'left':\n score = -dx - Math.abs(dy) * 0.5;\n break;\n case 'down':\n score = dy - Math.abs(dx) * 0.5;\n break;\n case 'up':\n score = -dy - Math.abs(dx) * 0.5;\n break;\n }\n\n if (score > bestScore) {\n bestScore = score;\n best = c;\n }\n }\n\n return best?.id ?? null;\n }\n\n function onKeyDown(e: KeyboardEvent): void {\n switch (e.key) {\n case 'Tab': {\n // Focus first/selected node\n const selected = getSelectedIds();\n const nodes = getNodes();\n if (nodes.length === 0) return;\n\n if (selected.length > 0) {\n focusedNodeId = selected[0];\n } else if (!focusedNodeId || !findNodeById(focusedNodeId)) {\n focusedNodeId = nodes[0].id;\n }\n\n e.preventDefault();\n break;\n }\n\n case 'ArrowUp':\n case 'ArrowDown':\n case 'ArrowLeft':\n case 'ArrowRight': {\n if (!focusedNodeId) return;\n e.preventDefault();\n\n const focusedNode = findNodeById(focusedNodeId);\n if (!focusedNode) return;\n\n const adjacency = getAdjacency();\n const neighbors = adjacency.get(focusedNodeId);\n if (!neighbors || neighbors.size === 0) return;\n\n const dirMap: Record<string, 'up' | 'down' | 'left' | 'right'> = {\n ArrowUp: 'up',\n ArrowDown: 'down',\n ArrowLeft: 'left',\n ArrowRight: 'right',\n };\n\n const nextId = pickDirectionalNeighbor(focusedNode, neighbors, dirMap[e.key]);\n if (nextId) {\n focusedNodeId = nextId;\n onSelect(nextId);\n }\n break;\n }\n\n case 'Enter': {\n if (focusedNodeId) {\n e.preventDefault();\n const selected = getSelectedIds();\n if (selected.includes(focusedNodeId)) {\n onDeselect();\n } else {\n onSelect(focusedNodeId);\n }\n }\n break;\n }\n\n case 'Escape': {\n e.preventDefault();\n focusedNodeId = null;\n onDeselect();\n break;\n }\n\n case '+':\n case '=': {\n e.preventDefault();\n onZoom('in');\n break;\n }\n\n case '-':\n case '_': {\n e.preventDefault();\n onZoom('out');\n break;\n }\n\n case 'Home': {\n e.preventDefault();\n onFitAll();\n break;\n }\n\n case '/': {\n if (onFocusSearch) {\n e.preventDefault();\n onFocusSearch();\n }\n break;\n }\n }\n }\n\n canvas.addEventListener('keydown', onKeyDown);\n\n // Return cleanup function\n return () => {\n canvas.removeEventListener('keydown', onKeyDown);\n };\n}\n","/**\n * Interactive built-in graph legend.\n *\n * Replaces the old innerHTML legend build in graph-mount with a keyboard- and\n * pointer-accessible control. Node-category rows are buttons (`aria-pressed`)\n * that toggle emphasis; edge-category rows are non-interactive line swatches.\n *\n * The legend is a thin view: it holds no highlight state of its own. Toggling a\n * row calls back into the mount, which owns the single highlight slot and\n * re-renders the legend from the resulting active-category set. Hovering a row\n * previews that category via the same highlight code path.\n */\n\n/** Escape a string for safe interpolation into innerHTML. */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#x27;');\n}\n\n/** A node-category legend row. */\nexport interface GraphLegendNodeEntry {\n label: string;\n color: string;\n count?: number;\n /** Whether this category is currently emphasized (drives the inactive style). */\n active: boolean;\n}\n\n/** An edge-category legend row (non-interactive). */\nexport interface GraphLegendEdgeEntry {\n label: string;\n color: string;\n count?: number;\n}\n\n/** Data the legend renders. */\nexport interface GraphLegendViewData {\n nodes: GraphLegendNodeEntry[];\n edges: GraphLegendEdgeEntry[];\n}\n\n/** Callbacks the legend fires; the mount owns the resulting state. */\nexport interface GraphLegendCallbacks {\n /** Whether node rows toggle emphasis. When false, rows are static labels. */\n interactive: boolean;\n /** Whether to show per-category counts. */\n counts: boolean;\n /** Toggle a node category's emphasis. */\n onToggle(value: string): void;\n /** Hover a node category (null on leave) for a live preview. */\n onHover(value: string | null): void;\n}\n\nexport interface GraphLegendController {\n /** Re-render from fresh data (e.g. after a toggle or update()). */\n update(data: GraphLegendViewData): void;\n /** Remove listeners and clear the host. */\n destroy(): void;\n}\n\n/**\n * Create an interactive legend inside `host`. Returns a controller with\n * `update`/`destroy`. The host is fully owned by the legend (cleared on each\n * update and on destroy).\n */\nexport function createGraphLegend(\n host: HTMLElement,\n data: GraphLegendViewData,\n callbacks: GraphLegendCallbacks,\n): GraphLegendController {\n const listeners: Array<() => void> = [];\n\n function render(view: GraphLegendViewData): void {\n teardownListeners();\n host.replaceChildren();\n\n if (view.nodes.length === 0 && view.edges.length === 0) {\n host.style.display = 'none';\n return;\n }\n host.style.display = '';\n\n for (const entry of view.nodes) {\n host.appendChild(nodeRow(entry));\n }\n for (const entry of view.edges) {\n host.appendChild(edgeRow(entry));\n }\n }\n\n function nodeRow(entry: GraphLegendNodeEntry): HTMLElement {\n const interactive = callbacks.interactive;\n const el = document.createElement(interactive ? 'button' : 'div');\n el.className = 'oc-graph-legend-item';\n if (!entry.active) el.classList.add('oc-graph-legend-item--inactive');\n\n if (interactive) {\n const btn = el as HTMLButtonElement;\n btn.type = 'button';\n btn.setAttribute('aria-pressed', String(entry.active));\n const onClick = () => callbacks.onToggle(entry.label);\n const onEnter = () => callbacks.onHover(entry.label);\n const onLeave = () => callbacks.onHover(null);\n btn.addEventListener('click', onClick);\n btn.addEventListener('mouseenter', onEnter);\n btn.addEventListener('mouseleave', onLeave);\n listeners.push(() => {\n btn.removeEventListener('click', onClick);\n btn.removeEventListener('mouseenter', onEnter);\n btn.removeEventListener('mouseleave', onLeave);\n });\n }\n\n el.innerHTML =\n `<span class=\"oc-graph-legend-swatch\" style=\"background:${escapeHtml(entry.color)}\"></span>` +\n `<span class=\"oc-graph-legend-label\">${escapeHtml(entry.label)}</span>` +\n (callbacks.counts && entry.count != null\n ? `<span class=\"oc-graph-legend-count\">${entry.count.toLocaleString()}</span>`\n : '');\n return el;\n }\n\n function edgeRow(entry: GraphLegendEdgeEntry): HTMLElement {\n const el = document.createElement('div');\n el.className = 'oc-graph-legend-item oc-graph-legend-item--edge';\n el.innerHTML =\n `<span class=\"oc-graph-legend-swatch oc-graph-legend-swatch--line\" style=\"background:${escapeHtml(entry.color)}\"></span>` +\n `<span class=\"oc-graph-legend-label\">${escapeHtml(entry.label)}</span>` +\n (callbacks.counts && entry.count != null\n ? `<span class=\"oc-graph-legend-count\">${entry.count.toLocaleString()}</span>`\n : '');\n return el;\n }\n\n function teardownListeners(): void {\n for (const off of listeners) off();\n listeners.length = 0;\n }\n\n render(data);\n\n return {\n update: render,\n destroy(): void {\n teardownListeners();\n host.replaceChildren();\n },\n };\n}\n","/**\n * Graph search manager.\n *\n * Provides case-insensitive substring matching against node labels/ids.\n * Returns a Set of matching node ids that the renderer uses to dim\n * non-matching nodes and edges.\n */\n\nexport class GraphSearchManager {\n private matchedIds: Set<string> | null = null;\n /** The last active (non-empty) query, so a data update can re-run it. */\n private query: string | null = null;\n\n /**\n * Search for nodes matching the query string.\n * Returns a Set of matching node ids, or an empty set if nothing matches.\n */\n search(query: string, nodes: Array<{ id: string; label?: string }>): Set<string> {\n const q = query.toLowerCase().trim();\n\n if (q === '') {\n this.matchedIds = null;\n this.query = null;\n return new Set();\n }\n\n this.query = query;\n\n const matches = new Set<string>();\n for (const node of nodes) {\n const label = (node.label ?? '').toLowerCase();\n const id = node.id.toLowerCase();\n if (label.includes(q) || id.includes(q)) {\n matches.add(node.id);\n }\n }\n\n this.matchedIds = matches;\n return matches;\n }\n\n /**\n * Clear the current search.\n * Returns null to indicate no active search.\n */\n clearSearch(): Set<string> | null {\n this.matchedIds = null;\n this.query = null;\n return null;\n }\n\n /** Get the current set of matched ids, or null if no search is active. */\n getMatches(): Set<string> | null {\n return this.matchedIds;\n }\n\n /** The last active query string, or null when no search is active. */\n getQuery(): string | null {\n return this.query;\n }\n}\n","/**\n * Deterministic seeded initial layout for the force simulation.\n *\n * Without seeding, d3-force places nodes on a phyllotaxis spiral, so the settled\n * layout depends on node ORDER and isn't reproducible across data reshapes. With\n * a seed, each node's start position is a pure function of `(id, seed, community)`,\n * so the same spec + seed produces the same settled layout — and adding a node\n * leaves the others' start positions untouched (their hash inputs don't change).\n *\n * Determinism is scoped PER EXECUTION PATH (worker OR sync): identical within a\n * path, not guaranteed identical across the two.\n */\n\nimport type { SimNode } from './worker-protocol';\n\n/** FNV-1a 32-bit hash of a string. Fast, dependency-free, well-distributed. */\nexport function hash32(str: string): number {\n let h = 0x811c9dc5; // FNV offset basis\n for (let i = 0; i < str.length; i++) {\n h ^= str.charCodeAt(i);\n // 32-bit FNV prime multiply via shifts (stays within Math.imul's 32-bit range).\n h = Math.imul(h, 0x01000193);\n }\n // Coerce to an unsigned 32-bit integer.\n return h >>> 0;\n}\n\n/** Map a 32-bit hash to a float in [0, 1). */\nfunction unit(h: number): number {\n return (h >>> 0) / 0x100000000;\n}\n\n/**\n * Seed each node's initial `x`/`y` as a pure function of `(id, seed, community)`.\n * Mutates the nodes in place (matching how d3 mutates its node objects).\n *\n * Placement is a uniform disc: `r = R0·sqrt(u1)`, `θ = u2·2π`. When a node has a\n * `community`, its disc center is biased by `0.6·R0` toward a per-community angle,\n * so clusters start near their eventual region and settle faster/cleaner.\n *\n * DEVIATION from the plan's `R0 ≈ 10·√n`: R0 is a FIXED constant, not derived\n * from the live node count. An n-dependent R0 would rescale every node's start\n * position whenever a node is added, which directly violates the reproducibility\n * guarantee (\"adding a node leaves the others' seed positions unchanged\"). Since\n * a deterministic seed is pointless if one insertion reshuffles the layout, the\n * per-node determinism wins; d3-force's charge repulsion still spreads the cloud\n * to fill the viewport regardless of the initial disc radius. R0 is sized for a\n * ~900-node graph (10·√900 = 300), a reasonable middle for the initial cloud.\n */\nconst R0 = 300;\n\nexport function seedNodePositions(nodes: SimNode[], seed: number): void {\n const n = nodes.length;\n if (n === 0) return;\n\n for (const node of nodes) {\n // Two independent hash streams per node from distinct salts.\n const u1 = unit(hash32(`${node.id}|${seed}|r`));\n const u2 = unit(hash32(`${node.id}|${seed}|t`));\n const r = R0 * Math.sqrt(u1);\n const theta = u2 * Math.PI * 2;\n\n let cx = 0;\n let cy = 0;\n if (node.community) {\n // Per-community region angle: stable across runs, varies with seed.\n const ca = unit(hash32(`${node.community}|${seed}`)) * Math.PI * 2;\n const bias = 0.6 * R0;\n cx = Math.cos(ca) * bias;\n cy = Math.sin(ca) * bias;\n }\n\n node.x = cx + r * Math.cos(theta);\n node.y = cy + r * Math.sin(theta);\n }\n}\n","/**\n * Shared graph mount shell.\n *\n * The wrapper, chrome band, legend slot, tooltip manager and resize wiring are\n * identical whether the graph paints on a 2D canvas or a WebGL scene, so they\n * live here and both renderers build on top. The renderer owns only the surface\n * it mounts between the chrome and the legend, plus its simulation, camera and\n * interaction state.\n *\n * See {@link GraphShell} in ./renderer-registry for the contract.\n */\n\nimport type { GraphSpec } from '@opendata-ai/openchart-core';\nimport type { GraphCompilation } from '@opendata-ai/openchart-engine';\nimport type { GraphMountOptions } from '../graph-mount';\nimport { observeResize } from '../resize-observer';\nimport { resolveDarkMode } from '../resolve-dark-mode';\nimport { resolvedSurface } from '../theme-tokens';\nimport { createTooltipManager } from '../tooltip';\nimport type { GraphShell } from './renderer-registry';\n\n/** Minimum surface height, so a collapsed container still paints something. */\nconst MIN_SURFACE_HEIGHT = 200;\n\n/**\n * Container size used for compilation. Falls back to 600x400 for an unmeasured\n * container (happy-dom, or a mount before first layout).\n */\nexport function getContainerDimensions(container: HTMLElement): {\n width: number;\n height: number;\n} {\n const rect = container.getBoundingClientRect();\n return {\n width: Math.max(rect.width || 600, 100),\n height: Math.max(rect.height || 400, 100),\n };\n}\n\n/**\n * Build the wrapper/chrome/legend/tooltip scaffolding and mount it into the\n * container. The returned shell is handed to whichever renderer the compilation\n * selected.\n */\nexport function createGraphShell(\n container: HTMLElement,\n spec: GraphSpec,\n compilation: GraphCompilation,\n options: GraphMountOptions | undefined,\n warn: (message: string) => void,\n): GraphShell {\n const isDark = resolveDarkMode(options?.darkMode);\n\n const wrapper = document.createElement('div');\n wrapper.className = isDark ? 'oc-graph-wrapper oc-dark' : 'oc-graph-wrapper';\n if (isDark) {\n container.classList.add('oc-dark');\n } else {\n container.classList.remove('oc-dark');\n }\n\n // Apply theme colors as CSS custom properties so chrome HTML picks them up.\n // Without this, consumer-supplied theme.colors.text only affects canvas-drawn\n // labels but not the HTML title/subtitle which read from --oc-text.\n const resolvedTheme = compilation.theme;\n if (resolvedTheme) {\n const s = wrapper.style;\n // The graph paints on an opaque canvas, so a transparent theme background\n // resolves to the mode's --oc-bg token. This is the single source for the\n // graph surface: the node knockout rings are cut in the same color.\n s.setProperty('--oc-bg', resolvedSurface(resolvedTheme));\n s.setProperty('--oc-text', resolvedTheme.colors.text);\n s.setProperty('--oc-text-secondary', resolvedTheme.colors.neutral.secondary);\n s.setProperty('--oc-text-muted', resolvedTheme.colors.axis);\n s.setProperty('--oc-border', resolvedTheme.colors.neutral.border);\n s.setProperty('--oc-font-family', resolvedTheme.fonts.family);\n s.fontFamily = resolvedTheme.fonts.family;\n }\n\n const chromeEl = document.createElement('div');\n chromeEl.className = 'oc-graph-chrome';\n wrapper.appendChild(chromeEl);\n\n // The legend slot is created up front so `mountSurface` can insert the\n // renderer's surface before it and keep the chrome / surface / legend order.\n // The mount option wins over the spec, so a host can override a spec it\n // doesn't own.\n const legendSetting = options?.legend ?? spec.legend;\n let legendEl: HTMLElement | null = null;\n if (legendSetting !== false) {\n legendEl = document.createElement('div');\n legendEl.className = 'oc-graph-legend';\n wrapper.appendChild(legendEl);\n }\n\n container.appendChild(wrapper);\n\n const tooltipManager = options?.tooltip !== false ? createTooltipManager(wrapper) : null;\n\n const shell: GraphShell = {\n container,\n wrapper,\n chromeEl,\n legendEl,\n tooltipManager,\n isDark,\n\n mountSurface(el: HTMLElement): void {\n if (legendEl) wrapper.insertBefore(el, legendEl);\n else wrapper.appendChild(el);\n },\n\n renderChrome(next: GraphCompilation): void {\n let html = '';\n\n if (next.chrome.title) {\n html += `<h2 class=\"oc-title\">${escapeHtml(next.chrome.title.text)}</h2>`;\n }\n if (next.chrome.subtitle) {\n html += `<p class=\"oc-subtitle\">${escapeHtml(next.chrome.subtitle.text)}</p>`;\n }\n\n chromeEl.innerHTML = html;\n\n // Hide chrome if empty\n chromeEl.style.display = html ? '' : 'none';\n },\n\n /**\n * Keep the chrome block out of the legend's column: the title/subtitle wrap\n * before they reach the legend box instead of running underneath it. No-op\n * when there's no legend (or it has no measurable width, e.g. in happy-dom).\n */\n syncChromeInset(): void {\n const legendW = legendEl?.offsetWidth ?? 0;\n chromeEl.style.right = legendW > 0 ? `${legendW + 24}px` : '';\n },\n\n getSize(): { width: number; height: number } {\n const { width, height } = getContainerDimensions(container);\n return { width, height: Math.max(height, MIN_SURFACE_HEIGHT) };\n },\n\n observeResize(callback: () => void): () => void {\n if (options?.responsive === false) return () => {};\n return observeResize(container, () => {\n callback();\n });\n },\n\n warn,\n\n destroy(): void {\n tooltipManager?.destroy();\n if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper);\n container.classList.remove('oc-dark');\n },\n };\n\n return shell;\n}\n\nexport function escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;');\n}\n","/**\n * SimulationManager: spawns a Web Worker for the force simulation,\n * or falls back to synchronous d3-force on the main thread.\n *\n * The worker is always preferred when available. Synchronous fallback\n * is only used when Web Workers are unavailable (SSR, test environments).\n * The sync path batches ticks via requestAnimationFrame to avoid\n * blocking the main thread.\n */\n\nimport {\n forceCenter,\n forceCollide,\n forceLink,\n forceManyBody,\n forceSimulation,\n forceX,\n forceY,\n type Simulation,\n type SimulationNodeDatum,\n} from 'd3-force';\n\nimport type { SimEdge, SimNode, WorkerOutMessage, WorkerSimulationConfig } from './worker-protocol';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst SYNC_TICKS_PER_BATCH = 15;\n/** Absolute ceiling on the derived sync tick cap (guards tiny alphaDecay). */\nconst SYNC_MAX_TICKS_CEIL = 800;\n/** d3's default alphaMin — the alpha at which forceSimulation stops. */\nconst DEFAULT_ALPHA_MIN = 0.001;\n/** Default warmup budget (ms) mirroring the engine's DEFAULT_WARMUP_BUDGET_MS. */\nconst DEFAULT_WARMUP_BUDGET_MS = 250;\n\n/**\n * Ticks d3 needs to reach `alphaMin` from alpha=1 for a given `alphaDecay`,\n * ceilinged. d3 stops when `alpha < alphaMin`, where `alpha *= (1 - alphaDecay)`\n * each tick, so `alpha(n) = (1 - alphaDecay)^n`. Solving `(1 - d)^n = alphaMin`\n * gives `n = log(alphaMin) / log(1 - d)`. This aligns the sync cap with the\n * worker path (which runs to `alpha < alphaMin`) instead of a fixed 300, so a\n * `settle: 'thorough'` graph (alphaDecay 0.01, ~690 ticks) settles fully on both.\n *\n * Determinism is scoped PER EXECUTION PATH: same spec + seed ⇒ identical settled\n * layout within a given path. Worker-vs-sync parity is not guaranteed as-is.\n */\nexport function ticksToAlphaMin(alphaDecay: number, alphaMin = DEFAULT_ALPHA_MIN): number {\n if (!(alphaDecay > 0) || alphaDecay >= 1) return SYNC_MAX_TICKS_CEIL;\n const n = Math.ceil(Math.log(alphaMin) / Math.log(1 - alphaDecay));\n return Math.min(SYNC_MAX_TICKS_CEIL, Math.max(1, n));\n}\n\n// ---------------------------------------------------------------------------\n// Internal node shape for sync simulation\n// ---------------------------------------------------------------------------\n\ninterface SyncNode extends SimulationNodeDatum {\n id: string;\n radius: number;\n community?: string;\n fx?: number | null;\n fy?: number | null;\n}\n\n// ---------------------------------------------------------------------------\n// Cluster force (duplicated in simulation-worker.ts for the Web Worker path.\n// Worker can't import from workspace packages, so both copies must stay in sync.)\n// ---------------------------------------------------------------------------\n\nfunction forceCluster(nodes: SyncNode[], strength: number) {\n return (alpha: number) => {\n const cx = new Map<string, number>();\n const cy = new Map<string, number>();\n const count = new Map<string, number>();\n\n for (const node of nodes) {\n if (!node.community) continue;\n const c = node.community;\n cx.set(c, (cx.get(c) ?? 0) + (node.x ?? 0));\n cy.set(c, (cy.get(c) ?? 0) + (node.y ?? 0));\n count.set(c, (count.get(c) ?? 0) + 1);\n }\n\n for (const [c, n] of count) {\n cx.set(c, cx.get(c)! / n);\n cy.set(c, cy.get(c)! / n);\n }\n\n const k = strength * alpha;\n for (const node of nodes) {\n if (!node.community) continue;\n const targetX = cx.get(node.community)!;\n const targetY = cy.get(node.community)!;\n node.vx = (node.vx ?? 0) + (targetX - (node.x ?? 0)) * k;\n node.vy = (node.vy ?? 0) + (targetY - (node.y ?? 0)) * k;\n }\n };\n}\n\n// ---------------------------------------------------------------------------\n// Cursor-repulsion force (duplicated in simulation-worker.ts for the Web Worker\n// path. Worker can't import from workspace packages, so both copies stay in sync.)\n// ---------------------------------------------------------------------------\n\n/** Live pointer position + radius/strength, mutated by setPointer(). */\ninterface PointerState {\n x: number;\n y: number;\n active: boolean;\n radius: number;\n strength: number;\n}\n\nfunction forceCursor(nodes: SyncNode[], pointer: PointerState) {\n return (alpha: number) => {\n if (!pointer.active || pointer.radius <= 0) return;\n const r2 = pointer.radius * pointer.radius;\n const k = pointer.strength * alpha;\n for (const node of nodes) {\n const dx = (node.x ?? 0) - pointer.x;\n const dy = (node.y ?? 0) - pointer.y;\n const dist2 = dx * dx + dy * dy;\n if (dist2 >= r2) continue;\n const dist = Math.sqrt(dist2) || 1e-6;\n // Linear falloff: full push at the pointer, zero at the radius edge.\n const falloff = (pointer.radius - dist) / pointer.radius;\n const push = (k * falloff) / dist;\n node.vx = (node.vx ?? 0) + dx * push;\n node.vy = (node.vy ?? 0) + dy * push;\n }\n };\n}\n\n// ---------------------------------------------------------------------------\n// SimulationManager\n// ---------------------------------------------------------------------------\n\ntype TickCallback = (positions: Array<{ id: string; x: number; y: number }>, alpha: number) => void;\n\ntype SettledCallback = () => void;\n\nexport class SimulationManager {\n private worker: Worker | null = null;\n private syncSim: Simulation<SyncNode, undefined> | null = null;\n private syncNodes: SyncNode[] = [];\n private syncNodeMap: Map<string, SyncNode> = new Map();\n private tickCb: TickCallback | null = null;\n private settledCb: SettledCallback | null = null;\n private destroyed = false;\n private syncRafId: number | null = null;\n /** Derived per-graph cap on sync ticks, from alphaDecay via ticksToAlphaMin. */\n private syncMaxTicks = SYNC_MAX_TICKS_CEIL;\n /** True until the sync warmup loop has completed (nothing renders before then). */\n private syncWarmupPending = false;\n /** Remaining warmup ticks and the ms budget, consumed by the pre-reveal loop. */\n private syncWarmupTicks = 0;\n private syncWarmupBudgetMs = DEFAULT_WARMUP_BUDGET_MS;\n /** Injectable clock for the warmup ms budget (deterministic in tests). */\n private now: () => number =\n typeof performance !== 'undefined' ? () => performance.now() : () => Date.now();\n\n // Stored for worker->sync fallback\n private initNodes: SimNode[] = [];\n private initEdges: SimEdge[] = [];\n private initConfig: WorkerSimulationConfig | null = null;\n\n // Cursor-repulsion pointer state (sync path). radius=0 keeps the force inert.\n private pointer: PointerState = { x: 0, y: 0, active: false, radius: 0, strength: 0 };\n // Separately tracked alpha-target intents so drag and cursor don't stomp each\n // other. The higher intent wins; releasing one falls back to the other.\n private dragAlphaTarget = 0;\n private cursorAlphaTarget = 0;\n\n private constructor() {}\n\n /**\n * Create a SimulationManager. Uses Web Worker for large graphs,\n * synchronous fallback for small graphs or when Worker unavailable.\n *\n * `opts.now` injects a clock for the warmup ms budget (tests pass a fake one).\n */\n static create(\n nodes: SimNode[],\n edges: SimEdge[],\n config: WorkerSimulationConfig,\n opts?: { now?: () => number },\n ): SimulationManager {\n const mgr = new SimulationManager();\n if (opts?.now) mgr.now = opts.now;\n\n const useWorker = typeof Worker !== 'undefined';\n\n if (useWorker) {\n mgr.initWorker(nodes, edges, config);\n } else {\n mgr.initSync(nodes, edges, config);\n }\n\n return mgr;\n }\n\n /** Register a callback for position updates. */\n onTick(cb: TickCallback): void {\n this.tickCb = cb;\n }\n\n /** Register a callback for when the simulation has settled. */\n onSettled(cb: SettledCallback): void {\n this.settledCb = cb;\n }\n\n /** Reheat the simulation. */\n reheat(alpha?: number): void {\n if (this.destroyed) return;\n\n if (this.worker) {\n this.worker.postMessage({ type: 'reheat', alpha });\n } else if (this.syncSim) {\n this.syncSim.alpha(alpha ?? 0.3).restart();\n this.runSyncTicks();\n }\n }\n\n /**\n * Pin a node to fixed x/y coordinates.\n *\n * When `alphaTarget` is provided (springy drag), the sim is held warm so the\n * pinned node's neighbors follow springily. Omitting it posts a byte-identical\n * legacy message and leaves alpha untouched.\n */\n pinNode(id: string, x: number, y: number, alphaTarget?: number): void {\n if (this.destroyed) return;\n\n if (this.worker) {\n // Only include alphaTarget when springy, so the legacy message stays\n // byte-identical (and a stale worker sees exactly the old shape).\n this.worker.postMessage(\n alphaTarget != null\n ? { type: 'pin', nodeId: id, x, y, alphaTarget }\n : { type: 'pin', nodeId: id, x, y },\n );\n } else {\n const node = this.syncNodeMap.get(id);\n if (node) {\n node.fx = x;\n node.fy = y;\n }\n if (alphaTarget != null && this.syncSim) {\n this.dragAlphaTarget = alphaTarget;\n this.syncAlphaTarget();\n this.syncSim.restart();\n this.runSyncTicks();\n }\n }\n }\n\n /**\n * Unpin a node and reheat so forces settle it into equilibrium.\n *\n * When `alphaTarget` is provided (springy release), the sim cools back toward\n * that target instead of the legacy gentle reheat. Omitting it preserves the\n * exact legacy reheat behavior (and posts a byte-identical message).\n */\n unpinNode(id: string, alphaTarget?: number): void {\n if (this.destroyed) return;\n\n if (this.worker) {\n this.worker.postMessage(\n alphaTarget != null\n ? { type: 'unpin', nodeId: id, alphaTarget }\n : { type: 'unpin', nodeId: id },\n );\n } else {\n const node = this.syncNodeMap.get(id);\n if (node) {\n node.fx = null;\n node.fy = null;\n }\n if (alphaTarget != null) {\n // Springy release: cool toward the requested target (typically 0). Keep\n // the tick loop running so the sim actually eases down to the target;\n // the alpha-target path owns re-settling (no legacy reheat).\n this.dragAlphaTarget = alphaTarget;\n this.syncAlphaTarget();\n this.runSyncTicks();\n } else if (this.syncSim && this.syncSim.alpha() < 0.1) {\n // LEGACY path: gentle reheat, byte-identical to pre-springy behavior.\n this.syncSim.alpha(0.1).restart();\n this.runSyncTicks();\n }\n }\n }\n\n /**\n * Feed the cursor-repulsion force a pointer position. `active: false` clears\n * the force. No-op when the graph has no cursor force configured (radius 0).\n */\n setPointer(x: number, y: number, active: boolean): void {\n if (this.destroyed) return;\n\n if (this.worker) {\n this.worker.postMessage({ type: 'pointer', x, y, active });\n } else {\n // No cursor force configured (radius 0) → a true no-op; don't warm the sim\n // or the toy-force alpha would keep an otherwise-settled graph ticking.\n if (this.pointer.radius <= 0) return;\n this.pointer.x = x;\n this.pointer.y = y;\n this.pointer.active = active;\n this.cursorAlphaTarget = active ? 0.03 : 0;\n if (this.syncSim) {\n this.syncAlphaTarget();\n if (active) {\n this.syncSim.restart();\n this.runSyncTicks();\n }\n }\n }\n }\n\n /** Apply the max of the tracked alpha-target intents to the sync sim. */\n private syncAlphaTarget(): void {\n if (!this.syncSim) return;\n this.syncSim.alphaTarget(Math.max(this.dragAlphaTarget, this.cursorAlphaTarget));\n }\n\n /** Drag a node (pins it and reheats slightly). */\n dragNode(id: string, x: number, y: number): void {\n if (this.destroyed) return;\n\n if (this.worker) {\n this.worker.postMessage({ type: 'drag', nodeId: id, x, y });\n } else {\n const node = this.syncNodeMap.get(id);\n if (node) {\n node.fx = x;\n node.fy = y;\n }\n if (this.syncSim && this.syncSim.alpha() < 0.1) {\n this.syncSim.alpha(0.1).restart();\n this.runSyncTicks();\n }\n }\n }\n\n /** Tear down the simulation and release resources. */\n destroy(): void {\n this.destroyed = true;\n\n if (this.syncRafId !== null) {\n cancelAnimationFrame(this.syncRafId);\n this.syncRafId = null;\n }\n\n if (this.worker) {\n this.worker.postMessage({ type: 'stop' });\n this.worker.terminate();\n this.worker = null;\n }\n\n if (this.syncSim) {\n this.syncSim.stop();\n this.syncSim = null;\n }\n\n this.tickCb = null;\n this.settledCb = null;\n }\n\n // -------------------------------------------------------------------------\n // Worker path\n // -------------------------------------------------------------------------\n\n private initWorker(nodes: SimNode[], edges: SimEdge[], config: WorkerSimulationConfig): void {\n // Store for fallback if worker fails to load\n this.initNodes = nodes;\n this.initEdges = edges;\n this.initConfig = config;\n\n // Worker URL resolution:\n // - Built dist/ consumers: import.meta.url points at dist/index.js,\n // so ./simulation-worker.js resolves to dist/simulation-worker.js.\n // - Vite dev with source aliases (Ladle): import.meta.url points at\n // src/graph/simulation.ts, so ./simulation-worker.js doesn't exist.\n // The .js worker fails to load, and the onerror handler retries\n // with .ts which Vite transforms on the fly.\n // - Vite production build: detects `new Worker(new URL(...))` and\n // bundles the worker as a hashed .js asset.\n const initMsg = { type: 'init' as const, nodes, edges, config };\n const wireWorker = (worker: Worker) => {\n this.worker = worker;\n\n worker.onmessage = (event: MessageEvent<WorkerOutMessage>) => {\n if (this.destroyed) return;\n const msg = event.data;\n\n switch (msg.type) {\n case 'positions':\n this.tickCb?.(msg.nodes, msg.alpha);\n break;\n case 'settled':\n this.settledCb?.();\n break;\n case 'error':\n console.error('[SimulationManager] Worker error:', msg.message);\n break;\n }\n };\n\n worker.postMessage(initMsg);\n };\n\n try {\n const w = new Worker(new URL('./simulation-worker.js', import.meta.url), {\n type: 'module',\n });\n\n w.onerror = () => {\n // .js failed (likely Vite dev with source aliases). Try .ts.\n // The URL is constructed dynamically to prevent bundlers (Rollup)\n // from statically analyzing it and trying to resolve the .ts file\n // as an asset entry point in production builds.\n if (this.destroyed) return;\n w.terminate();\n this.worker = null;\n\n try {\n const tsUrl = new URL(import.meta.url.replace(/\\/[^/]+$/, '/simulation-worker.ts'));\n const w2 = new Worker(tsUrl, { type: 'module' });\n\n w2.onerror = () => {\n // Both .js and .ts failed - fall back to sync.\n if (this.destroyed) return;\n console.warn('[SimulationManager] Worker failed to load, falling back to sync');\n w2.terminate();\n this.worker = null;\n this.initSync(this.initNodes, this.initEdges, this.initConfig!);\n };\n\n wireWorker(w2);\n } catch {\n console.warn('[SimulationManager] Worker creation failed, using sync fallback');\n this.initSync(this.initNodes, this.initEdges, this.initConfig!);\n }\n };\n\n wireWorker(w);\n } catch {\n // Worker construction failed (e.g. SSR or restrictive CSP)\n console.warn('[SimulationManager] Worker creation failed, using sync fallback');\n this.initSync(nodes, edges, config);\n }\n }\n\n // -------------------------------------------------------------------------\n // Synchronous fallback\n // -------------------------------------------------------------------------\n\n private initSync(nodes: SimNode[], edges: SimEdge[], config: WorkerSimulationConfig): void {\n this.syncNodes = nodes.map((n) => ({\n id: n.id,\n x: n.x,\n y: n.y,\n radius: n.radius,\n community: n.community,\n }));\n\n this.syncNodeMap = new Map(this.syncNodes.map((n) => [n.id, n]));\n\n const linkForce = forceLink(edges.map((e) => ({ ...e })))\n .id((d) => (d as SyncNode).id)\n .distance(config.linkDistance);\n if (config.linkStrength != null) {\n linkForce.strength(config.linkStrength);\n }\n\n const padding = config.collisionPadding ?? 2;\n\n this.syncSim = forceSimulation<SyncNode>(this.syncNodes)\n .force('link', linkForce)\n .force('charge', forceManyBody().strength(config.chargeStrength))\n .force(\n 'collide',\n forceCollide<SyncNode>().radius((d) => d.radius + padding),\n )\n // Weak gravity keeps disconnected nodes from drifting far from center\n .force('gravityX', forceX<SyncNode>(0).strength(0.05))\n .force('gravityY', forceY<SyncNode>(0).strength(0.05))\n .alphaDecay(config.alphaDecay)\n .velocityDecay(config.velocityDecay)\n .stop(); // Don't auto-run; we tick manually\n\n // Center force (default true)\n if (config.centerForce !== false) {\n this.syncSim.force('center', forceCenter(0, 0));\n }\n\n // Add clustering force if configured\n if (config.clustering) {\n const clusterFn = forceCluster(this.syncNodes, config.clustering.strength);\n // d3 calls force functions with (alpha) on each tick\n this.syncSim.force('cluster', clusterFn as unknown as ReturnType<typeof forceCenter>);\n }\n\n // Cursor-repulsion force: always registered but inert until setPointer()\n // activates it (pointer.active + radius > 0). Reset intent so a re-init\n // (worker->sync fallback) doesn't inherit stale state.\n this.pointer.active = false;\n this.pointer.radius = config.cursorRepulsion?.radius ?? 0;\n this.pointer.strength = config.cursorRepulsion?.strength ?? 0;\n this.dragAlphaTarget = 0;\n this.cursorAlphaTarget = 0;\n const cursorFn = forceCursor(this.syncNodes, this.pointer);\n this.syncSim.force('cursor', cursorFn as unknown as ReturnType<typeof forceCenter>);\n\n // Initial alpha (entrance / reheat impulse) applied before warmup.\n if (config.initialAlpha != null) {\n this.syncSim.alpha(config.initialAlpha);\n }\n\n // Derive the tick cap from alphaDecay so the sync path settles as far as the\n // worker (which runs to alpha < alphaMin) rather than a fixed 300.\n this.syncMaxTicks = ticksToAlphaMin(config.alphaDecay);\n\n // Warmup: settle a bounded number of ticks BEFORE the first reveal so the\n // entrance doesn't start from an explosive layout. The sync path is on the\n // main thread, so warmup is chunked across rAF frames (a single 100-tick\n // batch at 10k nodes is a 1s+ freeze). Nothing renders until warmup finishes.\n this.syncWarmupTicks = config.warmupTicks ?? 0;\n this.syncWarmupBudgetMs = config.warmupBudgetMs ?? DEFAULT_WARMUP_BUDGET_MS;\n this.syncWarmupPending = this.syncWarmupTicks > 0;\n\n // Defer initial delivery: callbacks aren't wired yet at create() time\n this.runSyncTicks(true);\n }\n\n /**\n * Run simulation ticks in batches, yielding to the main thread between\n * batches via requestAnimationFrame. This prevents a multi-second freeze\n * when the sync fallback handles large graphs (1k+ nodes).\n *\n * Each batch runs SYNC_TICKS_PER_BATCH ticks, emits positions for\n * progressive rendering, then schedules the next batch.\n *\n * @param deferred - When true, start via microtask (initial run where\n * callbacks aren't wired yet). Otherwise start immediately.\n */\n private runSyncTicks(deferred = false): void {\n if (!this.syncSim || this.destroyed) return;\n\n // Cancel any in-flight batched run (e.g. from a previous reheat)\n if (this.syncRafId !== null) {\n cancelAnimationFrame(this.syncRafId);\n this.syncRafId = null;\n }\n\n const sim = this.syncSim;\n const maxTicks = this.syncMaxTicks;\n let tickCount = 0;\n\n // Pre-reveal warmup: chunk the headless settle across rAF frames, bounded by\n // BOTH the remaining tick count AND the ms budget. Nothing is delivered to\n // the tick callback until this completes, so the entrance never starts from\n // an explosive layout and the main thread never freezes. Duplicated in the\n // worker (simulation-worker.ts) as a synchronous loop (it's off-thread).\n const runWarmup = () => {\n if (this.destroyed || !this.syncSim) return;\n this.syncRafId = null;\n\n const start = this.now();\n while (this.syncWarmupTicks > 0) {\n for (let i = 0; i < SYNC_TICKS_PER_BATCH && this.syncWarmupTicks > 0; i++) {\n sim.tick();\n this.syncWarmupTicks--;\n if (sim.alpha() < DEFAULT_ALPHA_MIN) {\n this.syncWarmupTicks = 0;\n break;\n }\n }\n // Budget check between chunks: bail (accept a truncated warmup) at scale.\n if (this.syncWarmupTicks > 0 && this.now() - start >= this.syncWarmupBudgetMs) {\n this.syncWarmupTicks = 0;\n break;\n }\n }\n\n this.syncWarmupPending = false;\n // Warmup done → proceed to the normal reveal/settle loop.\n runBatch();\n };\n\n const runBatch = () => {\n if (this.destroyed || !this.syncSim) return;\n this.syncRafId = null;\n\n for (let i = 0; i < SYNC_TICKS_PER_BATCH && tickCount < maxTicks; i++, tickCount++) {\n sim.tick();\n if (sim.alpha() < DEFAULT_ALPHA_MIN) {\n tickCount = maxTicks;\n break;\n }\n }\n\n const positions = this.syncNodes.map((n) => ({\n id: n.id,\n x: n.x ?? 0,\n y: n.y ?? 0,\n }));\n const alpha = sim.alpha();\n const settled = alpha < DEFAULT_ALPHA_MIN || tickCount >= maxTicks;\n\n this.tickCb?.(positions, alpha);\n\n if (settled) {\n this.settledCb?.();\n } else {\n this.syncRafId = requestAnimationFrame(runBatch);\n }\n };\n\n const start = this.syncWarmupPending ? runWarmup : runBatch;\n\n if (deferred) {\n queueMicrotask(start);\n } else {\n start();\n }\n }\n}\n","/**\n * Re-export shim: the spatial index moved to `../spatial-index` and is now\n * generic over any `{ x, y, radius }` entry so the scatter canvas layer can\n * reuse it. Graph call sites get the node-typed instantiation.\n */\nimport { SpatialIndex as GenericSpatialIndex } from '../spatial-index';\nimport type { PositionedNode } from './types';\n\nexport type { SpatialEntry } from '../spatial-index';\n\n/** Quadtree index over positioned graph nodes. */\nexport class SpatialIndex extends GenericSpatialIndex<PositionedNode> {}\n","/**\n * Resolved simulationConfig equality for update-diff's visual-only detection.\n *\n * The old React heuristic only compared `clustering.field`, so a physics change\n * (charge strength, settle/energy, raw force numbers) with the same ids silently\n * took the position-preserving path and never reheated. That's a correctness bug\n * for the flagship API. Here we compare the FULL resolved config via a stable\n * stringify so any physics change forces a structural update.\n */\n\n/** The subset of engine SimulationConfig fields that affect the simulation. */\nexport interface SimulationConfigLike {\n chargeStrength: number;\n linkDistance: number;\n clustering: { field: string; strength: number } | null;\n alphaDecay: number;\n velocityDecay: number;\n collisionRadius: number;\n collisionPadding?: number;\n linkStrength?: number;\n centerForce?: boolean;\n seed?: number;\n warmupTicks?: number;\n warmupBudgetMs?: number;\n initialAlpha?: number;\n}\n\n/**\n * A stable, key-ordered stringify of the config fields that influence the\n * settled layout. `initialAlpha` is INTENTIONALLY excluded: it's the update\n * reheat impulse the mount sets per-update, not a spec-level physics knob, so it\n * must not force a structural update on its own.\n */\nfunction stableKey(c: SimulationConfigLike): string {\n return JSON.stringify([\n c.chargeStrength,\n c.linkDistance,\n c.clustering ? [c.clustering.field, c.clustering.strength] : null,\n c.alphaDecay,\n c.velocityDecay,\n c.collisionRadius,\n c.collisionPadding ?? null,\n c.linkStrength ?? null,\n c.centerForce ?? null,\n c.seed ?? null,\n c.warmupTicks ?? null,\n c.warmupBudgetMs ?? null,\n ]);\n}\n\n/** Deep-equal two resolved simulation configs (excluding `initialAlpha`). */\nexport function simulationConfigEqual(a: SimulationConfigLike, b: SimulationConfigLike): boolean {\n return stableKey(a) === stableKey(b);\n}\n","/**\n * Data-update diff for the graph mount's unified `update()`.\n *\n * Given the previously positioned nodes/edges and a freshly compiled graph,\n * classify the change into:\n * - `visualOnly`: same node AND edge id sets AND equal simulationConfig. The\n * mount can re-run the position-preserving visual refresh with no sim restart.\n * NOTE: unlike the old React JSON-diff heuristic (which only compared\n * `clustering.field`), visual-only here requires the FULL resolved\n * simulationConfig to match — a physics change (charge/settle/energy/etc.)\n * must reheat, not silently skip.\n * - Otherwise a structural update: which nodes enter, where they spawn, which\n * survive (and keep their prior x/y), and which nodes/edges exit (ghosts).\n *\n * Spawn placement: an entering node spawns at its first surviving neighbor's\n * NEW-adjacency position, nudged by a deterministic ±8px hash jitter (so a node\n * doesn't land exactly on its neighbor). A node with no surviving neighbor falls\n * back to a seeded disc position (same hash family as seed.ts).\n */\n\nimport { hash32 } from './seed';\nimport type { PositionedEdge, PositionedNode } from './types';\nimport type { SimulationConfigLike } from './update-diff-config';\nimport { simulationConfigEqual } from './update-diff-config';\n\n/** Minimal position record kept for survivors and computed for spawns. */\nexport interface XY {\n x: number;\n y: number;\n}\n\n/** A freshly compiled graph, reduced to what the diff needs. */\nexport interface NextGraph {\n nodes: Array<{ id: string; community?: string }>;\n edges: Array<{ source: string; target: string }>;\n simulationConfig: SimulationConfigLike;\n}\n\nexport interface GraphUpdateDiff {\n /** True when node+edge id sets are identical AND simulationConfig is equal. */\n visualOnly: boolean;\n /** Ids of nodes present in `next` but not in prev. */\n enteringIds: string[];\n /** Prior x/y for nodes present in BOTH prev and next, keyed by id. */\n survivingPositions: Map<string, XY>;\n /** Computed spawn x/y for entering nodes, keyed by id. */\n spawnPositions: Map<string, XY>;\n /** Prev-positioned nodes removed in `next` (ghosts). */\n exitingNodes: PositionedNode[];\n /** Prev-positioned edges removed in `next` (ghosts). */\n exitingEdges: PositionedEdge[];\n /**\n * Count of edges present in `next` but not in prev — including edges added\n * between two surviving nodes, which \"touches an entering node\" would miss.\n */\n enteringEdgeCount: number;\n}\n\n/** ±8px deterministic jitter from an id+seed hash (two independent streams). */\nconst JITTER = 8;\n\nfunction jitter(id: string, seed: number): XY {\n // Map two hash streams to [-1, 1) then scale, so a spawn doesn't land exactly\n // on its neighbor. Deterministic in (id, seed).\n const jx = (hash32(`${id}|${seed}|jx`) / 0x100000000) * 2 - 1;\n const jy = (hash32(`${id}|${seed}|jy`) / 0x100000000) * 2 - 1;\n return { x: jx * JITTER, y: jy * JITTER };\n}\n\n/**\n * Seeded disc fallback for a node with no surviving neighbor. Mirrors seed.ts's\n * disc placement (r = R0·√u1, θ = u2·2π) so orphan spawns land in the same cloud.\n */\nconst FALLBACK_R0 = 300;\n\nfunction seededDisc(id: string, seed: number): XY {\n const u1 = hash32(`${id}|${seed}|r`) / 0x100000000;\n const u2 = hash32(`${id}|${seed}|t`) / 0x100000000;\n const r = FALLBACK_R0 * Math.sqrt(u1);\n const theta = u2 * Math.PI * 2;\n return { x: r * Math.cos(theta), y: r * Math.sin(theta) };\n}\n\n/**\n * Diff the previous positioned graph against a freshly compiled `next`.\n *\n * @param prevNodes - Previously positioned nodes (carry x/y).\n * @param prevEdges - Previously positioned edges.\n * @param next - The freshly compiled graph (nodes/edges/simulationConfig).\n * @param prevConfig - The prior simulationConfig, for visual-only detection.\n * @param seed - Layout seed for deterministic jitter/fallback placement.\n */\nexport function diffGraphUpdate(\n prevNodes: PositionedNode[],\n prevEdges: PositionedEdge[],\n next: NextGraph,\n prevConfig: SimulationConfigLike,\n seed: number,\n): GraphUpdateDiff {\n const prevIds = new Set(prevNodes.map((n) => n.id));\n const nextIds = new Set(next.nodes.map((n) => n.id));\n\n const survivingPositions = new Map<string, XY>();\n const enteringIds: string[] = [];\n\n for (const n of prevNodes) {\n if (nextIds.has(n.id)) survivingPositions.set(n.id, { x: n.x, y: n.y });\n }\n for (const n of next.nodes) {\n if (!prevIds.has(n.id)) enteringIds.push(n.id);\n }\n\n // Exiting marks: prev nodes/edges gone from next. An edge exits when its\n // (source,target) key is absent from the next edge set — this catches BOTH\n // edges whose endpoint was removed AND edges removed between two survivors.\n const exitingNodes = prevNodes.filter((n) => !nextIds.has(n.id));\n const nextEdgeKeys = new Set(next.edges.map((e) => `${e.source} ${e.target}`));\n const exitingEdges = prevEdges.filter((e) => !nextEdgeKeys.has(`${e.source} ${e.target}`));\n const prevEdgeCounts = new Map<string, number>();\n for (const e of prevEdges) {\n const k = `${e.source} ${e.target}`;\n prevEdgeCounts.set(k, (prevEdgeCounts.get(k) ?? 0) + 1);\n }\n let enteringEdgeCount = 0;\n const remainingPrev = new Map(prevEdgeCounts);\n for (const e of next.edges) {\n const k = `${e.source} ${e.target}`;\n const c = remainingPrev.get(k);\n if (c && c > 0) {\n remainingPrev.set(k, c - 1);\n } else {\n enteringEdgeCount++;\n }\n }\n\n // Visual-only: identical node AND edge id sets AND equal simulationConfig.\n const sameNodes = prevIds.size === nextIds.size && enteringIds.length === 0;\n const sameEdges = edgeSetsEqual(prevEdges, next.edges);\n const visualOnly =\n sameNodes && sameEdges && simulationConfigEqual(prevConfig, next.simulationConfig);\n\n // Spawn placement for enterers: first surviving neighbor in the NEW adjacency,\n // plus ±8px jitter; fallback to a seeded disc when no surviving neighbor.\n const spawnPositions = new Map<string, XY>();\n if (enteringIds.length > 0) {\n const enteringSet = new Set(enteringIds);\n const nextAdjacency = buildAdjacency(next.edges);\n for (const id of enteringIds) {\n const neighborPos = firstSurvivingNeighborPos(\n id,\n nextAdjacency,\n survivingPositions,\n enteringSet,\n );\n const j = jitter(id, seed);\n if (neighborPos) {\n spawnPositions.set(id, { x: neighborPos.x + j.x, y: neighborPos.y + j.y });\n } else {\n const disc = seededDisc(id, seed);\n spawnPositions.set(id, { x: disc.x + j.x, y: disc.y + j.y });\n }\n }\n }\n\n return {\n visualOnly,\n enteringIds,\n survivingPositions,\n spawnPositions,\n exitingNodes,\n exitingEdges,\n enteringEdgeCount,\n };\n}\n\n/** Build an undirected adjacency map from an edge list. */\nfunction buildAdjacency(edges: Array<{ source: string; target: string }>): Map<string, string[]> {\n const map = new Map<string, string[]>();\n const push = (a: string, b: string) => {\n const list = map.get(a);\n if (list) list.push(b);\n else map.set(a, [b]);\n };\n for (const e of edges) {\n push(e.source, e.target);\n push(e.target, e.source);\n }\n return map;\n}\n\n/**\n * First neighbor of `id` (in the new adjacency) that already has a known\n * position — i.e. a survivor, not another enterer. Deterministic: neighbors are\n * scanned in edge-declaration order.\n */\nfunction firstSurvivingNeighborPos(\n id: string,\n adjacency: Map<string, string[]>,\n survivingPositions: Map<string, XY>,\n enteringSet: Set<string>,\n): XY | null {\n const neighbors = adjacency.get(id);\n if (!neighbors) return null;\n for (const nid of neighbors) {\n if (enteringSet.has(nid)) continue;\n const pos = survivingPositions.get(nid);\n if (pos) return pos;\n }\n return null;\n}\n\n/**\n * Order-insensitive multiset equality of two edge lists by (source,target) key.\n * Counts matter: prev [A→B, A→B, C→D] vs next [A→B, C→D, C→D] is a structural\n * change, not visual-only.\n */\nfunction edgeSetsEqual(\n prev: Array<{ source: string; target: string }>,\n next: Array<{ source: string; target: string }>,\n): boolean {\n if (prev.length !== next.length) return false;\n const key = (e: { source: string; target: string }) => `${e.source}->${e.target}`;\n const counts = new Map<string, number>();\n for (const e of prev) {\n const k = key(e);\n counts.set(k, (counts.get(k) ?? 0) + 1);\n }\n for (const e of next) {\n const k = key(e);\n const c = counts.get(k);\n if (!c) return false;\n counts.set(k, c - 1);\n }\n return true;\n}\n"],"mappings":"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","names":["cx","cy","set","escapeHtml","start","SpatialIndex","SpatialIndex","getContainerDimensions","set","resolveHighlightTarget","categoryHighlightSet"]}