November 2024

Graph Visualizer

An interactive force-directed graph renderer built in Rust compiled to WebAssembly. Visualizes arbitrary node/edge data with real-time physics simulation.

  • Rust
  • WebAssembly
  • Canvas
  • TypeScript

Interactive demo loading…

Set demoUrl in frontmatter to embed a live demo

Overview

This project started as an experiment in compiling Rust to WebAssembly for performance-sensitive browser code. The force-directed layout algorithm runs entirely in WASM, with JavaScript handling only the canvas rendering calls and user input.

The result is a graph renderer that can handle thousands of nodes at 60fps — something that would be impractical with a pure JS physics loop.

Architecture

The project is split into two parts:

  • graph-core/ — Rust library: graph data structures, force simulation (Barnes-Hut approximation), layout algorithms
  • web/ — TypeScript frontend: canvas renderer, UI controls, WASM bindings via wasm-bindgen
// Force simulation tick
pub fn tick(&mut self) {
    self.apply_repulsion();
    self.apply_springs();
    self.apply_gravity();
    self.integrate(0.016); // ~60fps timestep
}

Key decisions

Why Barnes-Hut? Naïve O(n²) repulsion becomes unusable beyond ~500 nodes. The Barnes-Hut approximation reduces this to O(n log n) by grouping distant nodes into quadtree cells.

Why WASM instead of a JS physics library? At the time, every JS force-graph library I found had the same fundamental bottleneck: the physics loop is synchronous and blocks the main thread. Offloading to WASM (and eventually a Web Worker) keeps the UI responsive.

What I’d do differently

The WASM ↔ JS boundary is expensive if you cross it too often. The current design copies the full position array to JS each frame — fine for now, but SharedArrayBuffer with an atomic lock would eliminate the copy for large graphs.