# fcharts > A dependency-free TypeScript charting library that renders 100k+ data points at 60fps on a > `` while staying fully accessible — keyboard-navigable, screen-reader-announced, > find-in-page-able — through a real-DOM overlay. Because the data lives in the DOM (not just > canvas pixels), the same layer makes charts **machine-readable to AI agents**. Web platform > only (browsers); ~21 KB gzip for the tree-shaken canvas core (~39 KB with everything > included), zero runtime dependencies. npm package: `fcharts-js`. This file is everything an agent needs to build on fcharts. Source of truth: the public API below. Chart family today: time-series **line + area + candle** (multiple series, live streaming, linked panes, time/log axes) on canvas, plus server-safe **SVG primitives** (donut, scatter, sparkline, bars, progress, heatmap) and **React / Vue / Svelte / web component** adapters, SSR hydration, and a no-code render CLI. ## Install ```sh npm install fcharts-js ``` ```html ``` ## Minimal usage ```ts import { FChart } from 'fcharts-js'; // The container must have a non-zero size (e.g. width:100%; height:420px). Styles auto-inject. const chart = new FChart(document.getElementById('chart'), { series: [ { name: 'Price', color: '#16a34a', type: 'area' }, { name: 'VWAP', color: '#d97706' }, ], options: { ariaLabel: 'Price vs VWAP', xLabel: 'time', yLabel: 'value' }, }); // x is shared and non-decreasing; one y per series, each the same length as x. const N = 100_000; const x = Float64Array.from({ length: N }, (_, i) => i); chart.setData({ x, y: [seriesA, seriesB] }); // seriesA/B: Float64Array | number[] of length N ``` ## Public API ```ts new FChart(el: HTMLElement, config: FChartConfig) interface FChartConfig { series: SeriesConfig[]; data?: FChartData; // optional; or call setData later options?: FChartOptions; annotations?: AnnotationSpec[]; // event/point markers, announced + agent-readable } interface SeriesConfig { name: string; // announced to SR, shown in legend/tooltip color: string; // CSS color type?: 'line' | 'area' | 'candle'; // default 'line' visible?: boolean; // default true width?: number; // stroke px, default 1.25 fillAlpha?: number; // area fill 0..1, default 0.15 upColor?: string; // candle body, close >= open (default palette green) downColor?: string; // candle body, close < open (default palette red) } interface FChartData { x: Float64Array | readonly number[]; // shared, NON-DECREASING, finite y: (Float64Array | readonly number[])[]; // one per series slot (candle series take FOUR // arrays — open, high, low, close), each length // === x.length, finite } interface FChartOptions { ariaLabel?: string; // accessible chart name (also the summary label) xLabel?: string; yLabel?: string; legend?: boolean; // default true xType?: 'linear' | 'time'; // 'time': x = epoch ms, calendar-boundary ticks + date labels yScale?: 'linear' | 'log'; // 'log': base-10; needs positive data (<=0 clamps to bottom) exportControl?: boolean; // visible "Download data (CSV)" button; default false maxDpr?: number; // device-pixel-ratio cap, default 2 yPadding?: number; // y-extent padding fraction, default 0.06 xInteger?: boolean; // integer-stepped x ticks, default false xTickCount?: number; // default 8 yTickCount?: number; // default 6 locale?: string; // BCP-47 tag: Intl-localized DEFAULT formatters (dates, // month names, decimal separators); formatX/formatY win formatX?: (v: number) => string; // tick + readout x formatter formatY?: (v: number) => string; // tick + readout y formatter reducedMotion?: boolean; // else auto from prefers-reduced-motion highContrast?: boolean; // else auto from prefers-contrast strings?: Partial; // localize fixed UI text (legend, keyboard help, // data summary, table caption) for non-English pages } // Methods (setData/update/renderSync return `this` for chaining) chart.setData(data: FChartData): FChart // replace data, reset view to full x-domain chart.append(x: number, ys: number[]): FChart // append one sample, amortized O(1) — streaming chart.amendLast(ys: number[]): FChart // rewrite the last sample in place — forming candles chart.update(patch: Partial): FChart // patch series/options/data in place chart.renderSync(domain?: readonly [number, number]): FChart // synchronous render / programmatic zoom chart.summary(): ChartSummary // structured machine-readable summary (see below) chart.toCSV(): string // full dataset as CSV (candles → 4 OHLC columns) chart.onDomainChange(cb): () => void // x-domain change subscription (returns unsubscribe) chart.destroy(): void // remove DOM, listeners, observers, timers chart.renderPath: 'dom-overlay' | 'html-in-canvas' // getter; DOM-overlay is the default, no-flags path chart.htmlInCanvas: { supported: boolean; via: string | null } // getter interface ChartSummary { label: string; points: number; xStart: number; xEnd: number; series: SeriesSummary[]; annotations?: AnnotationSummary[]; // present when the chart has annotations } interface SeriesSummary { name: string; visible: boolean; min: number; max: number; first: number; last: number; mean: number; changeAbs: number; // last - first changePct: number; // percent change vs first (range-relative when first is 0) trend: 'up' | 'down' | 'flat'; // derived from changePct; <1% is flat } ``` ## Real-time / streaming `append(x, ys)` adds one sample without rebuilding anything (only the pyramid tail updates), so cost stays flat as the series grows. `x` must be >= the current last x; pass one y per series slot. `amendLast(ys)` rewrites the last sample in place (forming candle bucket). The view follows the live tail when already showing it; a user panned back into history stays put. The library never auto-updates on its own — if you drive `append` on a timer, provide a Pause/Stop control and respect `prefers-reduced-motion` (WCAG 2.2.2). ## Agent-readable: how an AI agent reads the data A canvas chart is opaque pixels. fcharts puts the data in the DOM, so an agent can read it three ways (all inside the chart container): 1. **JS, if you hold the instance:** `chart.summary()` → `ChartSummary`. 2. **Embedded JSON, from the DOM:** `JSON.parse(container.querySelector('script[data-fcharts]').textContent)` → the same `ChartSummary`. 3. **Accessibility tree:** the focusable `[role="application"]` surface has `aria-describedby` pointing to a one-line natural-language summary (values + trend), `aria-details` pointing to a hidden `` of downsampled rows, and an `[aria-live="polite"]` region that announces the focused sample. Axis ticks are real text inside `.fc-ticks`. To navigate values programmatically: focus the surface, dispatch ArrowLeft/Right/Up/Down/Home/End, and read the `[aria-live]` element's textContent after each step. Static SVGs produced by the primitives below embed the same summary as `