Skip to content

Getting started ​

charton ships as eight packages:

PackageUse it for
@chart-on/coreThe chart engine. Canvases only, no UI chrome. Bring your own controls.
@chart-on/uiOptional shell: top toolbar, drawing-tool rail and styles around a core chart.
@chart-on/indicatorsOptional built-in indicators.
@chart-on/taOptional technical-analysis helpers for writing indicators.
@chart-on/localesOptional translations of the built-in text, one entry per language, applied through the localization() plugin.
@chart-on/reactOptional components and hooks for apps built with a component-based UI framework.
@chart-on/vueOptional components and composables for apps built with a template-based component framework.
@chart-on/svelteOptional components and an action for apps built with a compiler-based component framework.

Install ​

Install the engine, and the UI shell if you want it:

bash
pnpm add @chart-on/core
# optional UI shell
pnpm add @chart-on/ui

Use the same version for every @chart-on/* package (versioning). Requires a browser with WebGL2. No bundler? Use the script-tag build.

Core only ​

The container needs a size. The chart fills it and follows resizes automatically.

html
<div id="chart" style="height: 480px"></div>

Every example in these docs is a module whose mount(el) draws into an element and returns a clean-up function; in your app, el is that #chart element. The chart below runs exactly the code under it. sampleBars makes up sample data in the same shape as yours:

ts
import { createChart } from '@chart-on/core';
import type { ExampleContext } from './types';
import { sampleBars } from './sample-data';

export default function mount(el: HTMLElement, { theme }: ExampleContext) {
  const chart = createChart(el, { theme });
  const candles = chart.addCandles({ title: 'SAMPLE' });

  // Bars are { time (unix seconds), open, high, low, close, volume? }.
  candles.setData(sampleBars());
  chart.fitContent();

  return () => chart.destroy();
}

With the UI shell ​

The shell adds a small toolbar (interval, chart style, indicators, price scale, settings and fullscreen) and the drawing-tool rail by default; a bottom bar is opt-in (bottomBar: true). theme picks the 'dark', 'light' or 'system' scheme (default 'system'). See Shell, toolbar and rail.

ts
import { createChartShell } from '@chart-on/ui';
import '@chart-on/ui/style.css';
import type { ExampleContext } from './types';
import { sampleBars } from './sample-data';

export default function mount(el: HTMLElement, { dark }: ExampleContext) {
  // Toolbar, drawing-tool rail and bottom bar around the chart.
  const shell = createChartShell(el, { theme: dark ? 'dark' : 'light' });
  shell.chart.addCandles({ title: 'SAMPLE' }).setData(sampleBars());
  shell.chart.fitContent();

  return () => shell.destroy();
}

Live updates ​

ts
candles.update({ time: 1767229200, open: 103, high: 106, low: 101, close: 105 }); // same time: replace
candles.update({ time: 1767232800, open: 105, high: 107, low: 104, close: 106 }); // newer: append

Clean up ​

ts
chart.destroy(); // or shell.destroy() when using the UI shell

destroy() is safe to call twice. Code that still holds the chart afterwards (a live update, a late data response) does no harm: nothing is drawn; view changes (scrollToRealtime, fitContent, setVisibleTimeRange, resetView), applyOptions and removing series do nothing; chart.data is null; captureImage resolves to null. Calls that load data fail loudly instead: setSymbol rejects and compare throws a ChartError with code 'destroyed', and a setSymbol still loading when the chart is destroyed rejects the same way.

Requirements and failure modes ​

The chart draws its series with WebGL2, which every current desktop and mobile browser supports. There is no fallback renderer.

Supported browsers: Chrome and Edge 111+, Firefox 114+, Safari 16.4+ (macOS and iOS). WebGL2 alone needs less (Safari has it from 15), but the packages are compiled for this set, so older browsers fail on the JavaScript before they reach WebGL. The list is the browserslist field of each package's package.json, so tools that read it (autoprefixers, transpilers) pick it up. Node.js 22+ is needed only for tooling and tests, for example @chart-on/core/testing.

Check before creating a chart if you want to show your own message:

ts
import { createChart, isWebGL2Supported, ChartError } from '@chart-on/core';

if (!isWebGL2Supported()) showMessage('This chart needs WebGL2.');

try {
  createChart(el);
} catch (error) {
  if (error instanceof ChartError) console.warn(error.code); // 'webgl2-unavailable' or 'webgl-shader'
}
  • Without WebGL2, createChart throws a ChartError with code 'webgl2-unavailable' and leaves the container untouched. A shader that fails to compile or link (usually a driver bug) throws code 'webgl-shader', also leaving the container untouched. createChartShell removes its markup and rethrows the same error, so wrap it the same way and render your own fallback.
  • The browser can take the GPU context away, for example after a driver reset or when a page opens too many contexts. The chart emits context:lost. The frame loop keeps running: axes, labels, the crosshair and drawings keep drawing and responding, and only the GPU-drawn series are skipped, so they disappear until the browser restores the context. On context:restored the chart re-creates its GPU buffers and textures from the series data and redraws. Nothing you set is lost. Both events carry reason: 'browser' here, 'budget' when the chart gave the context up itself (next point).
  • Browsers cap live WebGL contexts per page (commonly 16 on desktop and 8 on mobile) and drop the oldest past that, so a page with many charts would blank its first ones. Each chart holds one context, and the charts share a page budget: 12 contexts, or 6 with a coarse pointer. Past it, the chart that has been out of view the longest gives its context up (context:lost with reason: 'budget'), or the least recently used one when every chart is in view. It takes a context back, evicting another, when it scrolls into view or gets a pointer, wheel or focus event. Set the budget with setContextBudget(n), read it with getContextBudget(); lowering it evicts at once. Leave room for other WebGL content on the page, such as maps or 3D views.
  • If the restored context cannot be set up again, context:restore-failed fires with a ChartError (code 'webgl-restore-failed', the original error in cause) and the series stay blank. Destroy the chart and create a new one to recover.
  • While the context is lost, captureImage() resolves to null; other methods keep working.

Using the docs with coding assistants ​

Besides these pages, the docs site publishes plain-text copies that coding assistants read more reliably than HTML:

  • /llms.txt: an index of every guide page, with its URL and a one-line description.
  • /llms-full.txt: every guide page in one Markdown file, in sidebar order. Live examples appear as their source code.
  • Each page as Markdown at the same path with .md, such as /guide/series.md. The link sits at the bottom of each page.

Point your assistant at the index, or at the full text when it can read a long file.

Next ​