•5 min read

Five Graph Engines, One Unified WebGL & SVG Physics Kernel

Unified Multi-Engine Graph Physics synchronizes hover tooltips, 60fps drag adaptation, dynamic hop z-ordering, and 750ms sine transitions across five heterogeneous web renderers, spanning D3.js SVG [BOH11], vis-network Canvas, Cytoscape.js Canvas [Fra16], Sigma.js WebGL [JVHB14], and Three.js 3d-force-graph, by decoupling visual tokens in graph-payload.ts and Preact overlays in vis-tooltip.tsx from engine-specific draw loops.

Table
Graph EngineRender PipelineLive Drag Solver (force / radial & columns)Z-Order Strategy
d3/engine.tsRetained SVG DOM [BOH11]Native d3.forceSimulation alphaTarget(0.3) / live recomputeSlotTargets(true).sort() on SVG <line> & <g> DOM nodes
vis-network/engine.ts2D HTML5 CanvasLive forceAtlas2Based simulation / cubic-eased animateTo() slot interpolationSorting network.body.edgeIndices & nodeIndices
cytoscape/engine.ts2D HTML5 Canvas [Fra16]Auxiliary d3.forceSimulation coupled via cy.batch() / live animateTo() slotsStylesheet z-index mapper on displayZIndex
sigma/engine.ts2D WebGL ShadersPer-frame forceAtlas2.assign [JVHB14] + 8% centroid shift / animateTo()Graphology zIndex attributes (zIndex: true)
3d-force-graph/engine.tsThree.js WebGLd3ReheatSimulation() on unpinned nodes / 3D cubic-eased animateTo()True 3D WebGL depth buffer + s3s^3 volume scaling

This five-engine matrix allows direct empirical comparison of SVG, HTML5 Canvas, and WebGL rendering pipelines under identical interaction physics.

How Do We Unify Hover Tooltips and Styling Tokens Across Five Renderers?

We unify visual identity across all five engines by suppressing cluttered on-canvas text labels globally, reading centralized radii, stroke widths, and domain palettes from getGraphStyleTokens in graph-payload.ts, and mounting a shared Preact hover card via createGraphTooltipController in vis-tooltip.tsx with deterministic viewport boundary clamping across every interactive graph container [BOH11] [Fra16].

SVG
FIVE GRAPH ENGINES, ONE TOOLTIP KERNEL Zero On-Canvas Labels (label: '') + Shared Preact Hover Overlay vis-tooltip.tsx D3.js SVG DOM <g> vis-network 2D Canvas Cytoscape.js cy.batch() Sigma.js 2D WebGL 3D Force Three.js WebGL Shared Tokens (graph-payload.ts) & Clamped Tooltips

Rendering permanent text labels next to 100+ nodes turns dense knowledge graphs into illegible typographic noise, whereas native tooltip implementations diverge wildly across SVG, Canvas, and WebGL. By setting label: '' across D3, Vis.js, and Cytoscape, renderLabels: false in Sigma, and .nodeLabel(() => '') in 3d-force-graph, we delegate all metadata inspection to vis-tooltip.tsx. In 3d-force-graph/engine.ts, we explicitly mount createGraphTooltipController(container) after invoking ForceGraph3D()(container) so Kapsule’s internal DOM initialization does not wipe the overlay element, and fall back to graph2ScreenCoords(x, y, z) whenever pointer capture precedes cursor movement.

How Do We Achieve 60fps Drag Adaptation Without Camera Jumps?

Achieving 60fps drag adaptation without disorienting camera jumps requires coupling pinned node coordinates to live force simulations in force mode while gently recentering the graph centroid instead of invoking fit(), dynamically re-sorting angular and linear slots during radial and columns drags, and enforcing touch-action: pan-y on all 2D canvases [BOH11] [JVHB14].

SVG
60FPS DRAG PHYSICS & STEADY CAMERA FRAME Pinned Drag Simulation Without Disorienting fit() Camera Jumps -8% Drift Steady Centroid Pinned Drag setCustomBBox LIVE POLAR & LINEAR SLOT INTERPOLATION atan2 Radial Slot Swap Column & Touch Guard animateTo() + touch-action: pan-y

Because Cytoscape’s initial cose layout is static and Sigma’s WebGL renderer lacks a built-in drag spring loop, we drive live force dragging in cytoscape/engine.ts via an auxiliary d3.forceSimulation flushed inside cy.batch() and in sigma/engine.ts by running two forceAtlas2.assign iterations per animation frame while locking the camera (setCustomBBox) and shifting unpinned nodes by 8% of mean centroid drift. In radial and columns modes, dragging a node recomputes normalized polar angles θ=atan2⁡(y−cy,x−cx)\theta = \operatorname{atan2}(y - c_y, x - c_x) or column coordinates (useCurrentOrder = true) and smoothly animates peer nodes into newly vacated slots when the ordering signature changes:

