Chart Settings Script
Each chart has a settings script (gear panel) with the same globals as a code indicator, but a different execution model: it runs once as a static description of the chart, not on every tick. It re-runs only when the script is saved, the symbol or interval changes, or historical data first loads.
For a worked build of a renderer from scratch, see Create a Custom Footprint Chart.
The values on this page - use, chart.condenseLevel, chart.numberDisplay, chart.columnWidth, and the chart.candles / chart.frame renderers - are honored only here; indicators may reference them but they are ignored there.
What do you want to change?
Each row is a thing people ask for and the one value that does it. Several of these look similar and are not: the space a candle occupies, the space between candles, and the candle drawing itself are three different numbers.
| You want | Change this |
|---|---|
| Zoom in / bigger candles | raise chart.columnWidth |
| Zoom out / fit more candles | lower chart.columnWidth |
| Less space between candles | check the drawing FILLS the column first - unsized draw.text is capped at ~0.7x row height and the empty column it leaves dwarfs any gap. Give text a size + scale: "x". Only then lower chart.columnGap |
| Numbers too small / too much empty column | draw.text size with scale: "x" ties them to the column instead of the row height |
| Thinner or wider candle body | the body width in your chart.candles renderer - size it off candle.widthPx to work at any zoom |
| Merge price rows together | raise chart.condenseLevel |
| Show delta x volume instead of sell x buy | chart.numberDisplay = "deltaVol" |
| Run an indicator on this chart | use("Name") |
| Change colors, numbers, bars - anything drawn | the chart.candles renderer; nothing else draws the footprint |
use()
Imports an indicator from your library so it runs on this chart. Resolves by indicator id first, then exact name (library order breaks name ties). A name that matches nothing shows an error toast.
- Imports can be conditional - the import set is re-evaluated live, so an indicator can be enabled only when a condition holds.
- Enabled indicators run with their own per-tick execution model, exactly as if toggled on in Chart Settings -> Indicators.
use(name)use("VWAP")
use("Big Trades")chart.condenseLevel / chart.numberDisplay
Chart display config. These shape the DATA the renderer receives - row merging, and which pair of numbers is meaningful - not how it looks. The look is entirely the chart.candles renderer.
condenseLevel- how many price rows merge into one displayed row (the Condense Prices setting).numberDisplay-"sellBuy"or"deltaVol", the value pair shown at each price level.- These two and
chart.columnWidth/chart.columnGapare read back after every run of your script, and a line you leave out falls back to its default. Deleting one reverts that setting rather than keeping whatever was set before.
chart.condenseLevel = 2
chart.numberDisplay = "deltaVol"chart.columnWidth
Width of one candle column in pixels - equivalently, the zoom: wider candles mean fewer on screen. This is also the space draw.rectangle's from / width and draw.text's at fractions divide up, so a layout that splits the column into regions - a profile beside a delta histogram, say - sets the width it was designed against.
0(the default) leaves the width to the zoom, as before.- Applied when the settings script runs - on save, preset pick, or a symbol or interval change. Zooming afterwards behaves normally. A width wider than the usual 160px zoom-in limit raises that limit; a narrower one is only a starting point and never restricts zooming in.
- Clamped to 400px.
chart.columnWidth = 118 // px per candle column
// ...so these fractions are 60px and 43px wide
const PROFILE_W = 0.55
const DELTA_W = 0.39chart.columnGap
Pixels of empty space between neighbouring candle columns. The gap is taken off the drawable span and split evenly either side, so less padding means wider rows for the same chart.columnWidth.
0(the default) keeps the automatic gap. That gap grows with the column AND with the chart's own size scale, so the same column keeps a different share of itself on different screens - on a 100px column it is about 17px in a small panel and about 30px on a large one.- Declaring a value pins the gap in pixels. A layout that fixes
chart.columnWidthshould fix this too, or its drawable span changes from screen to screen. - Capped at 60px, and never allowed to consume the column: on narrow columns a large gap thins the drawable span rather than erasing it.
chart.columnWidth = 118
chart.columnGap = 8 // pinned, not the scaling defaultchart.candles()
Registers the per-candle renderer. The chart invokes it for each visible candle on each frame with the aggregated candle view - no history loops, no per-tick cost. This is the ONLY footprint renderer: whatever the callback draws is what the chart shows, candle included.
draw.rectangleplaces rows by price and fractions of the candle column;draw.textanchors at the same column fraction.candle.widthPxis that column in pixels, so a fraction of6 / candle.widthPxis a 6px mark at any zoom. Use it for anything that should keep a fixed thickness - a wick, a body - instead of growing with the column.showRowText/showSummaryare the chart's readability gates - honor them or numbers turn to mush when zoomed out.sizeindraw.textis a base size at reference zoom;scalepicks the axis that stretches it ("y"default,"x","both", or"fixed"px).- Omitting
sizeuses the chart's automatic sizing, which is capped at about 0.7x the row height - on short rows the text shrinks and leaves most of the column empty, which reads as a large gap between candles. Give text a size withscale: "x"to tie it to the column instead.
chart.candles((candle, draw) => {
for (const price of Object.keys(candle.rows)) {
const { buys, sells } = candle.rows[price]
draw.rectangle({
price: Number(price),
width: (buys + sells) / candle.maxLevelVolume,
color: buys > sells ? "#3b82f6" : "#ef4444",
})
if (candle.showRowText) {
draw.text({ price: Number(price), text: `${sells} x ${buys}` })
}
}
})chart.frame()
Registers a whole-frame renderer, invoked on each frame with the full visible view - all visible candles, the price range, and the time range. Use it for overlays that span candles (session levels, value areas) computed against the visible window.
draw.line/draw.hline/draw.rectangle/draw.textdraw in real prices and ms timestamps.
chart.frame((view, draw) => {
let poc = 0
let pocVolume = 0
for (const candle of view.candles) {
if (candle.maxLevelVolume > pocVolume) {
pocVolume = candle.maxLevelVolume
poc = candle.poc
}
}
draw.hline({ price: poc, color: "#eab308", style: "dashed" })
})chart.frame() on the heatmap
The heatmap has its own settings script (its header's gear) and draws through the same chart.frame renderer - its draw calls were already in absolute prices and ms, which is exactly the heatmap's coordinate system. What differs is the view: the heatmap has no candles, so view.candles is empty and view.trades carries its trade series instead (on the footprint chart it is the other way round, so a renderer written for one draws nothing on the other rather than breaking). Depth and the bid/ask lines stay native; the script draws on top of them.
view.trades- every trade in the visible time range, oldest first and unfiltered. Filtering it is the trade-size control: the seeded "Big Trades" indicator, which draws the dots, skips trades under itsMIN_SIZE.view.minTradeSizeis the instrument's own floor (ES 20, most instruments 1) - the default the dots filter at, so an indicator that has to work on every instrument does not have to pick a number.view.msPerPxis the zoom. Scale radii by it (min(1, baseline / view.msPerPx)) so dots stay separate when zoomed out.draw.circle/draw.rectangle/draw.line/draw.hline/draw.text- real prices and ms timestamps, clipped to the plot.onPresson a circle makes it clickable and reveals your text.
chart.frame((view, draw) => {
const zoom = Math.min(1, 35 / view.msPerPx)
for (const trade of view.trades) {
if (trade.size < view.minTradeSize) continue
const ratio = (Math.min(trade.size, 50) - 1) / 49
const radius = (3 + 17 * ratio) * zoom
if (radius < 1) continue
draw.circle({
time: trade.time,
price: trade.price,
radius,
color: trade.side === 'buy' ? 'rgba(59, 130, 246, 0.88)' : 'rgba(239, 68, 68, 0.88)',
onPress: { text: trade.side + ' ' + trade.size },
})
}
})