Data Loading
Hand OHLCV candles to the engine — full reload, append on tick, update the last bar, prepend on infinite scroll. The full kline data API in one page.
The engine takes OHLCV as six parallel arrays. This shape lets the renderer fan out a 100 000-bar load in a few milliseconds without per-bar object overhead. Every kline method below uses the same shape.
Time units are seconds. Always. Convert milliseconds before passing in.
Initial load
chart.setKlines(timestamps, open, high, low, close, volume)
Replaces the entire kline dataset. Resets the viewport to show the last ~120 candles (typical "chart-just-opened" look).
| Argument | Type | Length | Notes |
|---|---|---|---|
timestamps | Float64Array | number[] | N | Unix seconds, strictly increasing |
open | Float64Array | number[] | N | |
high | Float64Array | number[] | N | |
low | Float64Array | number[] | N | |
close | Float64Array | number[] | N | |
volume | Float64Array | number[] | N | Base-asset volume |
chart.setKlines(timestamps, opens, highs, lows, closes, volumes)
Prefer typed arrays. Passing Float64Array skips a copy step — the engine reads the buffer directly. Regular number[] works but copies once on entry.
chart.setKlinesPreserveViewport(timestamps, open, high, low, close, volume)
Same as setKlines, but keeps the current viewport (pan offset + zoom). Use when reloading the same symbol+timeframe (e.g. on a websocket reconnect) — preserving the viewport avoids "the chart jumped" feeling.
Real-time updates
The two most-called methods in any live chart. They run in the microsecond range — call them once per tick.
chart.updateLastKline(ts, o, h, l, c, v)
Updates the last candle in place. Use on every tick while the current bar is still forming.
ws.on('trade', (t) => {
const lastTs = currentBarStart // Unix seconds, start of current bar
const lastOpen = currentBarOpen
const newClose = t.price
const newHigh = Math.max(currentBarHigh, t.price)
const newLow = Math.min(currentBarLow, t.price)
const newVolume = currentBarVolume + t.qty
chart.updateLastKline(lastTs, lastOpen, newHigh, newLow, newClose, newVolume)
})
chart.appendKline(ts, o, h, l, c, v)
Appends a new candle. Call when a bar closes and a new one starts.
function onBarClose(closedBar, newBar) {
chart.appendKline(
newBar.ts, newBar.open, newBar.open, newBar.open, newBar.open, 0,
)
}
After appendKline, the new bar becomes the "last" — subsequent updateLastKline calls update it.
Standard real-time pattern
function handleTrade(trade) {
if (trade.ts >= currentBarEnd) {
// Bar boundary crossed — finalise current, open new.
chart.updateLastKline(/* final OHLCV of the closing bar */)
chart.appendKline(/* opening tick of the new bar */)
currentBarEnd += intervalSeconds
} else {
chart.updateLastKline(/* updated OHLCV of the current bar */)
}
}
The engine handles the "candle is forming" visual cue (dimmer body, no border) for the last bar automatically.
Infinite scroll (load older bars)
When the user pans left past the oldest loaded bar, you'll want to fetch and prepend more history.
chart.prependKlines(timestamps, open, high, low, close, volume)
Prepends a batch of older candles. Adjusts the internal index so the viewport doesn't jump.
chart.onPanEnd(async () => {
const visible = chart.getKlineVisible()
// Trigger a fetch when the user pans within N bars of the oldest one.
if (/* user is near the left edge */) {
const older = await fetch(`/api/klines?to=${oldestTs}&limit=1000`)
const arrs = toFloat64Arrays(older)
chart.prependKlines(arrs.t, arrs.o, arrs.h, arrs.l, arrs.c, arrs.v)
}
})
A complete infinite-scroll recipe (with rate-limiting, dedup, and viewport-preservation tuning) lives in the Infinite Scroll guide.
Removing the last bar
chart.popLastKline()
Removes the last candle. Returns true on success, false if the dataset was empty.
Useful for replay flows where the user scrubs backwards, or for cleanly cancelling a partial bar before reloading from the exchange API.
Reading what the engine has
chart.getLastClose()
Returns the close price of the last candle, or null if no data is loaded. Cheaper than reading from your own array because the engine already has the value in memory.
chart.getKlineVisible()
Returns whether the candle layer is currently shown. Toggle with setKlineVisible(true | false) — useful for indicator-only views (e.g. an RSI-only research pane).
Common pitfalls
Timestamps are in milliseconds, not seconds. Every public exchange API returns milliseconds. Divide by 1000 once at parse time. Mixing units leads to candles spaced 1000× wider than expected.
Timestamps not strictly increasing.
Duplicate or out-of-order timestamps cause undefined rendering. If your data source can emit duplicates (some exchange websockets do), dedupe in your handler before calling appendKline.
Floats with too many decimals on shitcoin prices.
The engine handles arbitrary precision — but your fetch parser shouldn't. parseFloat("0.00000000023") works; coercing through int will not.
Calling setKlines on every tick.setKlines is the full reload method. It resets the viewport. For tick updates, use updateLastKline / appendKline.
Forgetting to call start() after the first load.
The engine has data but is not painting. Symptom: chart looks blank or stuck on its mount frame. Always call start() after the first setKlines.
Next
- Orderbook Heatmap — hand depth snapshots and append new columns on every snapshot tick.
- Footprint Chart — switch chart type and feed trade-level data for bid×ask cells.
- Indicators — turn RSI, MACD, VRVP, and 15+ others on / off.
- Performance & Smoothness — the do-this / don't-do-this list for keeping a tick-heavy chart at 60 fps.
- Common Pitfalls — symptom → one-line fix catalogue for the bugs we see most often.