Cette page n’est pas encore traduite ; elle s’affiche en anglais.

API reference

Public surface of the top-level classes: Chart, ChartWidget, ChartWidgetGrid and ChartGrid.

Chart

Headless renderer. Bring your own UI; subscribe to events; mutate state imperatively.

Construction

new Chart(host: HTMLElement, options?: ChartOptions)

Data

MethodPurpose
setData(data)Replace the entire series.
appendBar(bar)Append a new bar; auto-scroll if enabled.
appendBars(bars)Batch append; recalculates indicators once.
updateLastBar(bar)Mutate the current forming bar.
updateLastBarFromTick(tick)Merge a tick into the last bar.
getData()Read the raw OHLC series.

Chart type & theme

MethodPurpose
setChartType(type)One of 18 types — see Chart types.
setTheme(name)Switch between built-in themes.
setTimeframe(tf)Switch active timeframe; rewires the live stream.

Renderer

MethodPurpose
renderer option'canvas' (default), 'webgl', or 'auto' (WebGL on a hardware GPU only).
setRenderer(mode)Switch at runtime. Resolves to what draws now: 'canvas' where WebGL 2 can't be had.
getRenderer()'canvas' or 'webgl'.

What the GPU draws, and the numbers: Performance → WebGL renderer.

Indicators

MethodPurpose
addIndicator(id, params?, position?)Adds an overlay or panel indicator. Returns instance id.
updateIndicator(instanceId, params)Mutate a live indicator.
removeIndicator(instanceId)Remove and tear down.

Axis & scale

The price axis (right strip) and time axis (bottom strip) accept direct pointer interaction, with the gestures traders already know:

GestureEffect
Drag price axis up / downCompress / expand the vertical price range (disables auto-scale).
Drag time axis left / rightZoom in / out on the time axis.
Double-click price axisRe-enable auto-scale.
Double-click time axisFit all data to the viewport.

Timezone. Time-axis labels and the crosshair time pill follow the browser's local zone by default; switch to a fixed UTC offset (or back to local) from the settings sheet or directly:

chart.setTimezoneOffset(-300)  // EST (UTC-5), in minutes
chart.setTimezoneOffset(330)   // IST (UTC+5:30)
chart.setTimezoneOffset(null)  // back to browser-local

The same effects are also available programmatically:

chart.setAutoScale(false)  // freeze the current price range
chart.setLogScale(true)    // switch to logarithmic price scale
chart.setInvertScale(true) // upside down (Alt+I in the widget)
chart.fitContent()         // zoom out to all data
chart.scrollToEnd()
chart.setVisibleRangePreset('3M')       // 1D 5D 1M 3M 6M YTD 1Y 5Y All
chart.goToTime(Date.UTC(2025, 0, 1))    // centre that bar (Alt+G in the widget)

Price scale modes. Beyond regular and logarithmic, the axis can rebase its labels against the first visible bar — percentage shows % change, indexedTo100 rebases the baseline to 100. Regular, percentage, and indexed share the same linear geometry; only the labels differ. Settable from the chart-settings panel or directly:

chart.setScaleMode('percentage')   // axis labels: +12.34% from first visible bar
chart.setScaleMode('indexedTo100') // first visible bar reads as 100
chart.setScaleMode('logarithmic')
chart.getScaleMode()

Price and time formats

