pixi-heatmap
Smooth GPU heatmaps for PixiJS v8 — one code path for WebGL + WebGPU, from static datasets to extreme streaming workloads.
Installation
Usage
Create a HeatmapLayer, feed it points, add it to the stage, and call update(renderer) every frame — it's dirty-gated, so unchanged frames cost nothing.
Options
Everything is optional except width and height.
Dynamic updates
Stream points in over time — with decay the heat fades, which is ideal for cursor trails and live data.
Interaction
Choose direct Pixi transforms in Manual mode or pixi-viewport as the camera driver. Both paths render the same 150k NYC taxi pickups and preserve the same screen-stable heat field across pan, zoom, and rotation: ⌘/Ctrl + scroll to zoom, middle-drag to pan (touch: drag and pinch). The palette controls start from the library default, update its four visible color stops live, and generate perceptually spaced random palettes.
pixi-viewport driver code
Aggregate capacity benchmark
Every value-1 record enters addPoints() and contributes to a fixed 2px grid. Select 1M, 100M, or 1B; preparation progress and ETA measure the complete input workload. The retained field continuously sweeps 0.6×–6× zoom while panning, with a Manual camera available for direct control.
API Reference
HeatmapLayerOptions
widthnumber
Width of the heatmap in pixels. Required.
heightnumber
Height of the heatmap in pixels. Required.
optimization'manual' | 'auto' Default is 'manual'
Coordinate precision, normalization, tone mapping, aggregation, Worker use, and resolution from camera range, data size, device capacity, transform activity, and frame time. Explicit options remain authoritative.
radiusnumber Default is 25
Kernel radius of each point, in pixels.
minZoomnumber
Lower bound for the effective zoom used by kernel compensation and radius-derived aggregation.
maxZoomnumber
Upper bound for the effective zoom used by kernel compensation and radius-derived aggregation.
gradientRecord<number, string> Default is blue → red ramp
Color ramp stops as offset → color, from 0 (cold) to 1 (hot).
maxIntensitynumber | 'auto' Default is 10
Intensity mapped to the top of the ramp; auto optimization supplies 'auto'.
toneMapping'linear' | 'adaptive' Default is 'linear'
Transfer normalized density into the gradient. Auto optimization supplies adaptive peak compression so weak positive density stays visible.
accumulationPrecision'balanced' | 'high' Default is 'balanced'
Auto optimization supplies high precision; capable devices use 32-bit float accumulation.
webgpuDensity'auto' | 'raster' | 'gather' Default is 'auto'
Select instanced raster accumulation or the tiled WebGPU compute gather. Auto compares point, accumulation-pixel, and kernel work for each supported direct field.
resolutionnumber | 'adaptive' Default is 'adaptive'
Accumulation buffer scale; 'adaptive' lowers it under load to hold frame rate.
aggregationboolean Default is true
Keep smaller radius-following datasets as raw splats, then use a continuous bilinear grid under larger loads.
aggregationCellSizenumber Default is radius / 4
Use fixed weighted-centroid bins at this local-pixel cell size.
pointStorage'retained' | 'aggregate' Default is 'retained'
Retain every source record, or incrementally fold append batches into a fixed Cloud-in-Cell grid whose memory depends on fixed layer bounds and aggregation cell size.
workerboolean | 'auto' Default is 'auto'
Offload aggregation to a Web Worker; 'auto' uses one when the browser supports it.
decaynumber Default is 0
Per-frame heat decay; values above 0 fade old heat over time.
minOpacitynumber Default is 0
Opacity floor for faint areas, so sparse data stays visible.
Methods
optimizationProfile exposes the current heuristic and effective option decisions. A bounded zoom span of at least 8× with up to 160,000 active points on a desktop-sized instance chunk selects direct splats at fixed full resolution. Other sources use radius-following aggregation and adaptive resolution. The profile also reports the active tone-mapping exponent.
setPoints(points)(points: HeatmapPoint[]) => void
Replace the dataset; each point is { x, y, value? } with finite coordinates and a finite, non-negative value.
setRaw(points, count?)(points: Float32Array, count?: number) => void
Replace the dataset from interleaved (x, y, weight) triples using finite coordinates and finite, non-negative weights; this path validates every record and copies the active triples into retained storage.
setRawTrusted(points, statistics)(points: Float32Array, statistics: HeatmapTrustedRawOptions) => void
Borrow caller-validated, strictly positive interleaved triples with exact precomputed weight statistics. Retained point storage with decay 0 gives this streaming path O(1) ingestion.
addPoint(point)(point: HeatmapPoint) => void
Append a single point without replacing the dataset.
addPoints(points, count?)(points: HeatmapPoint[] | Float32Array, count?: number) => void
Append an object array or interleaved typed batch in one call. Aggregate point storage folds the batch into its persistent field during ingestion.
pointCount / totalWeight / renderPointCountnumber
Read the accepted representative count, summed contribution weight, and retained GPU splat count.
aggregateStorageBytesnumber | null
Read fixed-grid CPU storage in aggregate mode; retained mode reports null.
densityEngine / webgpuGatherStatsstring / WebgpuGatherStats
Read the latest complete density engine and the WebGPU compute dispatch, fallback, grid, and capability diagnostics.
clear()() => void
Remove all points.
moveTo(x, y) / moveBy(dx, dy)(...values: number[]) => void
Set or offset the layer position in parent coordinates.
zoomTo(scale, x?, y?) / zoomBy(factor, x?, y?)(...values: number[]) => void
Set or multiply display scale while keeping the optional parent-space anchor fixed.
rotateTo(radians, x?, y?) / rotateBy(delta, x?, y?)(...values: number[]) => void
Set or add rotation while keeping the optional parent-space anchor fixed.
setOpacity(opacity)(opacity: number) => void
Set the display alpha from 0 to 1.
setZoom(zoom)(zoom: number) => void
Pin the kernel scaling manually and stop following ancestor transforms. By default the layer auto-tracks its worldTransform scale, which keeps on-screen density stable inside scaled parents such as pixi-viewport. Configured minZoom and maxZoom clamp this effective value while display transforms keep scaling.
resumeAutoZoom()() => void
Re-enable worldTransform tracking after a manual setZoom().
resize(width, height)(width: number, height: number) => void
Resize retained-source layers and their accumulation buffers. Aggregate point storage uses fixed dimensions and a new layer for a different size.
update(renderer)(renderer: Renderer) => void
Flush pending changes to the GPU; dirty-gated, call every frame.
destroy()() => void
Release all GPU resources.
Credits
Built on PixiJS. The accumulation-and-ramp pipeline borrows techniques from the mapbox-gl and deck.gl heatmap layers.