Wasm-Driven Interactive Rendering Pipeline
Charton can be compiled to WebAssembly (WASM), bringing Rust's near-native performance to the browser. This enables a high-performance interaction model that handles large-scale datasets with lower latency than traditional JavaScript-based visualization libraries.
When a user interacts with a Charton chart compiled to Wasm, the pipeline works as follows:
- The browser captures a user event—e.g., a drag event for zooming or a brush gesture for selecting a range.
- Using
wasm-bindgen, the event details are passed into the Charton Rust core. - The Rust engine performs full or partial chart recomputation. These operations run at native-like speed inside Wasm.
- Charton generates a new SVG string or structured DOM patch representing the new view.
- The browser replaces the old SVG node with the new one.
Charton’s Wasm-driven model has several performance advantages:
1. Polars performance inside Wasm Traditional JS libraries rely on JavaScript arrays, D3 computations, or slower JS-based DataFrame libraries. Charton instead executes Polars in Wasm—offering:
- zero-copy columnar data
- vectorized operations
- multi-threaded execution (where supported)
2. Rust efficiency All chart logic—scales, encodings, transforms, layouts—is executed in compiled Rust, not interpreted JS.
Charton + Polars + wasm-bindgen — step-by-step example
Goal: expose a
draw_chart()function from Rust → returns an SVG string → JavaScript inserts that SVG into the DOM.
0) Prerequisites
- Rust toolchain (stable), with
rustup. wasm-pack(recommended) ORwasm-bindgen-cli+cargo build --target wasm32-unknown-unknown.-
Install
wasm-pack(recommended):cargo install wasm-pack
-
clang(required)- Linux:
apt install clang - Windows: Download and run the LLVM installer from LLVM Releases. During installation, select "Add LLVM to the system PATH".
- Linux:
- A simple static file server (e.g.
basic-http-serverfrom cargo,python -m http.server, orservevia npm). - Node/ npm only if you want to integrate into an NPM workflow; not required for the simple demo.
Important compatibility note (read before you start):
Many crates (especially heavy ones like polars or visualization crates) may have limited or no support for wasm32-unknown-unknown out of the box. If Polars and Charton compile to wasm in your environment, the steps below will work. If they don't, read the Caveats & alternatives section at the end.
1) Project layout
Assume you created a project:
web
├── Cargo.toml
├── index.html
├── pkg
│ ├── package.json
│ ├── web_bg.wasm
│ ├── web_bg.wasm.d.ts
│ ├── web.d.ts
│ └── web.js
└── src
└── lib.rs
We will build a cdylib wasm package that wasm-pack will wrap into pkg/.
2) Cargo.toml(example)
Put this into web/Cargo.toml.
[package]
name = "web"
version = "0.1.0"
edition = "2021" # Important: Stable standard for Wasm/Polars. Don't upgrade to 2024 yet to avoid toolchain conflicts.
# Produce a cdylib for wasm
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
polars = { version = "0.49", default-features = false }
# Avoids transitive mio dependency to ensure Wasm compatibility.
polars-io = { version = "0.49", default-features = false, features = ["parquet"] }
charton = { version = "0.4" }
[profile.release]
opt-level = "z" # or "s" to speed up
lto = true
codegen-units = 1
panic = "abort"
3) src/lib.rs-Rust (wasm entry points)
Create web/src/lib.rs.
#![allow(unused)] fn main() { use wasm_bindgen::prelude::*; use polars::prelude::*; use charton::prelude::*; // Build a small scatter plot and return the SVG string. #[wasm_bindgen] pub fn draw_chart() -> Result<String, JsValue> { // Create a tiny DataFrame let df = df![ "length" => [5.1, 4.9, 4.7, 4.6, 5.0, 5.4, 4.6, 5.0, 4.4, 4.9], "width" => [3.5, 3.0, 3.2, 3.1, 3.6, 3.9, 3.4, 3.4, 2.9, 3.1] ].map_err(|e| JsValue::from_str(&e.to_string()))?; // Build a Charton Chart let chart = Chart::build(&df) .map_err(|e| JsValue::from_str(&e.to_string()))? .mark_point()? .encode((x("length"), y("width"))) .map_err(|e| JsValue::from_str(&e.to_string()))?; let svg = chart.to_svg() .map_err(|e| JsValue::from_str(&e.to_string()))?; // Returns SVG string Ok(svg) } }
Key points:
#[wasm_bindgen]exposes functions to JS.- We return
Result<String, JsValue>so JS receives errors as exceptions.
4) Build with wasm-pack (recommended)
From project root (web/):
wasm-pack build --release --target web --out-dir pkg
wasm-pack will:
-
compile to
wasm32-unknown-unknown, -
run
wasm-bindgento generate JS wrapper(s), -
produce a
pkg/folder containing:web_bg.wasmweb_bg.wasm.d.tsweb.d.tsweb.js(ES module bootstrap)
💡Optimization Note: Binary Size
After building in
--releasemode, the resultingweb_bg.wasmis approximately 4 MB. However, for web production:
- Gzip compression reduces it to about 900 KB.
- Brotli compression can shrink it even further. This compact footprint makes it highly suitable for browser-side data processing without long loading times.
5) Creating index.html (Client-Side Loader)
The final step is to create a minimal HTML file (web/index.html) that loads the generated WASM module and renders the SVG chart into the page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Charton WASM Demo</title>
</head>
<body>
<div id="chart-container"></div>
<script type="module">
import init, { draw_chart } from './pkg/web.js';
async function run() {
// Initialize and load the WebAssembly module
await init();
// Call the Rust function that returns an SVG string
const svg = draw_chart();
// Insert the SVG into the page
document.getElementById("chart-container").innerHTML = svg;
}
run();
</script>
</body>
</html>
This minimal version:
- Loads the WASM module generated by
wasm-pack - Calls the Rust function
draw_chart()to generate the SVG string - Injects the SVG directly into the DOM
- Contains no additional CSS, error handling, or panic hooks — keeping the example simple and focused
This is the recommended simplest setup for demonstrating Charton rendering through WebAssembly.
6) Serve the folder
Browsers enforce CORS for WASM; open the page via HTTP server rather than file://.
Minimal options:
cd web
python -m http.server 8080
Then open http://localhost:8080/index.html and you'll see the chart in the browser:

