Orderbook Heatmap (Depth)
Render real-time orderbook depth as a heatmap behind the candles on the main chart canvas. The depth matrix protocol, real-time append API, color scheme and visibility controls.
The orderbook heatmap is the view no other web charting library ships: real-time bid/ask depth painted as a color-mapped layer behind the candles, in the same canvas, on the same render loop. This page documents the data shape it expects and every method that touches it.
Looking for a stand-alone price-ladder DOM (vertical ladder, per-tick bid/ask sizes) or a multi-exchange depth profile? Those are different modules — see DOM Ladder, Orderbook · Depth Profile, and Tick Stream. This page is the depth-heatmap layer embedded in the main chart.
What is the depth matrix?
The engine consumes a 2D matrix where:
- Rows correspond to price levels (one row = one price-bucket).
- Columns correspond to time (one column = one orderbook snapshot at some timestamp).
- Each cell holds the size resting at that price at that time. Positive = bid (resting buys below mid), negative = ask (resting sells above mid).
t0 t1 t2 t3 t4
┌─────┬─────┬─────┬─────┬─────┐
$60000 │ 0 │ 0 │ -8 │ -12 │ -15 │ ← asks (top of book, sells)
$59950 │ 0 │ -22 │ -30 │ -28 │ -25 │
$59900 │ -45 │ -50 │ -55 │ -60 │ -70 │
$59850 │ 35 │ 40 │ 45 │ 50 │ 55 │ ← bids (resting buys)
$59800 │ 60 │ 70 │ 75 │ 80 │ 85 │
$59750 │ 88 │ 90 │ 92 │ 95 │ 100 │
└─────┴─────┴─────┴─────┴─────┘
Each new orderbook snapshot from your data source becomes one new column. The engine maps cell magnitude to color intensity and renders the result as an adaptive heatmap behind the candles.
Initial load
chart.setHeatmap(matrix, rows, cols, xStart, xStep, yStart, yStep)
Hands a full depth matrix to the engine. Typically you call this once on chart mount with the last N snapshots fetched from your backend's depth cache.
| Argument | Type | Description |
|---|---|---|
matrix | Float32Array | Flattened row-major. Length must equal rows × cols. |
rows | number | Number of price levels (matrix height). |
cols | number | Number of time columns (matrix width). |
xStart | number | Unix-seconds timestamp of column 0. |
xStep | number | Seconds between adjacent columns. |
yStart | number | Price of row 0 (bottom of the matrix). |
yStep | number | Price difference between adjacent rows. |
// 200 price levels × 600 time columns = 120 000 cells (~480 KB).
const rows = 200
const cols = 600
const matrix = new Float32Array(rows * cols)
// Fill matrix[c * rows + r] = sizeAtPrice(r, c) — row-major, column-first.
chart.setHeatmap(
matrix,
rows,
cols,
startTimestamp, // Unix seconds of column 0
1, // 1 column per second
basePrice, // bottom of the price band
10, // $10 between adjacent rows
)
Float32, not Float64. The heatmap matrix is the largest array the engine consumes.
Float32Arrayhalves the bandwidth without losing meaningful precision for depth visualisation.
Real-time append
Every time your data source emits a new orderbook snapshot, append it as a single column.
chart.appendHeatmapColumn(values, colTimestamp, yStart, yStep)
| Argument | Type | Description |
|---|---|---|
values | Float32Array | One value per price row. Length = rows from setHeatmap. |
colTimestamp | number | Unix seconds of this snapshot. |
yStart | number | Price of row 0 in this column. Can differ from the initial load. |
yStep | number | Price difference between adjacent rows. Can differ from the initial load. |
ws.on('orderbook:snapshot', (snap) => {
const col = new Float32Array(rows)
for (let r = 0; r < rows; r++) {
const price = yStart + r * yStep
col[r] = snap.depthAtPrice(price) // your aggregation function
}
chart.appendHeatmapColumn(col, snap.ts, yStart, yStep)
})
The engine handles the column-shift internally — old columns scroll off the left as new ones arrive. No need to call setHeatmap again.
Display controls
chart.setHeatmapRange(min, max)
Sets the static intensity range. Cells with magnitude >= max paint at full color; cells <= min are transparent. Anything in between maps linearly.
| Argument | Type | Description |
|---|---|---|
min | number | Lower clamp. Values at or below this are invisible. |
max | number | Upper clamp. Values at or above this paint at full intensity. |
Setting (0, 0) enables adaptive range — the engine auto-tunes max to the 95th percentile of visible cells, which works well for most pairs.
chart.setHeatmapPrefetchRange(max)
Like setHeatmapRange(0, max) but takes only the upper bound. Defaults the lower bound to 0. Convenience helper for the most common case.
chart.setHeatmapColorScheme(scheme)
| Argument | Value | Description |
|---|---|---|
scheme | 'red-green' | Bids green, asks red. Default. |
scheme | 'blue-yellow' | Bids blue, asks yellow. Higher contrast on dark themes. |
scheme | 'mono' | Single-hue (luminance only). Useful for printing / colorblind users. |
chart.setHeatmapShowProfile(show)
Shows / hides the side profile — a histogram on the right edge of the chart summing the visible column's resting sizes by price. Useful for spotting where liquidity is densest right now.
chart.setHeatmapProfileBrightness(mul)
Multiplier (typically 0.5–2.0) for the side profile's color intensity. Independent of the main heatmap.
chart.setHeatmapProfileBarLength(mul)
Width multiplier (typically 0.5–2.0) for the side-profile bars. Lower = thinner bars, less screen real estate.
Walls (large resting orders)
The engine can highlight walls — rows whose resting size dwarfs the surrounding rows. Walls are the orderbook-equivalent of a support/resistance level: large limit orders that need a lot of flow to chew through.
chart.setHeatmapWalls(flat)
| Argument | Type | Description |
|---|---|---|
flat | Float32Array | Flattened [timestamp, price, size] triplets. Length = walls × 3. |
ws.on('orderbook:walls', (walls) => {
const flat = new Float32Array(walls.length * 3)
for (let i = 0; i < walls.length; i++) {
flat[i * 3 + 0] = walls[i].timestamp
flat[i * 3 + 1] = walls[i].price
flat[i * 3 + 2] = walls[i].size
}
chart.setHeatmapWalls(flat)
})
Wall detection (which orders count as "walls") happens server-side — the engine just renders the list you give it. The threshold typically lives in your backend's orderbook aggregator.
Detection helpers
The standalone iceberg detector ships in the same package and runs entirely client-side. It consumes the trade-stream + orderbook deltas you already have, and emits a stream of suspected iceberg fills.
import { createIcebergDetector } from '@mrd/chart-engine'
const det = createIcebergDetector({
/* tuning knobs */
})
det.onIceberg((ev) => {
// mark the chart at the detected price
chart.addMarker(ev.ts, ev.price, ev.isBid)
})
See Iceberg Detector (this page) for tuning details. Reference implementations of the data flow are in the React/Vue guides.
Common pitfalls
Heatmap is empty but setHeatmap returned no error.
The matrix is in column-major order in your code but the engine expects row-major. The constructor signature is matrix[col * rows + row]. If you produced it as matrix[row * cols + col], the data is transposed and individual columns will draw as horizontal stripes spanning the whole time axis.
Heatmap is too dim / too bright.
Adaptive range (setHeatmapRange(0, 0)) is the right default for 90 % of pairs. If you have an unusual depth profile (very thin orderbook, e.g. low-cap altcoin), set an explicit max close to your typical top-of-book size.
Heatmap lags the candle chart by 1–2 seconds.
You're aggregating depth snapshots in JS before calling appendHeatmapColumn. The engine handles the column shift in microseconds — push every snapshot through as it arrives. Don't debounce on the way in; the chart already coalesces per-frame.
Memory grows on a long session. The internal heatmap buffer is a ring — old columns are reclaimed automatically. If you see growth, you have a leak elsewhere (event-listener fan-out, your own snapshot history array, etc.). Profile with the browser DevTools heap snapshot; the engine module's allocation should plateau.
Next
- Footprint Chart — bid×ask cells inline on each candle, the per-trade view of order flow.
- Indicators — overlay VRVP / CVD / TPO on top of the heatmap layer.
- Drawing — programmatically place markers, trendlines, and horizontals.