Architecture
Rendering contract
Section titled “Rendering contract”graphraum consumes positioned nodes and edges. It does not fetch data or infer graph semantics. Its optional force helpers produce positions independently from the renderer.
flowchart LR App[Application graph state] -->|nodes, edges, positions| Graphraum[graphraum] Layout[Layout worker or server] -->|positions| App Graphraum --> Viewport[Spatial viewport query] Viewport --> Scene[Three.js scene] Scene --> Nodes[One InstancedMesh] Scene --> Edges[One LineSegments geometry] Graphraum -->|picked node ID| App
This keeps layout work independently replaceable and prevents a complete force simulation from blocking the first useful render.
Layout producers can send GraphraumLayoutPositions batches: node IDs plus transferable XYZ Float32Array data. The host calls applyLayout() for each batch, so useful positions can appear before full convergence. The package also exports dimension-aware static, clustered, and incremental force helpers; applications still decide where and when they run.
On-demand rendering
Section titled “On-demand rendering”graphraum renders when data, selection, camera controls, mode, or container size changes. It does not keep an idle animation loop alive. The one exception is an opt-in 3D auto-orbit (setAutoOrbit), which runs a frame loop only while it is actively orbiting and only renders on those frames, without rebuilding node or edge instances. Calls within one browser frame are coalesced through requestAnimationFrame.
Viewport materialization
Section titled “Viewport materialization”In 2D, graphraum queries visible nodes from its spatial grid with an overscan margin, derives unique incident edges without scanning the full edge list, and compacts that subset into the existing node and edge GPU buffers. The renderer remains at two draw calls; the opt-in node glow adds one bounded halo draw call only while a visible node glows.
When visible nodes exceed maxVisibleNodes, graphraum bins them into representative spatial density clusters. Density mode enters above 110% of the budget and exits below 85% so small camera changes do not flicker between tiers. Nodes always draw over edges, in 2D and 3D. In 3D nodes still hide each other by depth, but an edge never covers a node, even where it passes in front of one: a node behind an edge shows on top of it. Edges also no longer depth-sort among themselves in 3D: where they overlap, they draw in batch order. This keeps node faces readable at busy hubs at the cost of these depth cues.
When incident edges exceed maxVisibleEdges, graphraum applies a deterministic overview budget and reports that decision through diagnostics. Application-specific clustering and semantic aggregation remain host concerns.
Application-owned concerns
Section titled “Application-owned concerns”The host remains responsible for:
- layout and progressive position updates;
- labels, clustering, and domain-specific semantic level of detail;
- keyboard navigation and an accessible node list;
- domain actions such as expand, focus, archive, or inspect;
- persistence and synchronization.
graphraum provides the rendering primitive, not a replacement application architecture. Visual fidelity is progressive: GPU batches for overview and exploration, host HTML for a hard-capped focus set. See ADR: Fidelity on demand.
Graphology direction
Section titled “Graphology direction”Graphology is the canonical mutable graph input (PRO-248). Its core graph object supplies node/edge identity, typed attributes, events, traversal, and serialization. bindGraphology() subscribes to that contract and renders visual attributes; graphraum does not require Graphology’s optional layout or algorithm packages.
The existing array snapshot remains useful for static data, transport boundaries, fixtures, and one-shot rendering.