# API Reference This is a hand-written, conceptual reference for the Vela™ public surface — what each piece is for and how the pieces bind. A generated type reference (from the source declarations) may be added later; for now this prose is the source of truth. ## The mental model Vela™ is a small **core** plus three independently swappable **layers** — data providers, scripting engines, and renderers — each reached through a single narrow **port**. The **neutral model** — bars, series, pane overlays, drawings, inputs, update patches — is the only thing that crosses a port. No backend-specific type ever leaks across. That opacity is what makes each layer swappable. What ships is the native renderer and the provider-backed, cache-wrapped data feed — both plain **swappable defaults**. No scripting engine ships at all: you install one (Pine Script: `@luxalgo/vela-pinets`) or write one against the port — see [Scripting engines](./scripting-engines.md). > These snippets import from the npm package `@luxalgo/vela` (see [installation.md](./installation.md)). The higher-level shell is documented on its own page: [the workspace](./workspace.md) (`@luxalgo/vela/workspace` — one chart with `layout: false`, or a multi-chart grid with one shared chrome and sync links). It exposes the state surface — `getState()` / `applyState()` / `state:changed` over one shared document format — which is also what its `persist` option writes. --- ## `class Vela(container, options?, deps?)` The public, imperative chart and the composition root — the one place that wires concrete backends. - **`container`** — an `HTMLElement` or a CSS selector string. A missing selector throws. - **`options`** — display, behavior, and market options. See [options.md](./options.md). - **`deps`** — the **swap point** for the three layers (see below). Constructing a chart renders candles immediately. Scripting engines are opt-in. ### Core methods | Method | What it does | |---|---| | `registerEngine(language, engine)` | Register a scripting engine under a language id so `addIndicator` can run that language. **Vela™ ships none** — install an addon or write one ([Scripting engines](./scripting-engines.md)). Re-registering a language replaces it (affects future indicators only). Returns the chart for chaining. | | `addIndicator(source, options?)` | Run an indicator script over the chart's market data and render it. Returns an **`IndicatorHandle` synchronously**; values fill in asynchronously. See [options.md](./options.md) for per-indicator options. | | `addNativeIndicator(type, options?)` | Add a core-computed (non-scripting) **native indicator** by registered `type`. Returns an `IndicatorHandle` (same lifecycle: legend row, eye/remove, events). The layer-backed types `'volume'` (auto-added) and `'vpvr'` (the visible-range volume profile) are **single-instance** — a second call returns the existing handle; the built-in classic studies (`'sma'`, `'rsi'`, …) allow **several instances**, so each call adds one more. Plugin chart types can register more (see `multiInstance` on the descriptor). `options.inputs` seeds inputs. Native renderer only; an unregistered type returns a fail-soft handle that never mounts. | | `runScript(source, options?)` | Execute a script and resolve its **first computed run** — the data-out door for editors, consoles and dashboards. Resolves `{ ok: true, run }` with the [`ScriptRun`](#capturing-what-a-script-computes) itself, plus `onUpdate(cb)` to follow later runs and `remove()` to take it off the chart; `{ ok: false, error }` on a compile/runtime failure, which removes the script again (no dead legend row). Never rejects. Prefer it over `runIndicator` — same injection semantics, but it hands you the data instead of a handle to go fetch from. | | `runIndicator(source, options?)` | Execute a script and **inject it only if the run succeeds** — the seam for host editors/consoles. Resolves `{ ok: true, handle }` after the first successful evaluation, or `{ ok: false, error, context }` on a compile/runtime failure — `context` is the post-mortem execution-context snapshot when the engine had produced one, and the failed indicator is removed again (no dead legend row). Never rejects. | | `indicators()` | Live `IndicatorHandle[]` of everything on the chart (script + native), in insertion order — the seam for host panels (object trees, indicator lists) that need per-id visibility/removal. | | `availableNativeIndicators()` | Returns `Promise` — the catalog of built-in native indicators with their live state on this chart, for building an "add indicator" picker UI (lets a host list them, gate unsupported ones, avoid duplicates). Async because support may need to probe the provider (a type may need data the symbol lacks). | | `presentNativeIndicators()` | Returns `string[]` — the native types present on the chart **right now**, one entry per instance (a multi-instance type repeats), synchronously (the presence slice of `availableNativeIndicators()`, which stays async only for support probing). Persistence snapshots read this: an unload-time save must see an add or remove made microseconds earlier. | | `setMarket(next)` | Switch the chart's market **in place** — `{ symbol?, timeframe?, bars?, data?, visibleRange? }` — without destroying the chart. The symbol string carries the venue: bare = registered providers in declaration order, `'coinbase:BTC-USD'` pins one. Only the fields given change. Indicators re-execute over the new bars, native indicators restart, and panes, user drawings, renderer config and event subscriptions all **survive**. Resolves once the new market's history is painted (a deep backfill continues behind it — await `historyComplete()`); a call superseded by a newer `setMarket` resolves silently. Emits `market:changed` when the market identity changed (a depth-only `bars` reload is silent). `visibleRange` frames the first paint of the new market. Drawings are kept as-is — per-symbol drawing documents are a host policy (`chart.drawings.toJSON()/fromJSON()` keyed off `market:changed`). | | `market` (getter) | The current market identity — the read counterpart of `setMarket`. A **snapshot** `{ symbol?, provider?, timeframe?, bars?, offline }` of the *requested* market (`provider` = the symbol's own `EXCHANGE:` prefix, or undefined when bare — the venue that actually served it is `chart.data.resolve(symbol)`): it reflects an in-flight switch immediately (before the new bars land), which is what persist-on-close flows want. Listen to `market:changed` for *committed* identity changes. Mutating the returned object changes nothing. | | `ready()` | Returns a promise that resolves once the chart is painted and interactive. On a ranged feed the first paint is a small recent head (~200 bars) and the rest of the history keeps backfilling **behind** this — await `historyComplete()` for the full depth. | | `historyComplete()` | Returns a promise that resolves once the **current load's** full requested history has loaded — immediately for small/offline charts, after the background backfill for deep ones. **Per-load**: each `setMarket` re-arms the cycle (the superseded load's promise resolves rather than hanging), so call it again after a switch for the new market's depth. Never rejects: on destroy or a failed backfill it resolves with whatever depth loaded. | | `on(event, handler)` | Subscribe to a chart-level event. Returns an unsubscribe function. | | `getVisibleRange()` | The current visible time range (`{ from, to }` in epoch-ms), or `null` before data loads. | | `setVisibleRange(range)` | Set the visible time range explicitly (epoch-ms). Returns the chart for chaining. | | `panBy(fraction)` | Pan by a fraction of the visible width — positive ⇒ toward the latest bars, negative ⇒ into history. Behaves exactly like dragging the chart: constant zoom, the same pan limits (forward stops at the newest candle plus the bounded empty space), eased on renderers that animate pans; repeated calls stack into one continuous scroll. The widget's `Ctrl/Cmd + ←/→` keys use it. Returns the chart for chaining. | | `setVisibleRangePreset(preset)` | Frame a named date range over the loaded bars: `'1D'`, `'1W'`, `'1M'`, `'3M'`, `'6M'`, `'1Y'`, `'5Y'`, `'YTD'`, or `'ALL'`. A preset deeper than the loaded history just frames everything (it doesn't fetch more bars — the widget's range chips do that for you). Returns the chart for chaining. | | `setTheme(theme)` | Swap the app theme at runtime — `'dark'`, `'light'`, or a full theme object (same shape as the `theme` option). Re-skins the chart surface, axes, legends and in-chart chrome live (no indicator re-run) and emits `theme:changed` with the resolved theme so surrounding host chrome can follow. Candle colors are shared across the built-in themes, so a swap never recolors the series. The same switch is available to users in chart settings → Canvas → Theme. Returns the chart for chaining. | | `inspect()` | A renderer-agnostic snapshot of the graphic elements the core has generated (series, fills, drawings, tables, …) — a deterministic check that a feature was produced, independent of which renderer drew it. | | `resize()` | Re-measure the container and relayout. Call after the container's size changes. | | `destroy()` | Tear down the chart, renderer, engines, and subscriptions — no leaks. | Because `registerEngine` returns the chart, you can wire an engine straight off construction: ```js const chart = new Vela('#chart', { data: myBars, timeframe: '1h' }) .registerEngine('pine', new PineEngine()); ``` ### Adding an indicator: sync handle, async data `addIndicator` returns right away so you can wire up UI before any computation finishes. The script is prepared (its inputs are parsed) and then executed over the bar history; the plotted output appears when execution resolves. Listen on the handle's `ready` event (or `chart.ready()` for the whole chart) rather than assuming data is present on return. On a deep-history chart the indicator waits for the background backfill and then computes once over the full depth — its `ready` fires when that single run lands. A minimal end-to-end setup — construct over data, register a scripting engine (here the Pine addon), add an indicator, then await the first render: ```js import { Vela } from '@luxalgo/vela'; import { PineEngine } from '@luxalgo/vela-pinets'; const chart = new Vela('#chart', { data: myBars, timeframe: '1h', theme: 'dark' }); chart.registerEngine('pine', new PineEngine()); // addIndicator returns synchronously — wire UI now; plotted values fill in later. const rsi = chart.addIndicator(`//@version=5 indicator("RSI") plot(ta.rsi(close, input.int(14, "Length")), "RSI", color.purple)`); rsi.on('ready', () => console.log(rsi.title, 'has computed')); await chart.ready(); // resolves once the chart is painted and interactive ``` --- ## `IndicatorHandle` What `addIndicator` returns. Usable immediately. | Member | Description | |---|---| | `id` | This instance's identity on the chart — stable for as long as the indicator stays on it (input edits, hide/show and `updateCode` all keep it). | | `title` | Display title (overridable via the `title` option). | | `source` | The script source the indicator currently runs — the one it was added with, until `updateCode` replaces it; `undefined` for a native indicator. | | `nativeType` | The registered type of a native indicator (`'volume'`, `'sma'`, …); `undefined` for a script indicator. A handle has one of `source` or `nativeType`, never both. | | `inputs` | The inputs parsed from the script source — each with a `key`, `title`, `type`, `defval`, and optional `min`/`max`/`step`/`options`/`group`/`inline`/`tab`/`tooltip`. Populated once the script is prepared. | | `props` | The script's declaration properties (a strategy's `initial_capital`, an indicator's `precision`, …) in the same schema shape as `inputs`. Empty when the engine exposes none. | | `visible` | Whether the indicator is currently shown. | | `setInput(key, value)` | Change one input by its key. Triggers a re-run (an input edit can restructure output, so this may remount the indicator). | | `setInputs(values)` | Change several inputs at once, keyed by input key or title. | | `setProp(key, value)` | Override one declaration property (e.g. `initial_capital`). A prop change replays the whole script. | | `setProps(values)` | Override several declaration properties at once. | | `updateCode(source)` | Replace the script and re-run it **in place** — same `id`, legend row, pane placement and handle. The new source is compiled first; only then is the running script stopped and the new one started, so a broken edit leaves the current indicator computing and painting and reports through `error`. Input and prop values survive where the new script still declares their key; anything else takes the new default. No-op for a native indicator and for an unchanged source. | | `setVisible(visible)` | Hide or show the indicator. Hiding suspends it — its visuals are dropped and its computation stops; showing re-runs it over the current bars. | | `on(event, handler)` | Per-indicator events — `ready`, `error` (`{ error }`), `alert` (`{ id, message, title?, time }`). Returns an unsubscribe function. | | `context(select?)` | `Promise` of a **read-only, serializable snapshot** of the engine's execution context — see [below](#capturing-what-a-script-computes). `null` when the engine lacks the capability or nothing ran yet. | | `remove()` | Remove this indicator from the chart. | The handle is usable the moment `addIndicator` returns; drive the indicator's lifecycle through its events and mutators: ```js const macd = chart.addIndicator(macdSource); // React to the computation outcome. macd.on('ready', () => console.log('inputs:', macd.inputs.map((i) => i.key))); macd.on('error', ({ error }) => console.error('MACD failed:', error.message)); // Retune inputs — each change triggers a re-run. macd.setInput('fast', 8); macd.setInputs({ slow: 21, signal: 5 }); // Swap the code under the same row — an editor's "run my edit". A source that fails // to compile leaves the current MACD running and fires `error` instead. macd.updateCode(editedMacdSource); // Hiding suspends it (visuals dropped, computation stopped); showing re-runs it. macd.setVisible(false); console.log(macd.visible); // false macd.remove(); // drop it from the chart ``` ### Capturing what a script computes Two doors, one vocabulary. `script:run` reports **every** script the chart runs; `runScript()` executes one and hands you its first run. Both deliver the same `ScriptRun` object, so host code reads a manifest indicator, a console `addIndicator`, and an editor's script identically. ```js chart.on('script:run', (run) => { run.title; // 'SMA cross' — the title the script DECLARED run.kind; // 'indicator' | 'strategy' run.cause; // why it computed (see below) run.bar; // index of the last computed bar run.plots.fast; // that plot's value at that bar run.vars.fastLen; // the script's own variables, by their SOURCE names if (run.strategy) { run.strategy.position; // signed contracts held run.strategy.equity; run.strategy.openPnl; run.strategy.netPnl; } }); ``` The handle is already resolved and the data already extracted — there is nothing to look up and nothing to await. #### Provisional vs final `cause` is the field that decides whether a value can be trusted: | `cause` | Fires when | |---|---| | `'history'` | The first computation over the loaded history. | | `'tick'` | The forming bar changed — **values can still move**. | | `'bar'` | A new bar opened, so the one before it is **final**. | | `'inputs'` | An input was edited. | | `'code'` | The script was replaced in place (`handle.updateCode`). | | `'viewport'` | The visible range moved (viewport-aware scripts only). | | `'market'` | The chart's market changed and the script re-executed. | Anything that records, alerts, or exports should key off `'bar'`: ```js chart.on('script:run', (run) => { if (run.cause === 'bar') persist(run); // a bar just closed — settled else updateDashboard(run); // provisional }); ``` `'tick'` runs are **throttled to ~1/s** per indicator (a live stream re-executes the open candle several times a second, which no dashboard can use). Every other cause is emitted unconditionally — dropping a `'bar'` run would break exactly the case above. Two more flags round out the picture: `forming` is true while the run's last bar is still open (always so on a live chart, never on a static one), and `complete` is false only while an engine that computes progressively is still being fed a deep backfill. #### What rides the run, and what you ask for Flat and at the current bar → on the run. Historical and unbounded → an explicit call, so a per-tick listener never ships a ledger it will not read: ```js const trades = await run.trades(); // the strategy's round trips, closed then open const history = await run.series('fast'); // one plot's full history ``` #### Executing a script and capturing its result ```js const result = await chart.runScript(source); if (!result.ok) return console.error(result.error); result.run.strategy.netPnl; const off = result.onUpdate((run) => console.log(run.strategy.openPnl)); result.remove(); // take it off the chart ``` `runScript` resolves on the **first computed run**, injects the script only if it runs (a failure removes it again — no dead legend row), and never rejects. #### Across a grid `@luxalgo/vela/workspace` relays the same event with the cell it came from, so one subscription covers every cell — including cells a later layout change creates: ```js ws.on('script:run', (run) => console.log(run.cell, run.title, run.strategy?.equity)); ``` #### The lower-level snapshot `handle.context(select?)` remains the pull counterpart — a **read-only** deep copy of the engine's execution context (`phase`, `barIndex`, `meta`, `plots`, `variables`, `strategy`, `trades`, `warnings`), async on both the in-process and Web-Worker engines (the context never leaves the worker; only the snapshot crosses), and selective via `select`. `script:run` is built on it and is what host code should normally use; reach for `context()` when you want a snapshot at a moment of *your* choosing rather than at a run. On a `runIndicator` failure the same snapshot is attached post-mortem (`{ ok: false, error, context }`) — the state at the moment of the crash. --- ## Chart-level events Subscribe with `chart.on(event, handler)`; every subscription returns an unsubscribe function. | Event | Payload | Fires when | |---|---|---| | `ready` | — | The chart is painted and interactive (a deep chart's history may still be backfilling). | | `market:changed` | `{ symbol, timeframe, prev: { symbol, timeframe } }` | The market switched **in place** via `setMarket` — symbol (venue prefix included), timeframe, or offline data changed (a depth-only reload does not fire). Fires after the new market's history is painted and every consumer restarted. `prev` lets hosts re-key per-symbol state (e.g. swap user-drawing documents between symbols). | | `load:start` | `{ symbol, timeframe, firstLoad }` | A bar load began with nothing painted: the first load (fires during construction — later subscribers see only its `load:end`), or an identity switch, which blanks the old series in the same breath. Fires **before** the first fetch — plugins and custom indicators hide or reset their own visuals here. A depth-only reload fires neither event. | | `load:end` | `{ symbol, timeframe, bars }` | The load ended: its first bars painted (`bars` > 0 — on deep histories the quick preview), or it ended with none (`bars` = 0 — a failed fetch, an empty market, or a parked symbol). Exactly one per `load:start`; plugins restore or rebuild their visuals here. | | `history:progress` | `{ loaded, target }` | A deep-history backfill chunk landed — `loaded` of `target` bars are on the chart. | | `history:complete` | `{ reason, oldestTime, barsLoaded }` | The history load finished. `reason`: `'depth'` (requested count loaded), `'genesis'` (the source has nothing older), or `'aborted'` (a fetch failed — the chart keeps what loaded). Fires exactly once, including for small/offline charts. | | `indicator:added` | `{ id }` | An indicator was added. | | `indicator:removed` | `{ id }` | An indicator was removed. | | `indicator:error` | `{ id, error }` | An indicator failed. | | `script:run` | the [`ScriptRun`](#capturing-what-a-script-computes) | A script computed — the first pass over the history, a live tick, a new bar, an input edit, a viewport move, a market switch. Carries the run itself (title, cause, the plots/variables/broker state at the computed bar), so a listener reads it instead of resolving a handle and pulling a snapshot. Forming-bar (`'tick'`) runs are throttled to ~1/s; every other cause fires unconditionally. Never fires for native indicators — they run no script. | | `context:changed` | `{ id }` | An indicator's execution context advanced (run finished; throttled to ~1/s while streaming). Re-pull `handle.context()` if you consume it. Fires only for context-capable engines. **Prefer `script:run`**, which delivers the data rather than a signal to go fetch it. | | `bar` | the bar (OHLCV) | A live tick — the forming bar updated or a new bar appended. | | `viewport:changed` | `{ from, to }` (epoch-ms) | The visible time range moved (pan/zoom/fit) — fires per applied change, not debounced. The seam viewport-sync links between charts build on. | | `theme:changed` | the resolved theme object | The app theme changed — `setTheme(...)` or the in-chart settings dialog (Canvas → Theme). Host chrome around the chart (toolbars, panels, page shells) re-skins from the payload. Plot-only cosmetic edits (a `layout.background` set through the config) do **not** fire it. | | `mark:click` | `{ id, ids, time, group? }` | A [timeline mark](#chartmarks--the-timeline-marks-control-surface) glyph was clicked — `ids` lists every mark under it (several marks of one group on one bar fold into a cluster), `id`/`time` its first. Fires before the popup opens; a mark without content opens none, so this is where a host shows its own UI. | | `alert` | engine alert | A script raised an alert. | | `warning` | engine warning | A script raised a warning. | --- ## `chart.renderer` — the renderer control surface A thin facade over the active renderer for reading and changing **how the chart is drawn** at runtime, with **no indicator re-run**. Unsupported keys/methods warn and no-op, so the chart is never left half-changed. | Member | Description | |---|---| | `name` | The active renderer's identity, e.g. `'native'`. | | `capabilities` | What the renderer can draw (drives graceful degradation). | | `supports(feature)` | Whether a feature is available — use to show/hide a UI control. | | `get(feature)` | Read a feature's current value (`undefined` if unsupported). | | `set(feature, value)` / `set({ … })` | Apply one feature, or several at once (one repaint). | | `screenshot()` | Export the chart as a PNG data URL, or `null` if unsupported. Composites the geometry and chrome layers only — the crosshair, DOM overlays (tables, legend), user drawings, and the volume-profile layer are not included. | | `getConfig()` | Snapshot the renderer's full cosmetics as a serializable, versioned JSON document (or `null`). | | `applyConfig(config)` | Apply a full or partial config document from `getConfig()`; malformed/unknown fields are ignored. | | `onConfigChanged(cb)` | Subscribe to cosmetic-config changes — the in-chart settings dialog commits through `applyConfig`, so this is how host chrome mirroring a config value (a time-zone display, a saved template) learns about in-chart edits. Re-pull `get(…)`/`getConfig()` in the callback. Returns an unsubscribe fn; silent no-op unsubscribe on a renderer without a rich config. | | `onCrosshairMove(cb)` | Subscribe to crosshair movement — `time`/`price` under the cursor, per-series values, and the hovered bar's OHLC (null fields when the cursor leaves the chart). Returns an unsubscribe fn. The public seam for host status lines and data windows. | | `dataWindowReadout()` | The bar under the crosshair (or the latest bar when the cursor is off the plot) as a display-ready snapshot: `date`, `time`, an `ohlc` block, and one `groups` entry per indicator with a row per plot in its own color. Values are pre-formatted on their pane's scale. `null` on a renderer without the seam — see [renderer features](./renderer-features.md#data-window-readout). | | `setExternalCrosshair(time, price?)` | Show (or clear, with `null`) a **ghost crosshair** at a data-space position driven from OUTSIDE this chart — the multi-chart crosshair-sync seam ([the workspace](./workspace.md) drives it from the linked cells' pointers). A ghost never re-emits `onCrosshairMove` (one-way by contract — no echo loops). Silent no-op on a renderer without the optional port seam; feature-detect with `supportsExternalCrosshair`. | | `set('dialogHost', el)` | Where the renderer mounts its MODAL dialogs (chart settings, indicator settings). Multi-chart shells pass their root element so dialogs center over the whole grid instead of clipping inside one cell — the workspace does this automatically for every cell. Runtime-only; never part of the config template. | | `supportsExternalCrosshair` (getter) | Whether the active renderer implements the optional `setExternalCrosshair` seam (the native renderer does). | | `setWallClock(clock)` | Drive the renderer's time-of-day chrome (the price-axis countdown to bar close) from the host's own second pulse — a `WallClock` (`SecondClock` is the exported second-aligned implementation) — so it ticks in step with a host clock display instead of on a separate timer that can read a different second. `null` hands the pulse back to the renderer. [The workspace](./workspace.md) does this for every cell with its bottom-bar clock. Silent no-op on a renderer without time-of-day chrome. | | `focus()` | Move keyboard focus back onto the chart's interactive surface — call after a host control (e.g. a shared toolbar button) stole focus, so chart/drawing shortcuts keep working. Silent no-op on a renderer without a focusable surface. | Feature-detect, read, and change how the chart is drawn at runtime — with no indicator re-run: ```js // Only touch a feature the active renderer actually supports. if (chart.renderer.supports('glow')) { console.log('glow is', chart.renderer.get('glow')); chart.renderer.set('glow', 0.6); } // Apply several features at once (one repaint). chart.renderer.set({ logScale: true, currentPriceLine: false }); ``` See [renderer-features.md](./renderer-features.md) for the full feature catalog (common + native-specific) and config/screenshot examples. --- ## `chart.data` — the data control surface A facade over the provider registry for **where candles come from**. Register one or more market-data providers; the chart routes each symbol to the right one. No provider is bundled — registering the one that resolves the chart symbol fires the parked initial load. | Member | Description | |---|---| | `registerProvider(name, provider)` | Register (or replace) a provider; chainable. Fires the parked load when it resolves the symbol. | | `unregisterProvider(name)` | Remove a provider. | | `providers()` | Metadata for every registered provider. | | `resolve(symbol)` | How a symbol routes now (`{ provider, ticker }`, or `null`). Accepts a provider-name prefix or a descriptor-declared **listing prefix** (`NASDAQ:AAPL` — strict: a wrong venue resolves to `null`). | | `displayPrefix(symbol)` | The venue label to display — the descriptor's listing prefix when declared, else the resolved provider name. `null` while unresolvable. | | `canonicalSymbol(symbol)` | The canonical `PREFIX:TICKER` form of the symbol (`edgx:aapl` → `NASDAQ:AAPL`). `null` while unresolvable. | | `symbols(provider?)` | Indexed symbols for autocomplete — one provider, or all. | | `symbolInfo(symbol)` | `Promise` of per-symbol metadata (Pine `syminfo.*`), via the owning provider. | | `capabilities(symbol)` | The full resolved per-symbol `ProviderCapabilities` (behavior flags), or `null` while nothing resolves the symbol. | | `ready()` | Resolves when every provider's symbol index has settled. | Register the provider that resolves the chart symbol, then wait for both the index and the initial load: ```js import { Vela } from '@luxalgo/vela'; import { BinanceProvider } from '@luxalgo/vela/providers/binance'; const chart = new Vela('#chart', { symbol: 'BTCUSDT', timeframe: '1h' }); // Registering the provider that resolves the symbol fires the parked initial load. chart.data.registerProvider('binance', new BinanceProvider()); await chart.data.ready(); // provider symbol indexes settled await chart.ready(); // chart painted and interactive console.log(chart.data.resolve('BTCUSDT')); // { provider: 'binance', ticker: 'BTCUSDT' } console.log(chart.data.symbols('binance').length, 'symbols indexed'); console.log(chart.data.capabilities('BTCUSDT')); // ProviderCapabilities | null ``` On a chart given a custom `deps.dataFeed`, these warn and no-op (that feed owns its data). See [data-providers.md](./data-providers.md) for symbol formats, resolution, and the load lifecycle. --- ## `chart.drawings` — the drawings control surface A chainable facade over the interactive [drawing tools](./drawing-tools.md). The drawing **model is core-owned**, so the *interactive* methods are capability-gated (they warn + no-op when the renderer can't paint drawings — `chart.drawings.supported` reports this), while the **model methods** (reading, persisting, undo) always work. | Member | Gated? | Description | |---|---|---| | `supported` | — | Whether the renderer can paint interactive drawings. | | `setTool(type \| null)` | yes | Arm a tool for the next clicks; `null` returns to select/idle. | | `getTool()` | no | The armed tool (`null` = select/idle). Follow changes on `drawing:tool`. | | `setSnapMode(mode)` · `getSnapMode()` | yes / no | The magnet: `'off' \| 'weak' \| 'strong'`. Changes land on `drawing:snap`. | | `setStayMode(on)` · `getStayMode()` | yes / no | Stay in drawing mode: keep the tool armed after each placement. Changes land on `drawing:stay`. | | `setMode(mode)` · `getMode()` | yes / no | Renderer-local mode: `'measure' \| 'eraser' \| null`. Mutually exclusive with armed tools (the renderer enforces it); changes land on `drawing:mode`. | | `showToolbar(visible?)` | yes | Show/hide the on-chart toolbar. | | `setToolbar(option)` | yes | Reconfigure the toolbar groups/tools live. | | `setToolShortcuts(map)` | yes | Show per-tool shortcut hints in the toolbar flyouts — `{ trendline: 'Alt+T', … }`. Values are pre-formatted display strings: the host owns the keymap and the platform formatting. The widget/workspace push their own bindings automatically. | | `add(type, init?)` | yes | Create a drawing from code; returns the `Drawing` (or `null` if unsupported). | | `remove(id)` | no | Delete a drawing. | | `update(id, patch)` | no | Apply a partial serialized record (for a custom settings UI). | | `lock(id, v?)` · `show(id, v?)` | no | Lock/unlock · show/hide a single drawing. | | `bringToFront(id)` · `sendToBack(id)` | no | Reorder paint order. With the `drawingDepth` capability they clear the whole stack — candles and indicators included, not just the other drawings. | | `zIndex` (on `add`'s init and `update`'s patch) | no | The draw-order key. On a `drawingDepth` renderer it shares one space with the pane's series, so a drawing can sit under the candles or between two indicators — see [depth](./drawing-tools.md#depth-anywhere-in-the-stack). Persists with the drawing either way. | | `undo()` · `redo()` · `canUndo()` · `canRedo()` | no | Snapshot history (core-owned). | | `clone(id)` · `duplicate(ids)` | yes | Copy in place; the copies become the selection. | | `copyToClipboard(ids)` · `paste()` | yes | In-memory, per-chart clipboard. | | `all()` | no | Every drawing as plain JSON, in paint order. | | `toJSON()` / `fromJSON(doc)` | no | Snapshot / restore a versioned `DrawingsDocument` (untrusted-safe). | | `getConfig()` / `applyConfig(doc)` | no | Aliases of `toJSON` / `fromJSON`, mirroring `chart.renderer`. | Drawing lifecycle is also surfaced as chart events (`drawing:created` / `drawing:edited` / `drawing:removed` / `drawing:selected` / `drawing:settings` — `drawing:selected` carries the primary `id` plus `ids`, every member of a multi-selection), and the tool/mode state as `drawing:tool` / `drawing:snap` / `drawing:stay` / `drawing:mode` — the seam an external toolbar mirrors. See [Drawing tools](./drawing-tools.md) for the tool catalogue, toolbar UX, and keyboard shortcuts. --- ## `chart.marks` — the timeline-marks control surface Host events pinned to a moment in time — dividends, splits, earnings, releases — shown as small colored **glyphs on a lane just above the time axis**, each opening a **popup** on click. The model is core-owned, so every method works on any renderer; on one without the `timelineMarks` capability nothing paints (`supported` reports it). Marks are **data, not user state**: the chart never persists them — re-supply them on `market:changed`. What *is* saved with the chart's config is each group's visibility (the checkboxes on the chart settings' **Events** tab, which appears once marks name groups). The [Timeline marks](./timeline-marks.md) guide walks through the whole surface with examples. | Member | Description | |---|---| | `supported` | Whether the active renderer paints timeline marks. | | `add(mark)` | Add one mark; an existing id is replaced in place. Chainable. | | `set(marks)` | Replace the whole set (a market switch). | | `remove(id)` · `clear()` | Drop one mark, or all of them. | | `all()` | Every mark, in insertion order. | | `defineGroup({ id, label, visible? })` | Name a visibility group: the label of its checkbox in settings and its default visibility. A mark may name a group that was never defined — it then shows its capitalized id. | | `groups()` | The defined groups, in definition order. | | `setGroupVisible(id, visible?)` · `isGroupVisible(id)` | The same switch as the settings checkbox; the choice is persisted with the chart's config. On a renderer without the `marks` feature the setter warns and no-ops. | A mark is `{ id, time, glyph, title?, tooltip?, group?, content? }`: - **`time`** is epoch-ms, but the glyph never sits at that exact pixel: it centers on **the bar whose span contains the time**. A time in a gap (a weekend, a closed session) goes to the first bar that follows; one past the newest bar projects onto the extrapolated grid in the right whitespace. Change timeframe and the marks re-snap. - **`glyph`** is a colored token — `{ shape?, color, letter? }` (`'circle'` · `'square'` · `'diamond'` · `'pin'`, one or two characters in auto-contrasted ink) or `{ shape?, color, icon }` with an id from the icon registry (`registerIcon` from `vela/ui`). Icons travel as ids, never inline markup, so a mark payload can come from data. - **`content`** is what the popup shows under `title`: `{ text }` (escaped), `{ html }` (sanitized through an allowlist — scripts, styles, frames, event handlers and `javascript:` URLs never reach the page; links open in a new tab), or `{ panel: { items } }` — rows of `text`, `field` (label/value) and `button` (`run`, optional `primary`, `close`). Pass a **function** (sync or async) instead and it resolves when the popup needs it — details fetched on click, one entry at a time as the user scrolls. Without `content` a click only emits `mark:click`. - **`group`** folds marks of one kind that land on the same bar into **one slightly larger glyph** listing them all (earliest first); marks of *different* groups on one bar **stack** — a small deck that fans out on hover (or a tap on touch) so each can be opened. ```js import { registerIcon, svg16 } from '@luxalgo/vela/ui'; registerIcon('myco.split', svg16('')); chart.marks .defineGroup({ id: 'dividends', label: 'Dividends' }) .defineGroup({ id: 'splits', label: 'Splits' }) .add({ id: 'div-2024-06-11', time: Date.UTC(2024, 5, 11), title: 'Dividends', group: 'dividends', glyph: { shape: 'circle', color: '#2962ff', letter: 'D' }, tooltip: 'Dividend · $0.01', content: { panel: { items: [ { type: 'field', label: 'Ex-dividend date', value: "Tue 11 Jun '24" }, { type: 'field', label: 'Amount', value: '0.01' }, { type: 'button', label: 'More NVDA dividends', primary: true, run: () => openDividends('NVDA') }, ], }, }, }) .add({ id: 'split-2024-06-10', time: Date.UTC(2024, 5, 10), title: 'Stock split', group: 'splits', glyph: { icon: 'myco.split', color: '#ff9800' }, content: async () => ({ html: await fetchSplitDetails('NVDA') }), }); chart.on('mark:click', ({ id, ids, time }) => { /* ids = every mark under the clicked glyph */ }); chart.on('market:changed', ({ symbol }) => chart.marks.set(marksFor(symbol))); chart.renderer.set('marks', false); // hide the whole lane (`{ groups: { splits: false } }` hides one group) ``` --- ## `deps` — the swap point The optional third constructor argument is where you replace a layer's default with your own implementation of that layer's port: | Key | Replaces | Guide | |---|---|---| | `renderer` | The drawing/output layer. Injects an already-constructed renderer *instance*, bypassing the `renderer` option's display-options wiring (a different axis from built-in vs custom — `options.renderer` already accepts any custom class too). | [Adding a renderer](../contributing/adding-a-renderer.md) | | `engines` | Scripting engines to register at construction (bulk form of `registerEngine`). | [Scripting engines](./scripting-engines.md) · [Adding an engine](../contributing/adding-an-engine.md) | | `dataFeed` | The market-data source. Replaces the default provider registry entirely with your own `MarketDataFeed` (used bare — no registry, no auto-cache). | [Adding a data provider](../contributing/adding-a-data-provider.md) | Each layer is one narrow port — implement it, declare its honest capabilities, and inject it here. The composition root is the only place that imports concrete backends. > The three *adding-a-backend* guides are **Contributing** docs and are still being written. Until they land, no link points at them — see the Contributing section of the [docs index](../index.md). > **Stability:** The API is still stabilizing and will evolve as the library develops; pin a source revision if you need a fixed surface.