Skip to content

feat: add force layout transform#529

Draft
RobertFrenken wants to merge 2 commits intosvelteplot:mainfrom
RobertFrenken:feat/force-transform
Draft

feat: add force layout transform#529
RobertFrenken wants to merge 2 commits intosvelteplot:mainfrom
RobertFrenken:feat/force-transform

Conversation

@RobertFrenken
Copy link
Copy Markdown
Contributor

Summary

Add a d3-force-based layout transform for network/graph visualizations.

  • forceLayout(nodes, links, options) — runs a force simulation synchronously, returns positioned { nodes, links } for use with Dot and Arrow marks
  • forceNode(links, options) / forceLink(nodes, options) — curried factory pair following the *Node/*Link convention (like treeNode/treeLink in feat: tree transform #526). Paired factories share a WeakMap-based cache so the simulation runs only once per data array + options reference
  • Configurable forces: charge, center, link, collide, x, y, radial — each accepts an options object, a number shorthand, or false to disable. collide, x, y, radial are opt-in (not applied by default)
  • setup callback for advanced simulation customization, typed as Simulation<SimulationNodeDatum>

What's included

  • Core transform (src/lib/transforms/force.ts) — 432 lines, full TypeScript types and JSDoc
  • 20 unit tests (src/lib/transforms/force.test.ts) — both API tiers, custom nodeId, field preservation, invalid link filtering, option shorthands, mutation safety, empty inputs, single-node graph, disconnected graph, cache verification
  • 8 examples (src/routes/examples/force/) — basic graph, hover highlighting, link styling, performance-tuned, degree sizing, tooltip, edge weights, bubble chart
  • Doc page (src/routes/transforms/force/+page.md) — live example, forceNode/forceLink factory pattern example, full options and forces reference tables
  • 16 VR baselines (src/snapshots/force/) — light + dark for all 8 examples
  • New dependency: d3-force ^3.0.0 + @types/d3-force ^3.0.10
  • Exports added to src/lib/transforms/index.ts
  • Sidebar entry in config/sidebar.ts

Design decisions

  • Static/synchronous simulation — runs all ticks and returns final positions, matching Observable Plot's approach. A reactive useForceSimulation hook with per-frame animation is planned as a follow-up (would also benefit from canvas Arrow Canvas rendering for Arrow mark #26 and canvas Link Canvas rendering for Link marks #28)
  • Curried factory pattern for forceNode/forceLink — same convention as treeNode/treeLink in feat: tree transform #526. Hierarchy transforms produce new record sets rather than modifying existing channel data
  • Collision force opt-incollide defaults to off (like x/y/radial), not on. Graph layouts don't typically need collision detection, and radius=1 has negligible visual effect at typical node sizes
  • d3 phyllotaxis initialization — nodes are not pre-positioned at origin; d3-force's built-in phyllotaxis spiral provides better initial spread for convergence
  • Removed src/shims.d.ts — 47 lines of unused virtual module type declarations; pnpm check passes with 0 errors without it

Related issues

Test plan

  • pnpm test — 778 tests pass (92 files)
  • pnpm lint — 0 errors
  • pnpm check — 0 type errors
  • pnpm build — successful static build
  • VR baselines generated for all 8 examples (light + dark)

🤖 Generated with Claude Code

@netlify
Copy link
Copy Markdown

netlify bot commented Mar 24, 2026

Deploy Preview for svelteplot ready!

Name Link
🔨 Latest commit db6e263
🔍 Latest deploy log https://app.netlify.com/projects/svelteplot/deploys/69d6702386ff5d0008ecb6b6
😎 Deploy Preview https://deploy-preview-529--svelteplot.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

RobertFrenken and others added 2 commits April 8, 2026 15:11
Add d3-force-based layout transform for network/graph visualizations.

Two-tier API: forceLayout() for direct use, and forceNode()/forceLink()
factory pair following the *Node/*Link convention with shared simulation
cache. Configurable forces (charge, center, link, collide, x, y, radial)
with number shorthand and false-to-disable support.

Includes 8 examples (graph variants, bubble chart), doc page with live
examples and full options reference, 16 VR baselines, and 20 unit tests
covering both API tiers, edge cases, and cache behavior.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gka gka force-pushed the feat/force-transform branch from 9635302 to db6e263 Compare April 8, 2026 15:11
@gka
Copy link
Copy Markdown
Contributor

gka commented Apr 10, 2026

I think I'm going to hold off on this transform for the 1.0 milestone for two reasons:

  1. It's not part of Observable Plot so I think we should take some more time to think about it
  2. You can already use SveltePlot as "rendering engine" for a force-layouted network diagram, as you can see in this example. This approach has the added benefit of being dynamic, so the layout can respond to dataset changes, etc.

@gka gka added this to the post-v1 milestone Apr 10, 2026
@gka gka marked this pull request as draft April 10, 2026 09:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants