BlazePlot v0.3: Turning a Fast Renderer Into a Charting Toolkit

The latest BlazePlot updates add a proper plugin layer, React and linked charts, server-sampled data, exports, a native WebGL2 backend, and a cleaner documentation site.

May 21, 20269 min read
BlazePlot v0.3: Turning a Fast Renderer Into a Charting Toolkit

The first version of BlazePlot was mostly about proving the rendering path: keep the hot loop on WebGL2, keep data in typed arrays, and make dense time-series plots stay interactive when the browser would normally start to feel heavy.

That foundation still matters, but the latest update cycle was about a different question: can this become something I would actually want to embed in a real application?

The answer is much closer to yes now.

Across the v0.3 releases I moved BlazePlot from a fast plotting prototype into a more complete charting toolkit. Not a dashboard framework, and still not a kitchen-sink chart library, but a library with the pieces real scientific and engineering interfaces tend to need: interaction plugins, linked views, time axes, mobile gestures, exports, server-sampled data, React integration, and documentation that explains the tradeoffs instead of hiding them.

A plugin layer that does useful work

The biggest visible change is the plugin surface.

BlazePlot now has built-in plugins for selection, crosshairs, rulers, tooltips, legends, annotations, interactions, and navigator views. These are optional and imported through subpaths, so the base chart does not have to carry UI code you are not using.

import { Chart } from "blazeplot";
import { interactionsPlugin } from "blazeplot/plugins/interactions";
import { crosshairPlugin } from "blazeplot/plugins/crosshair";
import { navigatorPlugin } from "blazeplot/plugins/navigator";

const chart = new Chart(element, {
  plugins: [
    interactionsPlugin(),
    crosshairPlugin(),
    navigatorPlugin(),
  ],
});

This changed the shape of the project more than it might look from the outside. Before, a lot of behavior was either hard-coded into the chart or living in preview code. That is fine while testing rendering, but it does not scale. Selection, ruler measurements, hover markers, annotations, and navigator controls all need lifecycle hooks, access to chart coordinates, and a safe place to draw DOM or SVG overlays.

The plugin API forced that boundary to become clearer. The renderer still owns the plot. Plugins own interaction and UI around it.

There were a lot of small fixes behind this: crosshair readouts needed to clamp inside the chart, hover markers needed to layer above guide lines, navigator handles needed to resize from the correct edge, and pointer move refreshes needed to be coalesced to animation frames instead of doing unnecessary work during fast mouse movement. None of those are headline features, but they are the difference between a demo and a component that feels stable.

Better interaction on desktop and mobile

Interaction was another major part of the update.

BlazePlot now supports wheel zoom, pointer pan, box zoom, double-click reset, trackpad panning, smoother trackpad pinch zoom, touch pan, pinch zoom, and double-tap reset through the interactions plugin. Axis interactions also became more complete, including support for the right-side Y axis.

This came from testing the chart the way people actually use plots. A mouse wheel, a MacBook trackpad, and a phone screen do not produce the same kind of input. Treating them as the same thing makes the chart feel wrong very quickly. The recent commits added separate handling for coarse mouse-wheel zoom, fine-grained trackpad gestures, two-finger panning, and mobile touch gestures.

The mobile work is especially important because scientific tools are not always used at a desk. A phone is not the ideal device for dense plots, but it should still be possible to inspect a signal, pan through a window, or check a live value without fighting the UI.

Time-series ergonomics

A lot of BlazePlot usage is time-series data, so v0.3 added several pieces specifically for that workflow.

Time axis tick formatting is now built in. Chart titles and axis titles are supported. Axis tick density and scale behavior are configurable. There are also viewport policies for live streams, including followX behavior and automatic Y fitting, so an application can follow incoming data without constantly rewriting camera logic around the chart.

The data path also improved. UniformRingBuffer is now public and is the preferred dataset for fixed-rate streams. It stores Y values only and derives X from an initial value plus a fixed step. For sensors, telemetry, audio-like signals, and other uniformly sampled data, that avoids storing and copying repeated X values.

import { Chart, UniformRingBuffer } from "blazeplot";

const dataset = new UniformRingBuffer(120_000, {
  xStart: Date.now(),
  xStep: 10,
});

const chart = new Chart(element);
const signal = chart.addLine({ dataset, name: "signal" });

signal.appendY(new Float32Array([0.1, 0.12, 0.09, 0.14]));
chart.render();

That appendY path is small, but it matters. If the X spacing is implicit, the API should not force callers to manufacture an X array just to satisfy the chart. The library now has a YAppendableDataset contract for that case, and dataset-backed series no longer have to repeat a capacity value in their config.

Server-sampled data

Another important addition is ServerSampledDataset with downsample: "server".

Client-side min/max LOD is useful when the browser owns the full data window. But not every application works that way. Sometimes the server is the only place that can efficiently query the full history. Sometimes the dataset is too large to ship raw. Sometimes an API already returns pre-bucketed min/max values for the current viewport.

