Core concepts
A quick mental model of how the engine fits together. You can skip straight to Chart Types if you just want to draw something.
The bar
Every price series is a list of bars. Time is UTC seconds (an integer), never a
Date:
interface Bar {
time: number; // UTC seconds
open: number;
high: number;
low: number;
close: number;
volume?: number;
}Line/area series use the same shape and read close. Gaps in a series are expressed with a
whitespace item ({ time } with no OHLC) - the renderer breaks the line there instead
of interpolating across it.
Gapless time axis
The x position of a bar is logicalIndex × barSpacing, not a function of its timestamp.
Each bar gets the next integer index regardless of the real-time gap before it, so
weekends, holidays, and overnight session breaks collapse automatically - there is no
empty space to draw. This is why panning and zooming feel continuous on intraday data.
Because x is a logical index, all panes share one time scale and stay aligned bar-for-bar.
Price scale
Each pane has its own price scale: linear or logarithmic, auto-scaling to the visible range by default, with optional inversion and manual override. Dragging the price axis rescales that pane; double-click restores autoscale. See Scales & Panes.
Panes
A chart has one or more stacked panes sharing the time axis. The main pane holds price;
add paneIndex: 1, 2, … to put volume or an oscillator (RSI) below. Panes are created on
demand the first time you reference them.
Series, primitives, and the registry
- Series are registry-driven.
addSeries('candlestick' | 'line' | 'area' | ...)looks the type up in the chart-type registry; you can register your own. - Primitives are everything else drawn on the chart - markers, event badges, price
lines, profiles, and the entire trading layer. They implement a small
IPrimitiveinterface (draw, optionalhitTest/autoscaleInfo/ lifecycle) and attach withchart.addPrimitive(primitive, paneIndex).
The render loop
Drawing is centralized in a single requestAnimationFrame loop driven by an invalidation
mask. Cursor moves only repaint the crosshair layer; data changes repaint the series; a
resize repaints everything. This keeps interaction at 60 fps without redrawing the world on
every event.
Time at the edge
Internally everything is UTC seconds. The OpenAlgo adapters convert at the boundary - IST date/time strings and epoch milliseconds become UTC seconds on the way in, and axis labels are formatted back to IST on the way out. Your application code never juggles timezones.