Graph integration and worker assets
Native mounting
Section titled “Native mounting”import { mountGraph } from 'entity-viz-kit/graph';import { contextSignature } from 'entity-viz-kit/core';import 'entity-viz-kit/styles.css';
const graph = mountGraph(container, { snapshot, // validated GraphSnapshot from your host registry: controller.registry, contextKey: contextSignature(controller.getSnapshot().state.context), workerUrl: '/assets/evk-layout.worker.js', onInspectEntity: entityId => controller.dispatch({ type: 'inspect', inspection: { kind: 'entity', entityId } }), onInspectPair: pairId => controller.dispatch({ type: 'inspect', inspection: { kind: 'relation', pairId } }), onAddEntity: entityId => controller.dispatch({ type: 'query.add', entityId }), onViewChange: view => { const current = controller.getSnapshot().state; if (contextSignature(current.context) === view.contextKey) { controller.dispatch({ type: 'presentation.set', presentation: { ...current.presentation, graph: view } }); } }});This example checkpoints presentation in the existing controller without writing browser storage. ControllerGraph provides the complete bidirectional React binding. The host owns which data may be supplied or persisted. Graph code performs no backend request itself.
mountGraph is client-only; importing either graph entry is SSR-safe. Native use requires no React. The graph model accepts one core pair per unordered endpoint pair; duplicate visual pairs/unknown endpoints/self-edges are rejected. Aggregate assertion inputs with the existing core helper first. A graph with no evidence remains a graph; absence of supplied evidence is explicitly shown by the existing evidence UI, never fabricated.
The actual worker file
Section titled “The actual worker file”The package exposes entity-viz-kit/graph/worker, resolving to build/compiled/graph/layout.worker.js. This is a standalone classic Worker script with the real force integrator, not an ESM stub that needs runtime dependency resolution. Copy the resolved file unchanged to the URL you provide. In Node-based build tooling, import.meta.resolve('entity-viz-kit/graph/worker') locates it. Host bundlers may instead use a local workerFactory returning a real Worker for that emitted asset.
If neither factory nor URL is provided, the default is evk-layout.worker.js relative to the HTML document’s base URL. The included dev application copies it there. In a routed application or extension, supply an explicit asset URL rather than relying on that default. Serve JavaScript MIME, allow the intended worker origin in CSP and retain same-origin/CORS browser requirements. There is no default Blob/eval/network workaround in the production library. The isolated-document test’s Blob transport is explicitly test-only and does not prove HTTP/CSP asset loading.
A worker startup failure yields a labeled static/manual view and working navigator; no hidden main-thread force loop starts. worker: false deliberately selects that mode. paused: true starts the actual worker paused. A custom factory and its credentials/extension context remain local application code, never a backend-serialized function.
import { createElement } from 'react';import { ControllerGraph } from 'entity-viz-kit/react/graph';
createElement(ControllerGraph, { controller, snapshot, workerUrl: '/assets/evk-layout.worker.js'});EntityGraph is the controlled wrapper for independent host stores. ControllerGraph supplies registry, query members, inspection, context and explicit view checkpoints from the existing EntityController. Keep callback/registry/factory references stable where possible. Normal search imports from /react remain graph-free. Worker positions update the native renderer directly, not one React component per vertex per tick.
View updates, pins and filtering
Section titled “View updates, pins and filtering”update({snapshot}) keeps surviving positions/pins/camera when graph/context identity is unchanged. Source revision or display metadata alone does not reheat. Real topology changes reconfigure the worker using surviving world positions. update({contextKey}) resets unrelated presentation, even when IDs happen to match.
pin(id) keeps its current world position. pin(id,{x,y}) places an explicit world pin. unpin, resetPins, fit, focus, zoom and setCamera are direct controls. getView() returns the existing serializable GraphView. Restore requires matching graph and context identities; host applications must reset their identity space/context for authorization transitions. The core serializer never includes credentials or hover state.
A temporary visual filter hides nodes/edges but retains full topology and pins. Counts always show visible/total. Real deletion removes presentation for the deleted IDs. setVisibleEntities(null) restores all supplied nodes. Never report a filtered subset as full-dataset performance.
Camera x/y is world center; ratio is CSS px/world unit. Framebuffer resolution and pixel ratio are independent. project returns stage-local CSS coordinates; pick returns either a node or all nearby pair IDs. Node radius and the eight-pixel edge tolerance are screen-space affordances, not world units. Same labels do not imply same identity.
Accessibility and resource lifecycle
Section titled “Accessibility and resource lifecycle”Mouse/touch/keyboard actions share the same exact IDs. Node activation inspects; a separately labeled action adds to query. Ambiguous crossings open a choice. The searchable, paged navigator uses the same callbacks. Type/selection/pin meanings use labels/glyphs as well as color. Focus alone does not pan the camera. Touch scrolling remains native until graph exploration is explicitly enabled.
Use setExpanded for an in-page larger view; Escape/close restores focus and page position. Real software-keyboard/assistive-technology checks are still required on supported devices. Native graphics are not the only accessible representation.
Always call dispose() on unmount. The React wrapper does so. Disposal terminates the worker, clears timers/RAF and observer/subscriptions, releases GPU resources and removes only owned DOM. It is idempotent. Do not call mutation/read-view operations afterward. getDiagnostics() remains available to check disposed status and cleared counts.
Performance and comparison
Section titled “Performance and comparison”Run npm run bench:graph for deterministic CPU solver timing and exact pin checks. Run EVK_BROWSER_EXECUTABLE=/path/to/chromium npm run bench:graph:browser for actual renderer/worker/frame/picking/input-probe measurements. On the provided Linux environment graphics required a virtual display and software ANGLE:
xvfb-run -a env EVK_BROWSER_EXECUTABLE=/usr/bin/chromium EVK_GRAPH_SOFTWARE_GL=1 npm run bench:graph:browserThis optional environment setting selects a software driver; it does not disable browser policy or add the lower-security --enable-unsafe-swiftshader flag. Results identify the actual GPU/driver, viewport, framebuffer, density, CPU and browser. Software results are not reference-device acceptance. Eight hundred nodes with 8,000 edges and p95 20 ms desktop/33 ms mobile remain targets; an unmet measurement is not relabeled passed. Backend latency is excluded.
A side-by-side comparison with Sigma/Graphology or G6 has not been run. The 0.4–0.8 comparison probe page was removed in 0.9.0 (plan 09) because it was never exercised; the comparison remains an open validation item (ADR 0004).