Prices can read in your own words, or in fractions of a point the way bonds and their futures are quoted (101'16 is 101 and 16/32). The format reaches everything that prints a price on the price scale: axis, crosshair, last-price tag, legend, tooltips, orders, alerts and drawing labels; the axis puts its ticks on whole fractions and roundPrice rounds to them. Indicator panes keep their own numbers. Times can read your way too; the formatter is told what a label is.

new Chart(host, { priceFormat: { denominator: 32 } })                    // 101'16
chart.setPriceFormat({ denominator: 32, subDenominator: 2 })            // 101'165: 16½ 32nds
chart.setPriceFormat((p) => '$' + p.toFixed(2))
chart.setPriceFormat(null)                                              // decimals again

chart.setTimeFormatter((time, { kind, timeZone }) =>
  // kind: 'date' (a daily bar), 'day' (a new day), 'time' (within a day), 'crosshair'
  new Intl.DateTimeFormat('en-GB', { timeZone: timeZone ?? undefined, hour: '2-digit', minute: '2-digit' }).format(time))

Comparing symbols

Another symbol's percent change on the price scale (addCompareSymbol), or its price on a scale of its own, in a pane of its own, or the spread or ratio of the chart's close to it — those last are indicators (compareSymbol, spread), with legends, value tags, alerts and saved layouts like any other. The other symbol's bars line up with the chart's by time. The chart asks for the bars it needs; give them again after a timeframe change.

chart.addCompareSymbol('eth', 'ETHUSDT', ethBars, '#7c4dff')   // percent change, on this scale
chart.addIndicator('compareSymbol', { symbol: 'ETHUSDT' }, 'bottom', { scale: 'left' })  // own scale
chart.addIndicator('spread', { symbol: 'ETHUSDT', mode: 'ratio' })                      // own pane

chart.on('symbolSeriesRequest', async ({ payload }) =>
  chart.setSymbolSeries(payload.symbol, await adapter.fetchHistory(payload.symbol, '1h', 1000)))
chart.getRequiredSymbols()                           // what to fetch again on a new interval
chart.setPaneScale(spreadId, { percent: true })     // a pane in percent of its first value

In ChartWidget, the object tree's compare button asks for a symbol, then how: percent change, own scale, own pane, spread or ratio.

Exporting data

The bars as CSV or JSON, each indicator line in a column of its own named as the legend names it. ChartWidget has Export data (CSV) in the chart's right-click menu.

chart.exportAllData('csv', 'btc-1h.csv')                 // every bar loaded
chart.exportVisibleData('json', undefined, { indicators: false })
const text = chart.getExportText('csv', { range: 'visible' })
const { bars, columns } = chart.getExportData()          // columns: { name, values }[]

Volume Profile

Horizontal histogram of traded volume bucketed by price over the visible range. Off by default — toggle programmatically or via the widget settings sheet:

chart.setVolumeProfileVisible(true)
chart.setVolumeProfileConfig({
  buckets: 48,        // resolution of the histogram
  widthRatio: 0.18,   // % of chart width
  opacity: 0.32,
  highlightPoC: true, // mark the highest-volume bucket
})

Swing markers (pivots)

Mark fractal swing highs/lows with small triangles (▼ above a confirmed pivot high, ▲ below a pivot low). The strength controls how many bars must be lower on each side. Toggle from the settings sheet, or:

chart.setPivotMarkersVisible(true)
chart.setPivotMarkersConfig({ left: 5, right: 5, showLabels: true })

// market-structure labels (HH / HL / LH / LL) instead of price
chart.setPivotMarkersConfig({ structureLabels: true })

// pure detection + classification are exported
import { findPivots, classifyPivots } from '@tradecanvas/core'
const pivots = findPivots(bars, 5, 5)        // [{ index, price, type }]
const structure = classifyPivots(pivots)     // adds label: 'HH'|'LH'|'HL'|'LL'

Session shading (regular trading hours)

Dim bars outside the regular session (pre/post-market or the overnight break) so the cash session stands out. Defaults to US equity RTH (09:30–16:00 New York time, daylight saving included); configure the window in minutes-of-day plus the market's time zone. A symbol whose feed reports its sessions sets them by itself.

chart.setSessionShadingVisible(true)
chart.setSessionShadingConfig({
  startMinute: 9 * 60 + 30,     // 09:30
  endMinute: 16 * 60,           // 16:00 (end-exclusive; end < start wraps midnight)
  timeZone: 'America/New_York', // or tzOffsetMinutes: -300 for a fixed offset
})

Extended hours. Turn them off and the bars outside the symbol's regular hours (SymbolInfo.sessions in its timezone) leave the chart; they are kept aside, live bars and history pages too, and come back when turned on. Bars a day or longer are left as they are.

chart.setSymbolInfo({ symbol: 'AAPL', timezone: 'America/New_York', sessions: [{ start: '09:30', end: '16:00' }] })
chart.setExtendedHours(false)     // or new Chart(host, { extendedHours: false })
chart.isExtendedHoursVisible()

Prior-period levels (PDH / PDL / PDC)

Draw the prior day's (or week's) high, low, and close plus the current period's open as labelled horizontal lines — the support/resistance levels intraday traders watch. Toggle from the settings sheet, or directly:

