Introduction
LLM-first financial charting library for modern web applications.
Try It LiveWhat is Axon Charts?
Axon Charts is a canvas-based financial charting library built from the ground up for AI agents and developers. It provides:
- LLM-First Design: Every feature is accessible via simple, predictable APIs that AI agents can discover and use
- Lightning Fast: Handles 5000+ candles at 60fps with optimized draw calls
- 164 Configuration Options: Customize every aspect of appearance and behavior
- TypeScript Support: Full type definitions for excellent developer experience
- Production Ready: Volume sub-panes, streaming support, market headers, and more
Key Features
| Feature | Description |
|---|---|
| Real-time Updates | WebSocket support with updateLastBar() and updateLastBarFast() methods |
| Volume Sub-pane | Display volume histogram below main chart with auto precision detection |
| Crosshair Modes | Normal, magnet (snap-to-bar), and disabled modes |
| Context Export | getContext() returns structured JSON for LLM analysis |
| 164 Options | Layout, grid, scales, colors, behavior, and more |
Browser Support
Works on all modern browsers that support ES6 and Canvas API:
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
Installation
NPM
npm install axon-charts
Yarn
yarn add axon-charts
CDN
<script src="https://cdn.jsdelivr.net/npm/axon-charts/dist/chart.js"></script>
<script src="https://unpkg.com/axon-charts/dist/chart.js"></script>
Bundled Size
- Minified + IIFE: ~184 KB / 180 KB ESM
- Gzipped: ~46.2 KB
Quick Start
Try It LiveBasic Chart
// Import the library
import { createChart } from 'axon-charts';
// Create a chart
const chart = createChart('#container', {
layout: {
background: '#1a1a1a',
textColor: '#aaa',
},
grid: {
show: true,
vertLines: { color: '#2a2a2a' },
horzLines: { color: '#2a2a2a' },
},
});
// Load data
chart.setData([
{ time: 1609459200000, open: 100, high: 105, low: 98, close: 103 },
{ time: 1609459260000, open: 103, high: 107, low: 102, close: 105 },
// ... more data
]);
Real-time Updates
// Update the latest candle
chart.updateLastBar({
time: Date.now(),
open: 105,
high: 108,
low: 104,
close: 107,
});
Get Chart State
// Export chart context for LLM analysis
const ctx = chart.getContext();
console.log(ctx.visibleBars);
console.log(ctx.viewport);
console.log(ctx.priceRange);
Settings Overview
164 configuration options across 25 categories
Try It LiveHow to Set Options
// Set options during initialization
const chart = createChart('#container', {
layout: { background: '#1a1a1a' },
grid: { show: true },
});
// Update options dynamically
chart.setOptions({
volume: { show: true, heightPercent: 0.25 },
crosshair: { mode: 'magnet' },
});
Categories
| Category | Options | Description |
|---|---|---|
| Layout | 10 | Dimensions, colors, fonts, padding |
| Grid | 7 | Background grid lines |
| Series | 2 | Candle colors |
| Price Scale | 15 | Y-axis mode, margins, format, current price, and reverse |
| Time Scale | 11 | X-axis visibility, spacing, format, and timezone |
| Crosshair | 9 | Cursor overlay and tooltip |
| Menu | 2 | Right-click context menu |
| Behavior | 6 | Drag, zoom, pan, and auto-scroll flags |
| Data | 2 | Data management and limits |
| Volume | 6 | Volume histogram sub-pane |
| Attribution | 1 | Axon.Watch logo at chart bottom-left |
| Market | 6 | Trading pair and exchange info |
| Context (LLM) | 3 | LLM data exposure, AI agent discovery, chart ID |
| Watermark | 6 | Semi-transparent overlay |
Layout
Chart dimensions, colors, fonts, and padding — 10 options.
Try It LiveWidth & Height
Set to 'auto' (reads container size) or a fixed pixel value. Auto is recommended for responsive layouts.
layout: {
width: 'auto', // reads container.clientWidth
height: 'auto', // reads container.clientHeight
background: '#1a1a1a',
textColor: '#ffffff',
fontSize: 12,
fontFamily: 'system-ui'
}
Padding
Controls chart margins. Maps directly to internal state variables.
layout: {
padding: {
top: 40, // → state.topMargin
right: 60, // → state.axisWidth
bottom: 35, // → state.bottomMargin
left: 10 // reserved
}
}
Dynamic Updates
chart.setOptions({ layout: { background: '#000000', textColor: '#ffffff' } });
chart.setOptions({ layout: { padding: { top: 20, right: 40 } } });
Grid
Background grid lines — 7 options.
Try It LiveMaster Toggle
grid.show acts as a master switch. When false, all grid lines are hidden regardless of sub-settings.
Vertical & Horizontal Lines
Each axis has independent show/color/width controls:
grid: {
show: true,
vertLines: { show: true, color: '#2a2a2a', width: 1 },
horzLines: { show: true, color: '#2a2a2a', width: 1 }
}
chart.setOptions({ grid: { show: false } }); // hide all
chart.setOptions({ grid: { vertLines: { color: '#333' } } }); // recolor
Series
Candle and line colors — 2 options.
Try It LiveUp / Down Colors
Controls the fill color of bullish and bearish candles. Also affects the current price line color.
series: {
upColor: '#10B981', // green candle body
downColor: '#E11D48' // red candle body
}
chart.setOptions({ series: { upColor: '#00ff00', downColor: '#ff0000' } });
Price Scale
Y-axis behavior and formatting — 15 options.
Try It LiveScale Mode
Linear is standard for most charts. Logarithmic is useful for assets with large price ranges.
priceScale: {
mode: 'linear', // 'linear' | 'logarithmic' | 'percentage'
scaleMargins: {
top: 0.1, // 10% padding above max price
bottom: 0.1 // 10% padding below min price
}
}
Percentage Mode
Normalizes all prices relative to the close price of the leftmost visible bar. The reference updates dynamically as you scroll or zoom. Display-only — internal data remains in original prices. Switch back to linear mode to restore numeric axis labels.
Axis labels, current price line, and crosshair price label use smart adaptive formatting:
| Change | Display | Range |
|---|---|---|
| +2.45 | +2.45% | Small changes (2 decimal places) |
| +245.8 | +245.8% | Medium changes (1 decimal place) |
| +8,450 | +8,450% | Large changes (thousands separator, 0 decimals) |
| +80,500 | +805.0× | Extreme moves ≥10,000% (× multiplier) |
× multiplier activates for changes of 100× or more (e.g. Bitcoin from $100 to $80k+). Formatting uses Intl.NumberFormat for proper locale-aware thousands separators.
Price Format
priceScale: {
priceFormat: {
type: 'price', // 'price' | 'volume' | 'percent'
precision: 2, // decimal places
minMove: 0.01 // tick increment (derives precision if not set)
}
}
Current Price Line
Dashed horizontal line at the latest close price with a label on the Y-axis. Colors cascade: if not set explicitly, they follow series colors and layout.textColor.
priceScale: {
currentPrice: {
show: true, // master toggle for line + label
showLine: true, // show/hide only the dashed line
showCountdown: false, // candle-close countdown timer (default: false)
countdownColor: 'rgba(255, 255, 255, 0.8)',
upColor: null, // bull line color — falls back to series.upColor
downColor: null, // bear line color — falls back to series.downColor
lineStyle: 'dashed', // 'dashed' | 'solid'
textColor: null // label text — falls back to layout.textColor
}
}
- upColor / downColor — when
null, follows candle colors. Set explicitly to decouple from candles. - textColor — when
null, followslayout.textColor. Essential for light backgrounds where white text would be invisible. - showLine — hides only the dashed horizontal line while keeping the price label visible.
- countdownColor — when
null, falls back totextColor, thenlayout.textColor. - lineStyle —
dashed(default) orsolid.
The label background uses the line color at 80% opacity. Line style supports both dashed (default) and solid.
chart.setOptions({ priceScale: { currentPrice: { textColor: '#000000', lineStyle: 'solid' } } });
Reverse
Inverts the price axis so high prices appear at the bottom:
chart.priceScale().setReverse(true);
chart.execute({ type: 'setReverse', reverse: true });
Time Scale
X-axis behavior, timezone, and formatting — 11 options.
Try It LiveVisibility & Format
timeScale: {
visible: true,
timeVisible: true, // show time on axis (false = date only)
secondsVisible: false, // HH:mm:ss format
showFullDate: true, // crosshair: "Fri 03 Jan'26 17:55"
showDayOfWeek: true, // show "Mon", "Tue" prefix in crosshair
dateFormat: 'MMM dd, yyyy' // tokens: yyyy, yy, MMM, MM, dd
}
Timezone
Controls how timestamps are displayed on the axis and crosshair. By default, uses the browser's local timezone.
timeScale: {
timezone: undefined // browser local (default)
// timezone: 'UTC' // universal time
// timezone: 'America/New_York' // any IANA name
}
Uses Intl.DateTimeFormat — handles daylight saving automatically. The crosshair tooltip and all axis labels respect the configured timezone.
Bar Spacing
timeScale: {
barSpacing: 11, // base bar width in pixels
minBarSpacing: 4, // zoom-in boundary
maxBarSpacing: 1000 // zoom-out boundary
}
chart.timeScale().setBarSpacing(20);
Right Offset
Gap in pixels between the last bar and the right edge of the chart area:
timeScale: {
rightOffset: 80
}
chart.setOptions({ timeScale: { rightOffset: 40 } });
chart.setOptions({ timeScale: { timezone: 'UTC', dateFormat: 'yyyy-MM-dd' } }); // ISO-style
Crosshair
Cursor overlay and tooltip — 9 options.
Try It LiveMode
'magnet'(default) — snaps to nearest bar'normal'— free-floating cursor'none'— crosshair hidden
chart.crosshairAPI().setMode('magnet');
chart.execute({ type: 'setCrosshair', mode: 'none' });
Labels & Tooltip
crosshair: {
showLabels: true, // axis highlight labels
showTooltip: true // OHLC tooltip near cursor
}
Line Styling
crosshair: {
vertLine: { color: '#555555', width: 1, style: 'dashed' },
horzLine: { color: '#555555', width: 1, style: 'dashed' }
}
chart.crosshairAPI().setVerticalLine({ color: '#ff0000', width: 2 });
Behavior
Interaction controls — 6 options.
Try It LiveAll behavior flags are boolean toggles that affect mouse/touch interaction. Changes take effect immediately on the next user action.
behavior: {
dragToZoom: true, // time-axis drag-zoom
scrollToZoom: true, // mouse wheel zoom
pinchToZoom: true, // touch pinch-zoom
panOnMouseDrag: true, // chart-area panning
dragPriceScale: true, // Y-axis drag zoom
autoScroll: true // viewport follows latest bar
}
chart.setOptions({ behavior: { panOnMouseDrag: false } }); // disable pan
Data
Data management and limits — 2 options.
Try It LiveControls how the chart manages its internal data buffer — useful for long-running streaming sessions.
data: {
maxBars: 5000, // max candles kept in memory (1-1,000,000)
autoCleanup: true // auto-trim old bars when limit is exceeded
}
chart.setOptions({ data: { maxBars: 10000 } });
maxBars
Maximum number of bars stored in memory. Older bars are trimmed when this limit is exceeded. Default is 5000. Higher values consume more memory but allow further panning into history.
autoCleanup
When true (default), the chart automatically removes bars from the beginning of the dataset when maxBars is exceeded. Set to false if you manage trimming manually.
Attribution
Axon.Watch branding (1 option)
The attribution logo displays a stylized A in a black circle at the bottom-left of the chart area. On hover, it expands to reveal xon.Watch and links to axon.watch.
attribution: {
show: true, // Show/hide the attribution logo
}
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
show | boolean | true | Show/hide the attribution logo |
Volume
Volume histogram sub-pane — 6 options.
Try It LiveVolume Sub-Pane
The volume histogram displays below the main chart with an independent Y-axis, draggable separator, and zoom/pan support.
volume: {
show: false, // toggle visibility
upColor: '#10B981', // bullish volume color
downColor: '#E11D48', // bearish volume color
heightPercent: 0.2, // 20% of total chart height (0.05-0.5)
precision: null, // decimal places (null = auto-detect)
minMove: null // min increment (derives precision)
}
Features
- Independent Y-axis with K/M suffix formatting
- Draggable separator (6px hit zone, ns-resize cursor)
- Scroll wheel and drag zoom (1x-10x)
- Double-click reset on sub-pane axis
- Crosshair spans entire chart including sub-pane
- Current value dashed line with axis label
- Volume tooltip at top-left of sub-pane
- Bars colored per candle direction (up/down)
- Precision auto-detected from data (or explicit via options)
chart.setOptions({ volume: { show: true, heightPercent: 0.25 } });
Market Header
Trading pair and exchange info — 6 options.
Try It LiveDisplays an in-chart label at the top-left showing the market pair, timeframe, and source.
market: {
show: false, // toggle visibility
baseAsset: 'BTC', // base asset name
quoteAsset: 'USDT', // quote asset name
timeframe: '1m', // chart timeframe
source: '', // exchange / source name
fontSize: 15 // header font size in px (default: 15)
}
Format: BTC/USDT | 1m | Binance
The header text is rendered at the configured fontSize (default 20px). The tooltip below it is offset by fontSize + 4 pixels so they don't overlap.
chart.setOptions({ market: { fontSize: 16 } }); // smaller header
chart.setOptions({ market: { fontSize: 24 } }); // larger header
chart.setOptions({ market: { baseAsset: 'ETH', quoteAsset: 'USDT', source: 'Hyperliquid', show: true } });
Context (LLM Exposure)
Control what AI agents can discover and read — 3 options.
Try It LiveThese options control the AI agent integration layer. Three settings are available:
exposeData
Controls what getContext() returns. When false (default), only viewport metadata is returned — visible bars, latest bar, and sub-pane data are excluded. When true, full trading data is available to agents.
context: {
exposeData: false // metadata only — safe default
// exposeData: true // full OHLCV data exposure
}
Runtime-toggleable via chart.setOptions({ context: { exposeData: true } }).
discoverable
Controls whether the chart registers in the global window.__AXON_CHARTS__ registry. When true (default), AI agents with the Axon Charts skill can discover and interact with the chart. Set to false for stealth mode.
context: {
discoverable: true // visible to AI agents — default
// discoverable: false // stealth mode
}
id
Optional identifier for the chart in the AI agent registry. Used to target specific charts in multi-chart environments.
- If set explicitly: used as-is (e.g.
'Bitcoin-1m') - If not set: auto-generated opaque token (e.g.
ax-uhq8fz) — no pair inference, no stale associations
Opaque IDs prevent stale instrument associations. Agents must read state.market in getContext() to determine the current instrument.
The ID is used as both the registry key and the data-axon-charts-id DOM attribute on the container element.
// Create with explicit ID
const chart = createChart('#container', {
context: { id: 'Bitcoin-1m' },
// ... other options
});
// Agent discovery in multi-chart page
window.__AXON_CHARTS__.charts['Bitcoin-1m'].setOptions({
context: { exposeData: true }
});
Agent Discovery Flow
- Agent checks
window.__AXON_CHARTS__— sees chart ID and version - Agent calls
chart.getContext()— gets metadata (exposeData: falseby default) - Agent decides to request data access via
chart.setOptions({ context: { exposeData: true } }) - Full context with bars, latest bar, and sub-panes is now available for analysis
Watermark
Semi-transparent overlay — 6 options.
Try It LiveDisplays auto-scaling text over the chart. Falls back to the market pair if text is empty.
watermark: {
show: false, // toggle visibility
text: '', // custom text (falls back to pair)
color: '#ffffff',
fontSize: null, // null = auto-scale to ~30% chart width
opacity: 0.07, // 0.01 - 1.0
rotate: false // true = -45° diagonal
}
chart.setOptions({ watermark: { text: 'DEMO', show: true, opacity: 0.05 } });
API Methods
Try It LiveChart Methods
| Method | Description |
|---|---|
setData(data) |
Load OHLCV data array |
updateLastBar(bar) |
Update the latest candle |
updateLastBarFast(bar) |
Fast update for high-frequency streams |
setOptions(options) |
Update chart options |
getContext() |
Get structured chart state |
execute(command) |
Execute universal command |
toDataURL() |
Export chart as image |
toBlob() |
Export chart as Blob (async) |
saveState() |
Serialize chart state (options, data, viewport, drawings, overlays) |
loadState(state) |
Restore chart state (accepts any 1.x schema version) |
resetState(opts?) |
Clear drawings + overlays (single render, preserves options/data/viewport) |
registerOverlayType(type, ctor) |
Register custom overlay type for saveState/loadState |
appendBar(bar) |
Append a completed bar |
prependData(bars) |
Insert historical bars at front (no viewport shift) |
getData() |
Get full data array |
getBarAtTime(ts) |
Lookup bar by timestamp |
Component APIs
| API | Methods |
|---|---|
chart.priceScale() |
setMode(), setMargins(), setReverse(), getReverse() |
chart.timeScale() |
setVisibleRange(), fitContent(), scrollToTime(), setBarSpacing(), zoomIn(), zoomOut() |
chart.crosshairAPI() |
setMode(), getMode(), setVisible(), isVisible(), setShowLabels(), getShowLabels(), setShowTooltip(), getShowTooltip(), setVerticalLine(), setHorizontalLine(), setOptions(), getOptions() |
LLM Integration
Try It LiveRead Chart State
const ctx = chart.getContext();
// Visible bars in current viewport
console.log(ctx.visibleBars);
// Price range
console.log(ctx.priceRange);
// Viewport info
console.log(ctx.viewport);
Control Chart
// Universal command interface
chart.execute({ type: 'setPriceScale', mode: 'logarithmic' });
chart.execute({ type: 'fitContent' });
chart.execute({ type: 'zoomIn', factor: 1.5 });
React to Events
Subscribe to chart events for interactive LLM workflows. See Events section for the full list.
Live Streaming
For real-time data integration patterns, see the Streaming Data section.
Export for Vision LLMs
// Get base64 screenshot
const screenshot = chart.toDataURL('image/png');
// Send to vision-capable LLM for analysis
Streaming Data
Try It LiveWebSocket Integration
const ws = new WebSocket('wss://api.example.com/stream');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
chart.updateLastBar({
time: data.time,
open: data.open,
high: data.high,
low: data.low,
close: data.close,
volume: data.volume,
});
};
High-Frequency Updates
For tick-by-tick updates (20+ per second), use updateLastBarFast():
// Fast path for every tick during live streaming
chart.updateLastBarFast(currentCandle);
// The current price line and label update atomically with every tick — no extra calls needed.
// The axis range auto-expands when price moves beyond a 1.5% hysteresis threshold.
// A full chart.render() is only needed on candle close or when manually syncing.
Events
React to user interactions and chart state changes.
Crosshair Move
Fired when the user moves the cursor over the chart. Returns the bar under cursor, price, and timestamp.
chart.onCrosshairMove(({ bar, price, time }) => {
console.log(`Price: $${price} at ${new Date(time)}`);
console.log(`OHLC: O=${bar.open} H=${bar.high} L=${bar.low} C=${bar.close}`);
});
Bar Click
Fired when the user clicks on a specific bar/candle.
chart.onBarClick(({ bar, index }) => {
console.log(`Clicked bar #${index}:`, bar);
});
Visible Range Change
Fired when the viewport changes (pan, zoom, resize). Returns the visible time range.
chart.onVisibleRangeChange(({ from, to }) => {
console.log(`Viewing: ${new Date(from)} to ${new Date(to)}`);
});
Scroll Lock Change
Fired when auto-scroll locks/unlocks as the user pans through history.
chart.onScrollLockChange((locked) => {
console.log(locked ? 'Exploring history' : 'Watching live');
});
Event Reference
| Event | Callback Parameter | Description |
|---|---|---|
onCrosshairMove |
{ bar, price, time } |
Cursor position changed |
onBarClick |
{ bar, index } |
Bar/candle was clicked |
onVisibleRangeChange |
{ from, to, fromIndex, toIndex } |
Viewport changed via pan/zoom/resize |
onScrollLockChange |
locked: boolean |
Auto-scroll state changed |
Types
Complete type definitions for the library.
Bar (OHLCV Data Point)
interface Bar {
time: number; // Timestamp in milliseconds
open: number; // Opening price
high: number; // Highest price
low: number; // Lowest price
close: number; // Closing price
volume?: number; // Optional volume data
}
ChartOptions
The main configuration object. All fields are optional — defaults fill in the rest.
interface ChartOptions {
layout?: {
width?: number | 'auto'; // default: 'auto'
height?: number | 'auto'; // default: 'auto'
background?: string; // default: '#1a1a1a'
textColor?: string; // default: '#ffffff'
fontSize?: number; // default: 12
fontFamily?: string; // default: 'system-ui'
borderVisible?: boolean; // default: false
padding?: {
top?: number; // default: 40
right?: number; // default: 60
bottom?: number; // default: 35
left?: number; // default: 10
};
};
attribution?: {
show?: boolean; // default: true (Axon.Watch attribution at bottom-left)
};
grid?: {
show?: boolean; // default: true
vertLines?: { show?, color?, width? };
horzLines?: { show?, color?, width? };
};
series?: {
type?: 'candlestick' | 'line' | 'area' | 'bar' | 'heiken-ashi' | 'hollow'; // default: 'candlestick'
upColor?: string; // default: '#10B981'
downColor?: string; // default: '#E11D48'
lineColor?: string; // default: '#1E90FF' (line/area only)
showMarkers?: boolean; // default: false (dot at every close)
showLatestPriceMarker?: boolean; // default: true (highlighted latest dot)
showLatestPriceAnimation?: boolean; // default: true (pulse on price change)
};
priceScale?: {
mode?: 'linear' | 'logarithmic' | 'percentage'; // default: 'linear'
scaleMargins?: { top?, bottom? };
priceFormat?: { type?, precision?, minMove? };
currentPrice?: {
show?: boolean; // default: true
showLine?: boolean; // default: true
showCountdown?: boolean; // default: false
countdownColor?: string; // default: 'rgba(255, 255, 255, 0.8)'
upColor?: string; // default: null (follows series.upColor)
downColor?: string; // default: null (follows series.downColor)
lineStyle?: 'dashed' | 'solid'; // default: 'dashed'
textColor?: string; // default: null (follows layout.textColor)
};
reverse?: boolean; // default: false
};
timeScale?: {
visible?: boolean; // default: true
timeVisible?: boolean; // default: true
secondsVisible?: boolean; // default: false
showFullDate?: boolean; // default: true
showDayOfWeek?: boolean; // default: true
dateFormat?: string; // default: 'MMM dd, yyyy'
timezone?: string; // default: undefined (browser local)
rightOffset?: number; // default: 80
barSpacing?: number; // default: 11
minBarSpacing?: number; // default: 4
maxBarSpacing?: number; // default: 1000
};
crosshair?: {
mode?: 'normal' | 'magnet' | 'none'; // default: 'magnet'
showLabels?: boolean; // default: true
showTooltip?: boolean; // default: true
vertLine?: { color?, width?, style? };
horzLine?: { color?, width?, style? };
};
menu?: {
enabled?: boolean; // default: true
items?: string[] | null; // default: null (all items, auto-dividers)
};
behavior?: {
dragToZoom?: boolean; // default: true
scrollToZoom?: boolean; // default: true
pinchToZoom?: boolean; // default: true
panOnMouseDrag?: boolean; // default: true
dragPriceScale?: boolean; // default: true
autoScroll?: boolean; // default: true
};
data?: {
maxBars?: number; // default: 5000
autoCleanup?: boolean; // default: true
};
market?: {
show?: boolean; // default: false
baseAsset?: string; // default: 'BTC'
quoteAsset?: string; // default: 'USDT'
timeframe?: string; // default: '1m'
source?: string; // default: ''
fontSize?: number; // default: 15
};
watermark?: {
show?: boolean; // default: false
text?: string; // default: '' (pair fallback)
color?: string; // default: '#ffffff'
fontSize?: number | null; // default: null (auto-scale)
opacity?: number; // default: 0.07
rotate?: boolean; // default: false
};
volume?: {
show?: boolean; // default: false
upColor?: string; // default: '#10B981'
downColor?: string; // default: '#E11D48'
heightPercent?: number; // default: 0.2
precision?: number | null; // default: null (auto-detect)
minMove?: number | null; // default: null
};
context?: {
exposeData?: boolean; // default: false (metadata only)
discoverable?: boolean; // default: true (visible to AI agents)
id?: string; // default: auto-generated opaque token (ax-xxxxxx)
};
// === Callbacks ===
onVisibleRangeChange?: VisibleRangeChangeCallback;
onCrosshairMove?: CrosshairMoveCallback;
onBarClick?: BarClickCallback;
onScrollLockChange?: ScrollLockChangeCallback;
onDataUpdate?: ((bars: Bar[]) => void) | null;
onCandleClose?: (closedBar: Bar) => void; // fires once per candle close
devicePixelRatio?: number; // default: window.devicePixelRatio (init only)
}
ChartCommand
Discriminated union for the execute() method.
type ChartCommand =
| { type: 'setVisibleRange'; from: number; to: number }
| { type: 'scrollToTime'; time: number }
| { type: 'zoomIn'; factor?: number }
| { type: 'zoomOut'; factor?: number }
| { type: 'fitContent' }
| { type: 'setPriceScale'; mode: 'linear' | 'logarithmic' | 'percentage' }
| { type: 'setCrosshair'; mode: 'normal' | 'magnet' | 'none' }
| { type: 'setSubPane'; id: string; show: boolean }
| { type: 'setReverse'; reverse: boolean };
Drawing
Persistent chart drawing (annotation, position, custom). Rendered on the main canvas overlay every frame.
interface Drawing {
id: string; // Unique identifier
type: string; // 'arrow_up' | 'arrow_down' | 'label' | 'hline' | 'vline' | 'position' | <custom>
color: string; // CSS color
text?: string;
// Primary anchor (one of barIndex/time required for anchored types)
barIndex?: number; // Bar index anchor (prefer time for stability)
time?: number; // Anchor timestamp (survives maxBars cleanup)
price?: number;
// Secondary anchor (two-point drawings: trendline, box, fib, measure)
barIndex2?: number;
time2?: number;
price2?: number;
data?: DrawingData; // Type-specific payload
}
interface DrawingData {
side?: 'long' | 'short'; // Position
qty?: number;
sl?: number; // Stop-loss
tp?: number; // Take-profit
status?: 'open' | 'closed' | 'pending' | 'working' | 'cancelled' | 'filled' | 'rejected';
kind?: 'limit' | 'stop' | 'stop_limit' | 'market'; // Order
lineWidth?: number; // Two-point (trendline, box, fib, measure)
lineStyle?: 'solid' | 'dashed' | 'dotted';
extend?: 'none' | 'left' | 'right' | 'both';
fill?: string;
lines?: string[]; // Text (multi-line)
textFill?: string;
showBackground?: boolean; // Default true
showBorder?: boolean; // Default true
borderColor?: string;
textColor?: string;
maxWidth?: number; // Wrap width (pixels)
showAnchorDot?: boolean; // Default true
showPrice?: boolean; // Label: show formatted price
[key: string]: unknown; // Custom types
}
Built-in types are registered at module load. Custom types can be registered via chart.registerDrawingType(type, renderer) — the renderer must implement the DrawingRenderer interface (a render(ctx, chart, drawing) method). The resolveAnchor() helper is exported for reuse in custom renderers.
ChartContext (LLM Output)
The structured object returned by getContext().
interface ChartContext {
viewport: {
width: number;
height: number;
rightGap: number;
visibleRange: {
fromIndex: number;
toIndex: number;
fromTime: number;
toTime: number;
};
priceRange: { min: number; max: number };
scales: {
pricePerPixel: number;
timePerBar: number;
barWidth: number;
};
};
state: {
totalBars: number;
isAutoScrolling: boolean;
};
visibleBars: Bar[];
latestBar: Bar;
subPanes: Record<string, any>;
}
Performance
Benchmarks and optimization guidelines.
Benchmark Results
| Metric | Target | Actual |
|---|---|---|
| Minified bundle | <50 KB gzipped | 46.2 KB gzipped ✓ |
| First render (500 bars) | <16ms | 3.4ms ✓ |
| Live tick update | <2ms | <1ms ✓ |
| Mousemove overlay | <1ms | <0.3ms ✓ |
| Memory (5000 bars) | <12 MB | minimal ✓ |
Architecture
The library uses a three-layer canvas architecture for maximum performance:
| Layer | Canvas | Renders |
|---|---|---|
| Background | bgCanvas |
Fill, grid lines, price/time axes, watermark, volume sub-pane, percentage reference line |
| Main | mainCanvas |
Candles (offscreen-buffered) + current price line (atomic render pass) |
| Overlay | overlayCanvas |
Crosshair, tooltip, market header |
Offscreen Buffer
The candle buffer is rendered at identity scale (1:1 pixel ratio) into an offscreen canvas. Only the visible portion is composited to the display canvas every frame. When a single candle updates (tick stream), only updateLastCandleInBuffer() is called — no full re-render.
Optimization Tips
- Streaming: Use
updateLastBarFast()for tick-by-tick updates (10-1000/sec). Callchart.render()every 20 ticks to refresh axes/grid. - Data Limits: Set
data.maxBarsto the minimum required for your viewport. 500 bars is sufficient for most charts. - Sub-Panes: Only enable volume/indicators when needed — each sub-pane adds render overhead.
- Overlays: Overlay indicators (SMA, EMA, BB, etc.) re-compute every frame. For large datasets, pre-compute values and inject via
SubPane.setData(). - Persistence:
saveState()captures drawings + overlays. UsemigrateSnapshot()to upgrade old snapshots beforeloadState(). - Resize: The library uses ResizeObserver with a re-entrancy guard — avoid manual resize calls when using auto-sizing containers.
- Canvas Export:
toDataURL()is the most expensive operation — debounce to every 500ms.
Bundle Size
- Minified + IIFE: ~184 KB / 180 KB ESM
- Gzipped: ~46.2 KB
- Zero external dependencies