Heatmaps
The heatmap drawing, columns of colored cells over time and price, painted as one image, with palettes, scales, the automatic reference and budgets.
A heatmap shows a value at every price and time, as color: the brighter the cell, the bigger the value. ChartDeck's Liquidation Heatmap and Order Book Heatmap are heatmaps, and a script can draw its own with the heatmap drawing.
A heatmap is one drawing that holds many columns. Each column covers a span of time (one bar by default) and a stack of equal cells from a bottom price up, one value per cell. The chart paints the whole heatmap as a single image per pane, so a heatmap of a hundred thousand cells costs about as much to draw as one picture of the chart: it is not made of boxes.
| Create | Add to it | Change it |
|---|---|---|
heatmap.new | hm.column, hm.add, hm.cell | hm.clear, hm.set_range, hm.set_opacity, hm.set_intensity, hm.delete |
#A first heatmap
Create the heatmap once, with state, and add a column on every bar. This one spreads each bar's volume over four rows between its low and its high, more towards the close:
//@chartscript 1
indicator("Volume heat", { overlay: true, behind_chart: true })
state hm = heatmap.new({ palette: heatmap.palette_viridis })
const step = (high - low) / 4
if (step > 0) {
const nearClose = math.floor((close - low) / step)
const cells = array.new_float(4, volume * 0.1)
cells.set(math.min(3, nearClose), volume)
hm.column(time, low, step, cells)
}hm.column(x, bottom, step, values) adds one column at x (the bar's time): cell k covers the prices from bottom + k × step to bottom + (k + 1) × step and gets values[k]. A cell whose value is na, 0 or negative stays empty and is not stored. behind_chart: true puts the heatmap (and everything else the script draws) behind the candles, where a heatmap belongs.
Do not create a heatmap without state: a const hm = heatmap.new() makes a new heatmap on every bar, and a script keeps only its 8 newest.
#Where a column goes
xloc decides what x and width mean:
xloc.bar_time(the default):xis a time,widtha duration in milliseconds.width = namakes the column one bar wide. A column that spans an hour on a 15-minute chart covers four bars.xloc.bar_index:xis a bar index andwidtha number of bars (na= 1).
hm.cell(x, low, high, value) adds a single cell from low to high, for scattered values that do not form columns.
#Colors and scale
The value of a cell becomes a color in three steps, the same ones the built-in heatmaps use:
- The range.
minandmaxare the values at the bottom and the top of the palette. Withmax = na(the default) the top is set automatically: thereferencepercentile (92 by default) of the cells in view, recomputed as you scroll and zoom, so a few giant cells cannot wash the rest out. - The scale.
heatmap.scale_linear(default),scale_sqrt(weak cells stand out more) orscale_log(values over several orders of magnitude).intensitymultiplies the values first: 2 is twice as bright. - The palette curve. Cells under
floor(a share of the range, 0.05 by default) stay clear; above it the palette index rises as the remaining share to the powergamma(0.9 by default).
| Palette | Colors |
|---|---|
heatmap.palette_heat | the Liquidation Heatmap's ramp; follows the theme |
heatmap.palette_bid, heatmap.palette_ask | the Order Book Heatmap's bid and ask ramps |
heatmap.palette_viridis, heatmap.palette_magma | perceptual ramps that read well in both themes |
heatmap.palette_mono | from clear to the heatmap's color |
//@chartscript 1
indicator("Volume heat, square root", { overlay: true, behind_chart: true })
state hm = heatmap.new({ palette: heatmap.palette_mono, color: color.accent, scale: heatmap.scale_sqrt, floor: 0.1, reference: 98, opacity: 80 })
hm.cell(time, low, high, volume)#ChartDeck's own heat data
Two built-in data sources come as ready-made columns, heatmap.column values with the fields time, span, bottom, step, values and max. Add one with hm.add(col): it goes at its own time and span.
liq.heat(): the liquidation model's hourly field over this bar (Liquidation data).book.heat(side): the recorded order book of this bar (Order-book data).
//@chartscript 1
indicator("My liquidation heat", { overlay: true, behind_chart: true })
state hm = heatmap.new({ palette: heatmap.palette_heat, floor: 0.07, gamma: 1.1, reference: 99.5 })
state lastT = 0
const col = liq.heat()
// on a chart below 1 hour, every bar of the hour gets the same column: add it once
if (!na(col) && col.time != lastT) {
hm.add(col)
lastT = col.time
}The official Liquidation heatmap and Order book heatmap scripts (View source in those studies' settings) are complete examples, including their legend keys.
#Budgets
| Limit | Value | When you go over |
|---|---|---|
| Heatmaps per script | 8 | the oldest is removed, as with other drawings |
| Non-empty cells, all heatmaps of a script together | 500,000 | the run stops with CS5120 |
| Rows in one column | 2,000 | the run stops with CS5121 |
Each stored cell also counts towards the script's memory (CS5403). To stay well inside the budget, add a column only when it is new, choose a step that gives tens or hundreds of rows rather than thousands, and clear a heatmap you rebuild (hm.clear()).
#How it is drawn
Only the columns in view are painted, into one image the size of the pane, which is kept until the heatmap, the view or the theme changes. On a live bar only the columns that changed travel from the script to the chart. A heatmap is drawn under the script's other drawings (lines, boxes, labels) and, with behind_chart: true, under the candles.