chart.setPeriodLevelsVisible(true)
chart.setPeriodLevelsPeriod('week')   // 'day' (PDH/PDL/PDC) | 'week' (PWH/PWL/PWC)

// pure computation is exported
import { computePeriodLevels } from '@tradecanvas/core'
const levels = computePeriodLevels(bars, 'day')  // [{ id, label, price }]

Market Profile (TPO)

A time-at-price histogram: each bar contributes one TPO to every price bucket its range touched, surfacing the Point of Control (busiest price) and the value area (≈70% of TPOs). Distinct from Volume Profile — it weights by time, not volume — and is left-pinned so both can show together. Off by default; toggle from the settings sheet or directly:

chart.setMarketProfileVisible(true)
chart.setMarketProfileConfig({
  buckets: 48,
  widthRatio: 0.18,
  opacity: 0.32,
  valueAreaPct: 0.7,  // fraction of TPOs in the value area
  highlightPoC: true, // dashed line at the point of control
})

// split into one mini-profile per calendar-day session
chart.setMarketProfileConfig({ splitBySession: true })

// classic TPO letters per session (when zoomed in enough to be legible)
chart.setMarketProfileConfig({ splitBySession: true, letters: true })

// pure computation is exported too
import { computeMarketProfile, computeSessionProfiles } from '@tradecanvas/core'
const profile = computeMarketProfile(bars, priceMin, priceMax, { buckets: 48 })
const sessions = computeSessionProfiles(bars, priceMin, priceMax)  // per-day TPO

Touch & mobile

GestureAction
1-finger drag (chart area)Pan + move crosshair
2-finger pinchZoom around the midpoint
Long-press (~500 ms)Pin OHLC tooltip at the bar (mobile equivalent of Alt-click)
1-finger drag inside price / time axis stripScale the corresponding axis

Modals (settings, hotkey sheet, command palette, symbol search) automatically switch to a bottom-sheet pattern with a grab handle and safe-area-aware padding under 640 px viewports.

Measure tool

Hold Shift and drag on the chart to measure bars × price between two points — the overlay shows price Δ (absolute + %), bar count, and time span. The overlay clears as soon as the mouse is released; it does not persist into saved state.

Events

All events are typed via ChartEventMap:

chart.on('orderPlace', e => /* OrderPlacePayload */)
chart.on('orderModify', e => /* OrderModifyPayload */)
chart.on('signalMarkerAdd', e => /* { marker } */)
chart.on('tradeZoneAdd', e => /* { zone } */)
chart.on('dataUpdate', e => /* { length } */)
chart.on('ordersChange', e => /* { orders } */)
chart.on('positionsChange', e => /* { positions } */)
chart.on('executionFill', e => /* { side, price, quantity, reason, pnl } */)
chart.on('chartContextMenu', e => /* { area, x, y, price, time } */)
chart.on('stateChange', () => /* drawings, indicators, alerts, chart type or theme may have changed */)
chart.on('paneChange', e => /* { instanceId, change: 'collapsed' | 'maximized' | 'order' } */)
chart.on('chartTypeChange', e => /* { type, previous } */)
chart.on('symbolChange', e => /* { symbol, previous } */)
chart.on('timeframeChange', e => /* { timeframe, previous } */)
chart.on('historyChange', e => /* { canUndo, canRedo } */)
chart.on('drawingSelect', e => /* { ids, primary } */)
chart.on('rendererChange', e => /* { renderer: 'canvas' | 'webgl', reason?: 'unsupported' | 'contextLost' } */)

Undo for your own changes

recordUndo puts a change of yours in the chart's undo history, with its drawings and indicators: Ctrl/Cmd+Z calls undo, redo calls redo. Changes about the same subject close together are one step. ChartWidget records its settings and chart type this way.

const before = panel.color
panel.color = 'red'
chart.recordUndo({ subject: 'panel-color', undo: () => (panel.color = before), redo: () => (panel.color = 'red') })

Keyboard and screen readers

A focused chart scrolls with the arrow keys (Shift for ten bars), zooms with ↑/↓ or +/−, and goes to the start and end with Home and End. For screen readers it is an application with a summary (symbol, type, timeframe, last price); after the keys move the view it says what is on screen, and comma and period read the bars one at a time. a11y.labels puts it in your language; a11y: false leaves it out. scrollBars(n) and selectDrawing(id) do from code what the keys and a click do.

