Docs·Charting Library·Core API

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).
JavaScript
         t0    t1    t2    t3    t4
       ┌─────┬─────┬─────┬─────┬─────┐
$60000 │   00-8-12-15 │  ← asks (top of book, sells)
$59950 │   0-22-30-28-25$59900 │ -45-50-55-60-70$59850 │  3540455055 │  ← bids (resting buys)
$59800 │  6070758085$59750 │  88909295100       └─────┴─────┴─────┴─────┴─────┘

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.

ArgumentTypeDescription
matrixFloat32ArrayFlattened row-major. Length must equal rows × cols.
rowsnumberNumber of price levels (matrix height).
colsnumberNumber of time columns (matrix width).
xStartnumberUnix-seconds timestamp of column 0.
xStepnumberSeconds between adjacent columns.
yStartnumberPrice of row 0 (bottom of the matrix).
yStepnumberPrice difference between adjacent rows.
JavaScript
// 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. Float32Array halves 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)

ArgumentTypeDescription
valuesFloat32ArrayOne value per price row. Length = rows from setHeatmap.
colTimestampnumberUnix seconds of this snapshot.
yStartnumberPrice of row 0 in this column. Can differ from the initial load.
yStepnumberPrice difference between adjacent rows. Can differ from the initial load.
JavaScript
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.

ArgumentTypeDescription
minnumberLower clamp. Values at or below this are invisible.
maxnumberUpper 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)

ArgumentValueDescription
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)

ArgumentTypeDescription
flatFloat32ArrayFlattened [timestamp, price, size] triplets. Length = walls × 3.
JavaScript
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.

JavaScript
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.