7) Troubleshooting
Processing heavy libraries like Polars in WASM can strain your system. Here is how to handle common bottlenecks:
- Compilation Hangs/Freezes: Building Polars for WASM is extremely CPU and RAM intensive. If your computer "freezes" during the
Optimizing with wasm-optstage, you can manually stop the process. The compiled.wasmfile inpkg/is usually already functional; it will simply be larger in size without the final optimization. For a smooth experience, a machine with high-core counts and 16GB+ RAM is recommended. - wasm-opt Errors: If
wasm-packfails because it cannot install or runwasm-opt, you can simply ignore the error if thepkg/folder was already populated. The unoptimized WASM file will still run in the browser. - Polars Version Incompatibility: If your project requires a Polars version uncompatible with the one used by Charton, passing a DataFrame directly will cause a compilation error. In this case, you can use the Parquet Interoperability method described in Section 2.3.4.
Charton + Polars + wasm-bindgen — advanced example: dynamic CSV visualization
Goal: Beyond a static demo, we now build a functional tool: users upload a local CSV file (e.g., iris.csv, which can be found and downloaded from the datasets/ folder in this project) → JavaScript reads it as a string → Rust/Polars parses the data in-browser → Charton generates a multi-colored scatter plot → The resulting SVG is rendered instantly.
1) Updated Cargo.toml
Update Note: This file updates the dependencies from 9.2.2 by enabling the csv feature in polars-io (to handle user uploads) and switching to the charton crate for more advanced encoding.
[package]
name = "web"
version = "0.1.0"
edition = "2021" # Important: Stable standard for Wasm/Polars. Don't upgrade to 2024 yet to avoid toolchain conflicts.
# Produce a cdylib for wasm
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
polars = { version = "0.49", default-features = false }
# Avoids transitive mio dependency to ensure Wasm compatibility.
polars-io = { version = "0.49", default-features = false, features = ["parquet", "csv"] }
charton = { version = 0.4 }
[profile.release]
opt-level = "z" # or "s" to speed up
lto = true
codegen-units = 1
panic = "abort"
2) Updated src/lib.rs
Update Note: This replaces the hard-coded draw_chart from 9.2.2. The new draw_chart_from_csv function accepts a String from JavaScript and uses std::io::Cursor to treat that string as a readable file stream for Polars.
#![allow(unused)] fn main() { use wasm_bindgen::prelude::*; use polars::prelude::*; use charton::prelude::*; use std::io::Cursor; #[wasm_bindgen] pub fn draw_chart_from_csv(csv_content: String) -> Result<String, JsValue> { /* * 1. Parse CSV data from String. * We use a Cursor to treat the String as a readable stream for Polars. */ let cursor = Cursor::new(csv_content); /* * 2. Initialize the Polars DataFrame. * CsvReader is highly optimized but runs in a single thread in standard WASM. */ let df = CsvReader::new(cursor) .finish() .map_err(|e| JsValue::from_str(&format!("Polars Error: {}", e)))?; /* * 3. Construct the Scatter Plot. * Ensure that the columns "length" and "width" exist in your CSV file. */ let chart = Chart::build(&df) .map_err(|e| JsValue::from_str(&e.to_string()))? .mark_point() .encode((x("sepal_length"), y("sepal_width"), color("species"))) .map_err(|e| JsValue::from_str(&e.to_string()))?; /* * 4. Generate SVG. * The to_svg() method returns a raw XML string representing the vector graphic. */ let svg = chart.to_svg() .map_err(|e| JsValue::from_str(&e.to_string()))?; Ok(svg) } }
3) Updated index.html
Update Note: This expands the simple loader from 9.2.2 by adding a File Input UI and a FileReader event loop. This allows the WASM module to process "live" data provided by the user.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WASM CSV Visualizer</title>
<style>
#chart-container { margin-top: 20px; border: 1px solid #ccc; }
</style>
</head>
<body>
<h2>Upload CSV to Generate Chart</h2>
<input type="file" id="csv-upload" accept=".csv" />
<div id="chart-container"></div>
<script type="module">
import init, { draw_chart_from_csv } from './pkg/web.js';
async function run() {
// Initialize the WASM module
await init();
const fileInput = document.getElementById("csv-upload");
const container = document.getElementById("chart-container");
// Event listener for file selection
fileInput.addEventListener("change", async (event) => {
const file = event.target.files[0];
if (!file) return;
/* * Use FileReader to read the file content as text.
* This text is then passed across the JS-WASM boundary.
*/
const reader = new FileReader();
reader.onload = (e) => {
const csvContent = e.target.result;
try {
// Call the Rust function with the CSV string
const svg = draw_chart_from_csv(csvContent);
// Inject the returned SVG string directly into the DOM
container.innerHTML = svg;
} catch (err) {
console.error("Computation Error:", err);
alert("Error: Make sure CSV has 'length' and 'width' columns.");
}
};
// Trigger the file read
reader.readAsText(file);
});
}
run();
</script>
</body>
</html>
4) Build and Serve Update Note: The build command remains the same as 9.2.3, but the compilation time may increase due to the added CSV and color encoding features.
# Build the package
wasm-pack build --release --target web --out-dir pkg
# Serve the files
python -m http.server 8080
Summary of Improvements over 9.2.3
- Data Handling: Shifted from static
df!macros to dynamicCsvReaderparsing. - Complexity: Added
colorencoding in Charton to demonstrate multi-dimensional data mapping. - User Interaction: Introduced the
FileReaderAPI to bridge the gap between the local file system and WASM linear memory.
Conclusion
The combination of static SVG and dynamic Rust/Wasm computation forms a powerful model for interactive visualization:
- SVG provides simple, portable output for embedding and styling.
- Rust/Wasm enables high-performance chart recomputation.
- Polars accelerates data transformations dramatically.
- Browser handles final rendering efficiently.
Charton does not attempt to patch SVGs with JavaScript like traditional libraries. Instead, it regenerates a complete static SVG—fast enough to support real-time interactivity.
This architecture makes high-performance, browser-based interaction not only possible but highly efficient.