Skip to content

feat: replace d3 canvas graph renderer with Sigma.js WebGL renderer#412

Open
shaunpatterson wants to merge 9 commits into
dgraph-io:mainfrom
shaunpatterson:sp/sigma-graph-view
Open

feat: replace d3 canvas graph renderer with Sigma.js WebGL renderer#412
shaunpatterson wants to merge 9 commits into
dgraph-io:mainfrom
shaunpatterson:sp/sigma-graph-view

Conversation

@shaunpatterson

Copy link
Copy Markdown
Contributor

What

Replaces the hand-rolled d3-force canvas renderer (~1100 lines of manual hit-testing, arc geometry and label culling) with sigma.js v3 + graphology, the WebGL graph rendering stack used for large-scale network visualization.

Why

  • Scale: WebGL rendering handles tens of thousands of nodes; the 2D canvas renderer degrades past a few hundred and culls labels aggressively (>50 nodes zoomed out, >500 always).
  • Responsiveness: ForceAtlas2 layout runs in a web worker (graphology-layout-forceatlas2/worker) — the UI thread never blocks during layout. Auto-stops after 4s.
  • Less code to maintain: hit-testing, label collision, hover/drag mechanics, and camera handling come from the library instead of bespoke canvas math.

Feature parity

Capability Status
Hover/select nodes & edges, properties panel ✅ same callbacks, original node/edge objects
Double-click to expand/collapse
Drag node to pin
Parallel edges between same nodes ✅ fan out as distinct curves (@sigma/edge-curve)
searchNode/focusNode/zoomToFit ref API (toolbar) ✅ same matching semantics
highlightPredicate, active edge emphasis ✅ via sigma reducers
New degree-scaled node sizes; neighbor highlighting dims non-neighbors on hover

One contract worth calling out: d3-force used to mutate edge source/target from uid strings into node object references, and both EdgeProperties and GraphParser.collapseNode depend on that. buildGraph preserves the mutation, with unit tests pinning it.

