WebAssembly (WASM) opens up new possibilities for bringing high-performance computing to web browsers. Combined with Rust’s memory safety and performance characteristics, it’s an excellent choice for computationally intensive visualization tasks.
Traditional JavaScript-based visualizations can struggle with:
WebAssembly addresses these limitations by providing near-native performance in the browser.
First, install the necessary tools:
# Install wasm-pack
cargo install wasm-pack
# Create a new Rust library
cargo new --lib wasm-visualization
cd wasm-visualization
Configure Cargo.toml:
[package]
name = "wasm-visualization"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
js-sys = "0.3"
web-sys = "0.3"
serde = { version = "1.0", features = ["derive"] }
serde-wasm-bindgen = "0.6"
[dependencies.web-sys]
version = "0.3"
features = [
"CanvasRenderingContext2d",
"Document",
"Element",
"HtmlCanvasElement",
"Window",
"console",
]
Let’s create a high-performance particle system for visualizing data points:
use wasm_bindgen::prelude::*;
use web_sys::{CanvasRenderingContext2d, HtmlCanvasElement};
use js_sys::Math;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
macro_rules! console_log {
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}
#[wasm_bindgen]
pub struct ParticleSystem {
particles: Vec<Particle>,
width: f64,
height: f64,
}
struct Particle {
x: f64,
y: f64,
vx: f64,
vy: f64,
color: String,
size: f64,
data_value: f64,
}
#[wasm_bindgen]
impl ParticleSystem {
#[wasm_bindgen(constructor)]
pub fn new(width: f64, height: f64, count: usize) -> ParticleSystem {
let mut particles = Vec::with_capacity(count);
for _ in 0..count {
particles.push(Particle {
x: Math::random() * width,
y: Math::random() * height,
vx: (Math::random() - 0.5) * 2.0,
vy: (Math::random() - 0.5) * 2.0,
color: format!("hsl({}, 70%, 50%)", Math::random() * 360.0),
size: Math::random() * 5.0 + 2.0,
data_value: Math::random(),
});
}
ParticleSystem {
particles,
width,
height,
}
}
pub fn update(&mut self, delta_time: f64) {
for particle in &mut self.particles {
// Update position
particle.x += particle.vx * delta_time;
particle.y += particle.vy * delta_time;
// Bounce off edges
if particle.x <= 0.0 || particle.x >= self.width {
particle.vx *= -1.0;
}
if particle.y <= 0.0 || particle.y >= self.height {
particle.vy *= -1.0;
}
// Clamp position
particle.x = particle.x.clamp(0.0, self.width);
particle.y = particle.y.clamp(0.0, self.height);
}
}
pub fn render(&self, context: &CanvasRenderingContext2d) {
context.clear_rect(0.0, 0.0, self.width, self.height);
for particle in &self.particles {
context.begin_path();
context.set_fill_style(&JsValue::from_str(&particle.color));
context
.arc(
particle.x,
particle.y,
particle.size,
0.0,
2.0 * std::f64::consts::PI,
)
.unwrap();
context.fill();
}
}
pub fn add_data_point(&mut self, x: f64, y: f64, value: f64) {
if let Some(particle) = self.particles.iter_mut().find(|p| p.data_value == 0.0) {
particle.x = x;
particle.y = y;
particle.data_value = value;
particle.size = value * 10.0;
particle.color = if value > 0.5 {
"rgb(255, 100, 100)".to_string()
} else {
"rgb(100, 100, 255)".to_string()
};
}
}
}
Build the WASM module:
wasm-pack build --target web --out-dir pkg
Create an HTML page to use the visualization:
<!DOCTYPE html>
<html>
<head>
<title>WASM Data Visualization</title>
<style>
canvas {
border: 1px solid #ccc;
display: block;
margin: 20px auto;
}
.controls {
text-align: center;
margin: 20px;
}
</style>
</head>
<body>
<div class="controls">
<button id="addData">Add Random Data</button>
<button id="reset">Reset</button>
<span id="fps">FPS: 0</span>
</div>
<canvas id="canvas" width="800" height="600"></canvas>
<script type="module">
import init, { ParticleSystem } from './pkg/wasm_visualization.js';
async function run() {
await init();
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Create particle system
const system = new ParticleSystem(800, 600, 5000);
let lastTime = 0;
let frameCount = 0;
let lastFpsUpdate = 0;
function animate(currentTime) {
const deltaTime = (currentTime - lastTime) / 1000.0;
lastTime = currentTime;
// Update and render
system.update(deltaTime);
system.render(ctx);
// Update FPS counter
frameCount++;
if (currentTime - lastFpsUpdate > 1000) {
document.getElementById('fps').textContent =
`FPS: ${frameCount}`;
frameCount = 0;
lastFpsUpdate = currentTime;
}
requestAnimationFrame(animate);
}
// Event handlers
document.getElementById('addData').addEventListener('click', () => {
for (let i = 0; i < 10; i++) {
system.add_data_point(
Math.random() * 800,
Math.random() * 600,
Math.random()
);
}
});
document.getElementById('reset').addEventListener('click', () => {
system.free();
system = new ParticleSystem(800, 600, 5000);
});
requestAnimationFrame(animate);
}
run();
</script>
</body>
</html>
Rust’s ownership system prevents memory leaks, but be careful with WASM bindings:
#[wasm_bindgen]
impl ParticleSystem {
// Explicitly free memory when done
pub fn destroy(&mut self) {
self.particles.clear();
self.particles.shrink_to_fit();
}
}
For datasets with millions of points, consider:
web-sys to access WebGL APIsuse web_sys::{WebGlRenderingContext, WebGlBuffer};
#[wasm_bindgen]
pub struct WebGLParticleSystem {
gl: WebGlRenderingContext,
vertex_buffer: WebGlBuffer,
position_data: Vec<f32>,
}
Monitor performance with browser dev tools and Rust profiling:
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = performance)]
fn now() -> f64;
}
pub fn benchmark_update(&mut self) {
let start = now();
self.update(0.016); // 60 FPS
let end = now();
console_log!("Update took: {}ms", end - start);
}
We’ve successfully used this approach for:
Compared to pure JavaScript implementations:
WebAssembly with Rust provides a powerful combination for data visualization:
The initial setup complexity is offset by significant performance gains and the ability to handle much larger datasets than traditional web technologies allow.
Start small with simple visualizations and gradually add complexity as you become comfortable with the WASM workflow.