TypeScript
// src/content/visualizations/sigma/engine.ts — 60fps ForceAtlas2 drag loop with steady centroid
const pinnedX = this.graph.getNodeAttribute(draggedNode, 'x') as number;
const pinnedY = this.graph.getNodeAttribute(draggedNode, 'y') as number;
forceAtlas2.assign(this.graph, { iterations: 2, settings: { gravity: 0.8, slowDown: 6 } });
this.graph.setNodeAttribute(draggedNode, 'x', pinnedX);
this.graph.setNodeAttribute(draggedNode, 'y', pinnedY);

let sumX = 0, sumY = 0, count = 0;
this.graph.forEachNode(nId => {
  sumX += (this.graph?.getNodeAttribute(nId, 'x') as number) || 0;
  sumY += (this.graph?.getNodeAttribute(nId, 'y') as number) || 0;
  count++;
});
const shiftX = (sumX / count) * 0.08, shiftY = (sumY / count) * 0.08;
this.graph.forEachNode(nId => {
  if (nId !== draggedNode) {
    this.graph?.setNodeAttribute(nId, 'x', (this.graph.getNodeAttribute(nId, 'x') as number) - shiftX);
    this.graph?.setNodeAttribute(nId, 'y', (this.graph.getNodeAttribute(nId, 'y') as number) - shiftY);
  }
});
this.sigma.refresh();

Subtracting 8% of mean centroid displacement per frame keeps the dragged sub-network centered within the locked viewport without triggering abrupt camera zoom resets.

Why Are Dynamic Z-Ordering and 750ms Sine Transitions Essential for Readability?

Without dynamic z-ordering, dimmed background edges drawn later in canvas or SVG paint order slice across bright foreground hubs and active ego-network links; we eliminate this occlusion by sorting draw order through getNodeHopZIndex and getEdgeHopZIndex while interpolating colors and radii over a 750ms sine ease-in-out requestAnimationFrame loop [Fra16].

SVG
UNSORTED PAINT ORDER (EDGE OCCLUSION) Sliced Focus Node Static Paint Order z=1 Bleeds Over z=40 DYNAMIC HOP Z-STACK & 750MS SINE LOOP z=1..20 (Back) z=30 (1-Hop) z=40 (Focus) 750ms Sine Easing -0.5·(cos(πp) - 1) .sort() | edgeIndices | displayZIndex | s³ 3D

When a user hovers or drags a node, graph-payload.ts maps effective hop distances deffd_{\text{eff}} to discrete z-layers (40 for the focus node, 30 for 1-hop neighbors and incident edges, 20 for 2-hop, 10 for 3-hop, and 1 for background elements) and eases every node and link over HIGHLIGHT_TRANSITION_MS = 750 via e(p)=−12(cos⁡(πp)−1)e(p) = -\frac{1}{2}(\cos(\pi p) - 1):

  • D3.js SVG DOM: Invokes .sort() on linkElements and nodeElements while guarding isReorderingDom = true so DOM mutation does not trigger spurious mouseleave events [BOH11].
  • vis-network & Cytoscape.js 2D Canvas: Sorts this.network.body.edgeIndices and nodeIndices prior to redraw() in vis-network/engine.ts, and batches displayZIndex data updates mapped to stylesheet z-index in cytoscape/engine.ts [Fra16].
  • Sigma.js & Three.js WebGL: Updates Graphology zIndex attributes (zIndex: true) in sigma/engine.ts and scales 3D sphere volume by s(deff)3s(d_{\text{eff}})^3 in 3d-force-graph/engine.ts so Three.js sphere radius r∝val3r \propto \sqrt[3]{\text{val}} matches our 2D linear radius progression.

Coupling hop-sorted draw order with a 750ms sine-eased interpolation loop ensures that foreground ego-neighborhoods remain crisp and unoccluded across all five rendering technologies—combining the mathematical decay curves of Focus-Aware Hop Attenuation in Knowledge Graphs, the touch-first spatial indexing of Voronoi Proximity Picking, the XSS-safe reactive patterns of our Zero-innerHTML Preact and Nanostores Architecture, and the palette semantics of our Ten-Domain Color Spectrum across the live Cytoscape.js, Sigma.js, D3.js, Vis-Network, and 3D Force-Graph explorers.

References

  • [BOH11]
    Michael Bostock, Vadim Ogievetsky and Jeffrey Heer (2011). D3: Data-Driven Documents. IEEE Transactions on Visualization and Computer Graphics, 17(12), pp. 2301--2309. DOI: 10.1109/TVCG.2011.185.
  • [JVHB14]
    Mathieu Jacomy, Tommaso Venturini, Sebastien Heymann and Michael Bastian (2014). ForceAtlas2, a Continuous Graph Layout Algorithm for Handy Network Visualization Designed for the Gephi Software. PLOS ONE, 9(6), pp. e98679. DOI: 10.1371/journal.pone.0098679.
  • [Fra16]
    Max Franz, Christian T. Lopes, Gerardo Huck, Yue Dong, Onur Sumer and Gary D. Bader (2016). Cytoscape.js: a graph theory library for visualisation and analysis. Bioinformatics, 32(2), pp. 309--311. DOI: 10.1093/bioinformatics/btv557.