Testing

  • buildGraph (graphology construction, endpoint resolution, curvature fanning, degree sizing, position reuse): 12/12 tests pass.
  • sigma is mocked under Jest — jsdom has no WebGL2RenderingContext (full-suite repair is fix: repair broken Jest test suite (16/18 suites failing) #410).
  • npm run build (webpack 5) passes.
  • Browser smoke test (client/scripts/graph-smoke.mjs, included): seeds a local Dgraph v25 (ACL) cluster, logs in through the real UI, runs a query, asserts the WebGL canvas layers render the expected graph, and exercises search + zoom-to-fit. Passes with zero page errors.

d3 is dropped from dependencies (D3Graph was its only consumer).

🤖 Generated with Claude Code

Swap the hand-rolled d3-force canvas renderer (~1100 lines of manual
hit-testing, arc math and label culling) for sigma.js v3 + graphology:

- WebGL rendering scales to tens of thousands of nodes (the canvas
  renderer struggles past a few hundred); labels get automatic
  collision/density handling.
- ForceAtlas2 layout runs in a web worker, so the UI thread never
  blocks during layout; it auto-stops after 4s.
- Parallel edges between the same node pair fan out as distinct curves
  (@sigma/edge-curve), preserving the sibling-edge behavior.
- Nodes sized by degree (capped); neighbor highlighting on hover;
  drag-to-pin; double-click to expand/collapse preserved.
- Implements the GraphContainer ref API (searchNode/focusNode/
  zoomToFit) with the same matching semantics as the d3 renderer.
- buildGraph keeps the d3-force contract of resolving edge
  source/target uids to node objects in place - EdgeProperties and
  GraphParser.collapseNode depend on that mutation. Covered by 12
  unit tests.
- sigma is mocked under Jest (jsdom has no WebGL2RenderingContext).
- scripts/graph-smoke.mjs: puppeteer smoke test that seeds a local
  Dgraph, logs in, runs a query through the real UI and asserts the
  WebGL canvases render and search/zoomToFit work.
- Drop the d3 dependency (D3Graph was its only consumer).

Verified: 12/12 buildGraph tests, production build, and the browser
smoke test against Dgraph v25 with ACL (3 nodes / 3 edges rendered
via WebGL, search + zoom-to-fit exercised, zero page errors).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@shaunpatterson shaunpatterson requested a review from a team as a code owner June 12, 2026 13:18
Three graph-view features on top of the sigma renderer:

- Style rules (Neo4j Bloom-style): a Graph styles panel in the toolbar
  lists every group in the current result with a color picker and node
  size slider; overrides apply live via sigma reducers and persist in
  localStorage. lib/graphStyles.js (sanitize/persist/merge) has 11
  unit tests.
- Layout switcher: Force (ForceAtlas2 worker), Circular, and Packed
  (circlepack clustered by group) via a toolbar select; static layouts
  auto-fit the camera.
- Legend filtering: clicking a predicate chip in the entity selector
  hides/shows that predicate's nodes and edges without re-querying;
  hidden chips render dimmed with strikethrough.

Verified in a real browser against Dgraph v25: layouts switch, style
panel renders per-group rows and applies color changes, legend chips
toggle - zero page errors. Unit suite green, production build passes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@shaunpatterson

Copy link
Copy Markdown
Contributor Author

Scope update: this PR now also includes the graph toolkit built on top of the new renderer (825cf76):

  • Style rules (Neo4j Bloom-style): a Graph styles toolbar panel lists every group in the current result with a color picker and node-size slider; overrides apply live via sigma reducers and persist in localStorage (lib/graphStyles.js, 11 unit tests).
  • Layout switcher: Force (ForceAtlas2 worker) / Circular / Packed (circlepack clustered by group), with auto camera fit on static layouts.
  • Legend filtering: clicking a predicate chip in the entity selector hides/shows that predicate's nodes and edges without re-querying; hidden chips render dimmed with strikethrough.

All three were exercised in a real browser against a live Dgraph v25 (layout switching, style panel color changes, chip toggling) with zero page errors. Unit suite and production build green.

shaunpatterson and others added 6 commits June 12, 2026 13:47
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add Louvain community detection and betweenness centrality (graphology
ecosystem) as optional node rendering modes alongside the existing
predicate-color / degree-size defaults:

- Color by: Predicate (default) or Community
- Size by: Degree (default), Centrality (betweenness), or Uniform

Metrics are annotated onto the graphology graph in buildGraph and applied
in the SigmaGraph node reducer, so default rendering is byte-for-byte
unchanged. Betweenness is skipped above 1500 nodes to keep expansion
responsive.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a path-finding mode to the graph toolbar. Toggle it on, click a
source node then a target, and the shortest route between them (computed
breadth-first over the currently rendered subgraph, edges treated as
undirected) is highlighted while the rest of the graph dims back. A banner
reports the hop count or that no path exists, with a Clear button.

Path-finding runs client-side on the loaded graph, so it needs no extra
server round-trip and works on exactly what the user can see.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a filter panel to the graph toolbar that hides nodes failing a
connectivity (degree) range or an attribute predicate (contains / = / ≠ /
> / < / exists). Edges drop out with either endpoint. The panel previews
how many nodes are hidden and clears in one click.

Filtering is applied in the SigmaGraph reducers via a precomputed
hidden-node set, recomputed only when the filter spec or dataset changes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Detect ISO datetime attributes on nodes and, when the dataset spans a
range, offer a timeline control: a clock toggle reveals a scrubber with
play/pause that reveals nodes as their earliest timestamp passes. Nodes
without a time stay as structural context; edges drop with either hidden
endpoint.

The cutoff is applied in the SigmaGraph reducers against a node _time
attribute annotated in buildGraph, so scrubbing/playback is a cheap refresh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@shaunpatterson

Copy link
Copy Markdown
Contributor Author

Scope update: four graph-exploration features added

Building on the Sigma.js renderer, this branch now also includes four analyst-focused features (each its own commit, each with unit tests; verified end-to-end in the real UI against a local Dgraph):

  • Color by community / size by centrality (c008a97) — Louvain communities + betweenness centrality via the graphology ecosystem, as optional node modes. Defaults (predicate color, degree size) render identically to before.
  • Find shortest path (474c0fd) — toggle path mode, click two nodes, BFS over the rendered subgraph highlights the route; banner reports hop count.
  • Faceted filtering (8e69188) — hide nodes by degree range or attribute predicate (contains / = / ≠ / > / < / exists); edges drop with either endpoint.
  • Timeline (be68a4d) — detects ISO datetime attributes and offers a scrubber + play to reveal nodes over time.

All applied via the existing SigmaGraph reducer architecture. New deps: graphology-communities-louvain, graphology-metrics. +44 unit tests across 4 new lib modules.

@rahst12

rahst12 commented Jun 17, 2026

Copy link
Copy Markdown

SigmaJS v4 is coming along. There's been a lot of development on that line over the past year. I'd recommend checking that out for long-term support/stability of the product. It improves on SigmaJS v3 quite a bit. Looks like they're also actively developing it and taking feedback with rapid fixes when things are found. gdotv is using it too. Discussion Post: jacomyal/sigma.js#1539 (reply in thread)

Sigma v4 is now stable enough for real projects (per the
jacomyal/sigma.js announcement), so swap in the v4 alpha release.

Highlights of the migration:
- Drop @sigma/edge-curve: v4 has curved paths built in
  (pathLine/pathCurved). Edge attributes change from
  type:'arrow'/'curvedArrow' to path:'line'/'curved' plus an arrow
  head via extremityArrow().
- Drop edgeProgramClasses / defaultEdgeType in favour of the
  declarative primitives block (shapes, layers, paths, extremities).
- Replace manual node-drag handlers (downNode/moveBody/upNode,
  setCustomBBox) with v4's built-in enableNodeDrag + autoRescale:
  'once' so the viewport doesn't recentre as nodes are dragged.
- Keep nodeReducer / edgeReducer as escape hatches: v4 still
  accepts them and the reducer logic is unchanged, only the
  signature is widened to (key, data, attrs, state, graphState, graph).
- Update sigmaMock.js for the v4 surface (setNodeState/setGraphState,
  getNodeDisplayData, new module subpaths sigma/types/settings/utils)
  and the buildGraph test for the new path/head edge attributes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants