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
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 algorithmsweb/— TypeScript frontend: canvas renderer, UI controls, WASM bindings viawasm-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.