new Chart(host, { a11y: { labels: { role: 'gráfico', summary: '{what}. Último precio {close}.' } } })

Prices from the pointer can be put on the market's grid with chart.roundPrice(price): a multiple of the symbol's minTick, else its precision. ChartWidget's menus and order ticket do this.

ChartWidget

Wraps Chart in a complete UI. Same instance is available via widget.chart.

import { ChartWidget } from '@tradecanvas/chart/widget'

const widget = new ChartWidget(host, {
  symbol: 'BTCUSDT',
  timeframe: '5m',
  theme: 'dark',
  adapter: new BinanceAdapter(),
  historyLimit: 500,
  trading: true,
  features: { drawings: true, indicators: true },
  onReady: (chart) => { /* ... */ },
})

widget.chart.setData(...)
widget.destroy()

Widget keyboard shortcuts

ShortcutAction
Ctrl / ⌘ + KCommand palette (indicators, chart types, drawings…)
Ctrl / ⌘ + PSymbol search — fuzzy picker over the configured symbol list
Ctrl / ⌘ + SSave the layout (asks for a name the first time)
0–9Type an interval (5, 15m, 1h, 1D) and press Enter (intervalTyping: false turns it off)
Alt + T / H / J / V / C / FTrend line, horizontal line, horizontal ray, vertical line, cross line, Fibonacci retracement
?Show the keyboard shortcuts sheet
Alt + click chartPin OHLC tooltip at the hovered bar (delta to live crosshair shown)
EscUnpin tooltip / cancel drawing
Click symbol in toolbarOpens the symbol search modal
Click play in toolbarOpens the bar replay scrubber (play/step/seek/speed)

Update the searchable catalog at runtime with widget.setSymbols(['BTCUSDT', 'ETHUSDT', …]).

Data Window

A floating readout of the exact O/H/L/C/V, bar change, and every active indicator's value at the hovered bar — updates live as you move the crosshair. Toggle it from the command palette (Ctrl/⌘ K → "Toggle Data Window").

Shareable view (deep links)

Encode the whole view — symbol, timeframe, chart type, price scale, indicators (with params), and drawings — into a compact, URL-safe string for deep-linking. With shareUrl: true the widget restores a #tcw=… hash on load and the "Share View" command palette action copies a link to the clipboard.

const widget = new ChartWidget(host, { shareUrl: true })

const token = widget.exportState()      // portable string
await widget.importState(token)         // restore a view
await widget.copyShareLink()            // copy "<url>#tcw=<token>"

Named layouts

