Skip to content

ScatterChart

Defined in: packages/pixi-charts/src/charts/ScatterChart.ts:195

Scatter plot — the library’s performance flagship.

Built for the 100k-point regime via a single batched ParticleContainer draw call: every point is a PixiJS v8 Particle sharing one tinted texture, so streaming stays smooth at scales where per-point Canvas libraries become unresponsive. Pointer hit-tests answer in O(log n) via a SpatialIndex (d3-quadtree) instead of a linear scan. See the Performance page for live numbers on commodity hardware.

ParticleContainer, not Graphics or custom shaders. Graphics (one circle per point) collapses above ~5k points; custom WebGL shaders are faster still but add a shader-compilation/debugging surface not worth it for v1. ParticleContainer is purpose-built for “many sprites, one texture, one draw call”. A custom-shader pass is a documented future optimization if profiling ever demands it.

One white texture + per-particle tint (not pre-baked per-colour textures). PIXI v8’s Particle supports a per-particle tint over a shared texture while still batching into a single draw — so a single white circle texture, tinted per point, covers categorical and continuous colour with no per-colour bake step and exactly one texture to free. (The “pre-bake one texture per colour” workaround is a PIXI v7 concern; v8’s per-particle tint makes it unnecessary.)

Square-root size scale. With a size encoding, radius ∝ √value so that area (visual weight) ∝ value. Scaling radius linearly with value — the common footgun — makes area grow with value², badly overstating large values. Range defaults to [3, 12] px (d3-scale’s scaleSqrt).

Viridis for continuous colour. A 'quantitative' colour encoding maps through a sequential interpolator (default viridis — perceptually uniform and colourblind-safe), paired with a continuous Legend. Categorical colour uses the discrete palette + categorical legend, like every other chart.

Static view: hover shows a tooltip, leave hides it, click is a wired no-op. No zoom/pan/brush, no multi-series shapes, circles only, no jitter. A size legend is a deliberate future addition (colour legends ship now).

const chart = new ScatterChart({ container, spec });
await chart.init(); // creates the PIXI app AND does the first render
chart.update(newRows); // warm path: reuses GL context + ParticleContainer
chart.destroy(); // idempotent; frees the shared texture + primitives

Construction is pure. Chart.update reuses the persistent ParticleContainer, particle texture, axes, legend, and interaction layer; only the data-derived particles, spatial index, scale domains, and axis ticks are recomputed. Chart.update ignores { animate: true } for scatter (always snaps) — animated updates across changing point counts genuinely need diffing.

Texture lifecycle. The shared particle texture is GPU-backed, baked once, and not freed by the base class’s app.destroy({ texture: false }); this class destroys it explicitly in destroy (it lives for the chart’s lifetime — not recreated per render or per update). Skipping that is a real per-instance GPU leak — covered by a test.

For most use cases, prefer the declarative render entry point — use this class directly only when you need fine-grained lifecycle control.

new ScatterChart(opts): ScatterChart

Defined in: packages/pixi-charts/src/charts/ScatterChart.ts:257

ScatterChartOptions

ScatterChart

Chart.constructor

get destroyed(): boolean

Defined in: packages/pixi-charts/src/core/Chart.ts:155

true once destroy has run.

boolean

Chart.destroyed


get initialized(): boolean

Defined in: packages/pixi-charts/src/core/Chart.ts:160

true once init has completed.

boolean

Chart.initialized

destroy(): void

Defined in: packages/pixi-charts/src/charts/ScatterChart.ts:282

Destroy every owned primitive plus the shared particle texture, in addition to the base-class teardown. Idempotent.

void

Chart.destroy


init(): Promise<void>

Defined in: packages/pixi-charts/src/charts/ScatterChart.ts:271

Override of Chart.init: after the PIXI Application is ready, runs the first render so the spec dispatcher hands back a fully-rendered chart.

Promise<void>

Chart.init


off(_event, handler): void

Defined in: packages/pixi-charts/src/core/Chart.ts:259

Remove a previously registered click handler. No-op if the handler wasn’t registered, or after destroy.

"click"

ChartEventHandler

void

Chart.off


on(_event, handler): () => void

Defined in: packages/pixi-charts/src/core/Chart.ts:248

Register a handler for a chart event. Returns an unsubscribe function; calling it (or off) removes the handler.

Currently only 'click' is supported. Handlers receive a ChartClickEvent describing the clicked datum, its index, the plot-area-local pixel position, and (for multi-series charts) the series name. The library reports the click; what it means is up to the consumer — pair with update to build drilldown.

Handlers are cleared automatically on destroy.

"click"

ChartEventHandler

() => void

const off = chart.on('click', (event) => {
console.log('clicked', event.datum, 'at index', event.index);
});
// later: off(); // or: chart.off('click', handler);

Chart.on


update(newData, options?): void

Defined in: packages/pixi-charts/src/core/Chart.ts:218

Swap the chart’s data without recreating the WebGL context. Reuses the existing PixiJS application, scales infrastructure, axes, legend, and interaction layer; recomputes scales, geometry, and hit-testing from the new data.

update() cannot change the chart type, encoding, orientation, donut inner radius, or color scheme — only the rows in data. To change any of those, destroy() this chart and construct a fresh one.

Synchronous. The PixiJS application is already initialised; updating is just recompute + redraw.

readonly Record<string, unknown>[]

UpdateOptions

void

If called before init has resolved.

If called after destroy, this is a silent no-op.

Chart.update