In those cases the client should not apply another sampler on top of already sampled data. ServerSampledDataset lets BlazePlot render either point samples or min/max buckets supplied by the backend. The chart still handles viewport, styling, overlays, and interaction, but the expensive historical reduction can happen where the data lives.

I added a server-sampled preview around this idea, using public Binance data: historical kline buckets from REST, plus a live candle built from public aggregate trade messages. That preview was useful because financial data has the same shape as a lot of scientific data: dense history, live updates, and a need to preserve extremes instead of smoothing them away.

React, linked charts, and application integration

BlazePlot now ships a blazeplot/react entry point. The wrapper is intentionally thin: React owns the container and lifecycle, BlazePlot owns the chart instance. That is the right split for this kind of library. Re-rendering a React component should not mean rebuilding GPU buffers.

There is also a linked charts helper under blazeplot/linked, plus a leaner blazeplot/linked-core subpath if you only want layout and X-range synchronization without pulling in tooltip and crosshair sync helpers.

This is aimed at the kind of UI where one panel shows a primary signal and another panel shows derived data, volume, residuals, or detector metadata. Shared X, independent Y. A single chart is useful; synchronized panels are closer to how real analysis tools are built.

Export and screenshots

Export support also got more serious.

chart.screenshot() now handles the WebGL canvas together with built-in DOM and SVG overlays, so titles, axes, annotations, and plugin overlays are not lost when the user exports an image. The screenshot composition was moved into a lazy-loaded chunk so the normal chart path does not pay for export code until it is needed.

There is also a new blazeplot/data subpath for collecting visible data, selected data, or all chart data, then serializing it as CSV or JSON. It includes small transform helpers such as binning and rolling means. This is not meant to become a data-frame library. It is just the basic set of utilities I keep needing around plots: inspect what is visible, export a selected window, and hand it to another tool.

Native WebGL2 backend and smaller imports

Earlier BlazePlot versions used a rendering dependency while the rendering model was still moving quickly. The latest version replaces that with a native WebGL2 backend and keeps a compatibility alias for the old backend name.

That change matters for control. BlazePlot has a very specific rendering path: dense min/max line segments, GPU buffers, explicit projection handling, context restoration, and predictable draw calls. Owning the backend directly makes that path easier to optimize and easier to reason about.

The package exports were also split into lean public subpaths:

  • blazeplot/core
  • blazeplot/interaction
  • blazeplot/render
  • blazeplot/react
  • blazeplot/linked
  • blazeplot/data
  • blazeplot/export
  • individual plugin entry points

That keeps chart-only imports smaller and makes the package less all-or-nothing. The current production build for the core chart runtime is about 139 KiB raw, or 34 KiB gzip, without optional plugins. Bundle-size checks now run in CI so growth is visible instead of accidental.

More testing, less guessing

A lot of this update cycle was not about adding public API. It was about making releases less fragile.

There are now browser visual tests, automated interaction tests, package export smoke tests, bundle-size checks, benchmark smoke tests, and release notes that can include benchmark output. The browser fixtures were moved under tests/browser/, and the public website now has a cleaner docs and preview structure.

That might sound like housekeeping, but for a rendering library it is not optional. A small change to coordinate transforms, device-pixel handling, or overlay layout can look fine in one manual preview and be broken everywhere else. Visual and interaction tests are the safety net that lets the library keep moving.

The docs are now part of the product

The documentation site at blazeplot.cervelli.dev has been rebuilt around actual usage paths: overview, examples, data semantics, performance recipes, built-in plugins, plugin authoring, theming and layout, browser support, migration notes, roadmap, and API reference.

That was overdue. BlazePlot has several intentional constraints: WebGL2 is required, X values need to be sorted for fast range queries, dense views use LOD, and optional plugins are split into subpaths. Those are reasonable tradeoffs, but only if they are documented clearly.

I do not want users to discover the library's assumptions by stepping through source code.

Where this leaves BlazePlot

The main difference after these updates is that BlazePlot now has a shape.

It is no longer just “a fast WebGL line chart.” It is becoming a focused plotting toolkit for real-time, dense, mostly time-series data. It has a clearer plugin model, better input handling, a practical data story, React support, linked charts, exports, a native renderer, and a documentation site that matches the library instead of lagging behind it.

There is still plenty to do. I want more examples, better benchmarks against real workloads, more polish around financial and scientific overlays, and continued work on bundle boundaries. But the foundation is much more useful now than it was a few releases ago.

If you want to try it:

bun add blazeplot

And the docs and previews are now live at blazeplot.cervelli.dev.

Federico Cervelli

Federico Cervelli

Computer Science graduate and Software Developer at CAEN S.p.A. This blog is my digital lab for architectural deep-dives, technical experiments, and personal reflections.