The layout button on the toolbar saves the chart under a name: symbol, interval, price scale, chart type, indicators, drawings and alerts (not the theme, which stays the viewer's). Open, rename and delete layouts from its menu; the layout open auto-saves as it changes, and Ctrl/⌘ S saves it. Layouts live in this browser's localStorage unless you give a storage: four calls, each may return a promise.

import { ChartWidget, type LayoutStorage } from '@tradecanvas/chart/widget'

const server: LayoutStorage = {
  list: () => api.get('/layouts'),              // [{ id, name, symbol, timeframe, updatedAt }]
  load: (id) => api.get(`/layouts/${id}`),     // { ...summary, content } or null
  save: (layout) => api.put(`/layouts/${layout.id}`, layout),
  remove: (id) => api.delete(`/layouts/${id}`),
}

const widget = new ChartWidget(host, {
  layouts: { storage: server, autoSave: true, openLast: true },  // or false for none
})

const layouts = widget.getLayoutSession()!
await layouts.saveAs('Swing BTC')
await layouts.open(id)
layouts.current()          // { id, name, … } or null
layouts.setAutoSave(false)

// The content alone, to keep wherever you like
const json = widget.getLayoutContent()
await widget.applyLayoutContent(json)

localStorageLayouts(prefix) and memoryLayouts() are the two storages that ship. Stored content is read defensively: a layout that does not parse is refused, not half applied. Each layout records its kind ('chart' or 'grid'), so a widget and a grid can share one storage and each lists only its own. Saving, opening and auto-saving run one at a time, so a save never lands in a layout opened after it.

Per-symbol layouts

Separately, persist per-symbol indicator stacks, drawings, alerts, and chart type to localStorage automatically:

new ChartWidget(host, {
  symbol: 'BTCUSDT',
  symbols: ['BTCUSDT', 'ETHUSDT', 'SOLUSDT'],
  adapter: new BinanceAdapter(),
  persistLayouts: true,  // or { keyPrefix: 'myapp:', debounceMs: 2000 }
})

// Reset a single symbol's layout
widget.clearSavedLayout('BTCUSDT')

Layouts flush on symbol switch and on widget destroy so nothing is lost when the user navigates away.

Drag-and-drop data import

Drop a CSV or JSON file onto the chart to load it instantly. Enabled by default — disable with dragDropImport: false. The parser handles common column layouts (time, open, high, low, close, volume), ISO 8601 timestamps, and unix seconds/ms.

// Programmatic use
import { parseOHLCV } from '@tradecanvas/chart'

const { data, rowCount, skipped } = parseOHLCV(csvText)
chart.setData(data)

Timeframe resampling

Feed the widget your finest-resolution series with widget.setData() and the toolbar timeframe buttons aggregate it on the client — one dataset drives every resolution, no refetch. Active whenever no live adapter is attached; opt out with resampleTimeframes: false. Weekly buckets anchor to Monday by default (weekStartsOn: 0 for Sunday).

const widget = new ChartWidget(host, {
  symbol: 'BTCUSDT',
  timeframe: '1h',
  timeframes: ['5m', '15m', '1h', '4h', '1d', '1w'],
})
widget.setData(oneMinuteBars)   // base series; clicking 4h/1d/1w resamples it

// Or use the pure function directly
import { resampleOHLCV, inferTimeframeMs } from '@tradecanvas/chart'

const hourly = resampleOHLCV(oneMinuteBars, '1h')   // OHLC merged, volume summed
const fourHour = resampleOHLCV(oneMinuteBars, '4h', { weekStartsOn: 1 })

Calendar-aware bucketing: intraday and daily frames anchor to UTC epoch boundaries, weeks to the configured week start, and months / quarters / years to calendar boundaries. Input bars are never mutated.

Watchlists

A right-side panel of lists of symbols, each row with last price, % change and a sparkline. Lists are switched, created, renamed and deleted from its menu; symbols are added from the symbol search (+), removed, and reordered by dragging or Alt+↑/↓. Rows take quotes from the adapter's subscribeQuotes, a quote source of yours, or what you push.

const widget = new ChartWidget(host, {
  symbol: 'BTCUSDT',
  adapter: new BinanceAdapter(),               // its quotes fill the rows
  watchlist: {
    lists: [
      { id: 'majors', name: 'Majors', symbols: ['BTCUSDT', 'ETHUSDT'] },
      { id: 'alts', name: 'Alts', symbols: ['SOLUSDT', 'ADAUSDT'] },
    ],
    persist: true,                             // kept in this browser
    onChange: (lists, active) => save(lists),  // or keep them yourself
  },
})

widget.addToWatchlist('BNBUSDT', 'alts')
widget.setActiveWatchlist('alts')
widget.setQuotes([{ symbol: 'AAPL', last: 190.2, prevClose: 188.1 }])   // from your own feed
widget.getQuote('AAPL')

watchlist: true shows one list of symbols, as before; setWatchlistEntry still pushes a row's price, move and sparkline.

Symbol info

A panel from the toolbar's ⓘ button (or the command palette): the symbol's names, last price and move, the market's status counting down to the next open or close, the day's open, range, volume, prior close, bid and ask, tick size, currency, zone and trading hours, and news. The status bar shows when the market is closed.

new ChartWidget(host, {
  symbol: 'AAPL',
  news: (symbol, limit) => api.headlines(symbol, limit),   // or the adapter's fetchNews
})
widget.toggleSymbolInfo(true)

Navigation, undo and direction

Buttons over the chart — zoom out and in, scroll earlier and later (held, they keep going), reset — show while the mouse is on it (navigation: false leaves them out). Settings changes and chart type changes are undone with Ctrl/Cmd+Z, with drawings and indicators. In Arabic, Hebrew, Persian or Urdu the widget mirrors (dir: 'auto'; or 'rtl' / 'ltr'); the chart keeps time running left to right.

Drawing favorites

Pin frequently-used drawing tools to a strip at the top of the sidebar. Right-click any tool (in a group flyout or the strip itself) to pin or unpin it; the set persists to localStorage. Seed the initial pins with drawingFavorites:

new ChartWidget(host, {
  drawingFavorites: ['trendLine', 'horizontalLine', 'fibRetracement', 'rectangle'],
})

Drawing style & templates

The palette button on the drawing sidebar opens a style popover — pick colour, line width, and line style for the next drawing (and the selected one), and save named templates persisted to localStorage for one-click reuse. Programmatic equivalents:

chart.setDrawingStyle({ color: '#e8505b', lineWidth: 2, lineStyle: 'dashed' })
chart.getDrawingStyle()
chart.setSelectedDrawingStyle({ color: '#1fa874' })  // restyle the selected drawing

Object tree

The toolbar layers button opens an object-tree panel listing every active indicator and drawing. Indicators can be removed; drawings get per-item show / hide, lock / unlock, settings and delete, and groups are listed with their drawings under them. Enabled by default — disable with objectTree: false. The drawing controls map to:

chart.getDrawings()                 // DrawingState[] (id, type, visible, locked)
chart.setDrawingVisible(id, false)  // hide a single drawing
chart.setDrawingLocked(id, true)    // lock it from edits
chart.removeDrawing(id)
chart.groupDrawings(ids, 'Weekly levels')  // hidden, locked and selected together
chart.renameDrawingGroup(groupId, 'Old highs')
chart.getActiveIndicators()         // active indicator instances
chart.updateIndicator(instanceId, { period: 50 })  // re-tune params live
chart.removeIndicator(instanceId)

Drawing settings and menu

Double-click a drawing, or use its gear in the object tree, to open its settings: style, the tool's own settings (Fibonacci levels, extensions, labels…) and its points in the chart's time zone. Right-click a drawing for its menu: settings, an alert on its line, order, group, lock, hide, duplicate and delete. The sidebar also has an eraser, a zoom tool and a magnet that goes off, weak and strong. See Drawing tools for the API underneath.

The gear button on each indicator row opens a settings dialog that introspects the indicator's parameters (numbers, toggles, colors) and applies edits live via updateIndicator — no need to remove and re-add to change a period or colour.

The object tree's Compare section overlays other symbols as normalized lines. With a live adapter, the + button opens the symbol picker, fetches that symbol's history via adapter.fetchHistory, and adds it in percent mode (so mixed-price symbols share one axis). Comparisons refetch automatically on timeframe change. Programmatic equivalents:

widget.addCompareSymbol('ETHUSDT')   // fetches + overlays (needs an adapter)

// or drive the chart directly with your own data
chart.addCompareSymbol('ETHUSDT', 'ETH', ethBars, '#627eea')
chart.setCompareMode('percent')      // 'percent' | 'absolute'
chart.removeCompareSymbol('ETHUSDT')

Price alerts

Beyond a level, an alert can compare a line with another line (the price crossing a moving average, MACD crossing its signal), fire on a move of some percent within some bars (2 to 500), look only at closed bars (no firing on a wick that comes back), and expire. The widget's alerts panel has all of these; from code they are the last argument of addAlert. Alerts check on every price, from setCurrentPrice or a connected feed, together with the lines they watch:

const ema = chart.addIndicator('ema', { period: 50 })
const rsi = chart.addIndicator('rsi')
chart.addAlert(NaN, 'crossingUp', 'above the 50 EMA', 'price', undefined, { target: `${ema}:value` })
chart.addAlert(NaN, 'movesUp', 'pump', 'price', undefined, { percent: 5, bars: 12 })
chart.addAlert(70, 'greaterThan', 'RSI closed above 70', `${rsi}:value`, 'RSI', { onBarClose: true })
chart.addAlert(64_000, 'crossing', 'today only', 'price', undefined, { expiresAt: Date.now() + 86_400_000 })

chart.on('alertExpired', (e) => e.payload)   // it reached its time without firing
chart.on('alertTriggered', (e) => e.payload)  // { id, condition, channel, target?, percent?, bars?, … }

The toolbar bell opens a floating panel to add, list, and delete price alerts; a toast fires when one triggers. Alert lines are also draggable — grab one on the chart and slide it to re-price (moving an alert re-arms it). Enabled by default — disable with alerts: false. Drive it programmatically via the Chart API and the typed alert events:

// Add from code (condition: 'crossing' | 'crossingUp' | 'crossingDown'
//                          | 'greaterThan' | 'lessThan')
const id = chart.addAlert(64200, 'crossingUp', 'breakout')
chart.removeAlert(id)
chart.getAlerts()      // PriceAlert[]
chart.saveAlerts('tcw:alerts:BTCUSDT')   // localStorage persistence
chart.loadAlerts('tcw:alerts:BTCUSDT')

// React to triggers
chart.on('alertTriggered', (e) => {
  console.log('hit', e.payload.price, e.payload.message)
})
// also: 'alertAdd' / 'alertRemove' / 'alertUpdate' (fired on drag)

// Indicator alerts: bind to an indicator line via channel '<instanceId>:<key>'.
// In the widget, the alerts panel's source dropdown lists every active line.
const ema = chart.addIndicator('rsi')
chart.addAlert(70, 'crossingUp', 'RSI overbought', `${ema}:rsi`, 'RSI')

Opt into a sound and/or desktop notification when an alert fires (both off by default). sound: true plays a built-in beep; pass a URL for a custom one. desktop: true uses the Notification API and asks permission on first use.

new ChartWidget(host, {
  alertNotifications: { sound: true, desktop: true },
})

Your own buttons and menu entries

Add buttons to the toolbar (a built-in icon or an element of yours, text, a switch) and entries to the chart's right-click menus, after the widget's own.

const news = widget.addToolbarButton({
  id: 'news',
  label: 'News',
  icon: 'bell',            // or an <svg> element; or text: 'News'
  side: 'right',           // 'left' sits with the chart controls
  toggle: true,
  onClick: () => news?.setActive(togglePanel()),
})
news?.setText('3')
news?.remove()

new ChartWidget(host, {
  chartMenuItems: ({ area, price, time }) => area === 'plot' && price !== undefined
    ? [{ label: `Copy ${price.toFixed(2)}`, icon: 'check', onSelect: () => copy(price) }]
    : [],
})

ChartWidgetGrid

Several chart widgets side by side, each with its own symbol, interval, indicators and drawings. A bar above them picks the arrangement, links the charts and saves the whole grid as a named layout. The chart pressed last is the active one (outlined).

import { ChartWidgetGrid } from '@tradecanvas/chart/widget'

const grid = new ChartWidgetGrid(host, {
  layout: '2x2',                                   // '1x1' '1x2' '2x1' '2x2' '1x3' '3x1' '2x3' '3x2'
  widget: { timeframe: '1h' },                    // every chart
  adapter: () => new BinanceAdapter(),            // one per chart: an adapter keeps one stream
  cells: [{ symbol: 'BTCUSDT' }, { symbol: 'ETHUSDT' }, { symbol: 'SOLUSDT' }, { symbol: 'BNBUSDT' }],
  sync: { crosshair: true, time: false, symbol: false, interval: false, drawings: false, replay: false },
})

grid.setLayout('1x2')
grid.setSync({ interval: true })   // lines the others up with the active chart
grid.getActiveWidget().getChart()
grid.getLayoutSession()?.saveAs('Majors')

// Each chart as it is made (at the start, and when the grid grows)
new ChartWidgetGrid(host, {
  onChartAdd: (widget, index) => widget.getChart().addIndicator('ema', { period: 21 }),
})

Crosshair sync shows the time under the pointer on every chart; time sync scrolls and zooms the others with the chart being used; drawings are copied to the charts showing the same symbol (switching it on puts their drawings together, none lost); replay sync replays the others to the same time as the chart being replayed (a chart whose bars are longer than the replay's steps shows the whole bar that holds that time). Charts the grid shrinks from are put away, kept in the saved layout, and come back as they were when it grows again; a brand-new chart opens on the active chart's symbol and interval when those are synced.

ChartGrid

Synchronized multi-chart layouts of headless charts (no toolbar); see ChartWidgetGrid for the full UI.

import { ChartGrid } from '@tradecanvas/chart'

const grid = new ChartGrid(host, { layout: '2x2', theme: 'dark' })
// One adapter per chart: an adapter keeps one stream
await grid.connectAll(() => new BinanceAdapter(), ['BTCUSDT','ETHUSDT','SOLUSDT','BNBUSDT'], '5m')
grid.setLayout('1x2')

Layouts: '1x1', '1x2', '2x1', '2x2', '1x3', '3x1', '2x3', '3x2'.