A small, fast, embeddable charting library for financial and quantitative visualisations. Canvas 2D rendering, typed-array buffers, layered redraw — built to stay smooth at 100k+ bars.
It ships in three layers, each usable on its own:
| Package | What you get |
|---|---|
@finterion/charts-core |
Framework-agnostic Canvas engine. Multi-panel, pan/zoom, indicators, markers, nine panel kinds including donut and box plots. |
@finterion/charts-react |
<Chart> and <ChartFromSpec> React bindings. |
@finterion/charts-spec |
JSON-only ChartSpec schema + validator + compiler. Designed for LLM tool-calling and forum embeds. |
Plus two apps:
| App | What it does |
|---|---|
apps/playground |
Vite playground with a SuperTrend live-coding demo and a portfolio tearsheet. pnpm dev. |
apps/embed |
Static iframe page that renders any ChartSpec from ?src=, #spec= or postMessage. |
The vertical crosshair snaps to the nearest bar center. Hover, pan, and cursor-anchored zoom use the same canvas width in both standalone and embedded charts.
Drag normally to pan the shared time axis. Shift + drag inside a price or indicator panel draws a dashed selection box: release to zoom the time axis to that region and that panel's Y-axis to the selected values. Dragging in either direction works; the selection stays within the starting panel. Press Escape to cancel. Tiny selections (less than 4 pixels on either axis) do nothing, and time zoom retains a minimum span of 10 bars where available.
Scroll over the Y-axis labels to zoom only that panel's vertical range,
without Ctrl/Cmd and without changing the time axis or scrolling the page.
Existing Y-axis dragging and double-click autoscale reset remain available;
reset also clears a box-selected vertical range.
Normal wheel behavior over the plot is unchanged, including Ctrl/Cmd zoom
when panels overflow. These gestures are disabled by interactive: false;
box zoom is unavailable while Compare points is active.
Decision annotations can set signalType to scale_in, scale_out,
stop_loss, or take_profit. Scale decisions use + and - symbols.
Protective exits use SL/TP badges: eventType: 'trigger' is hollow and
eventType: 'fill' is solid, with separate legend filters and inspector cards.
Simultaneous triggers and fills are offset left/right so both can be selected.
Existing annotations without these optional fields retain their circle markers.
Trade markers also accept shape: 'plus' and shape: 'minus'.
The left-hand vertical toolbar contains X axis placement, Compare points,
Panel visibility, and Enter fullscreen
icon buttons, plus Zoom out to all data, Indicator outputs, and Capture chart. The zoom-out button shows the full
loaded history across every synchronized panel and resets their vertical zoom
to fit the data, including any box-selected ranges. Panel visibility, comparison
points, and selected tools are preserved. It is disabled until data is loaded
and on charts with interactive: false.
Hover a button for its tooltip; active tools are highlighted.
Use Tab to focus the tools and Up/Down arrows to move between them.
Use Hide toolbar at the bottom of the sidebar to reclaim the chart space;
the small Show toolbar tab on the left edge brings it back. Hiding the
toolbar preserves active tools and selected comparison points.
X axis placement opens a picker for the shared time-axis location.
Panel visibility opens a checkbox picker for showing or collapsing multiple
panels together. Show all and Hide all change the selection; Apply
confirms it. Cancel, Escape, or an outside click discards changes.
Collapsed panels retain their title rows, just like their eye controls;
non-collapsible panels remain visible. Matching panels retain their collapsed
state when their definitions are refreshed.
Enter fullscreen expands this chart, including its toolbar and inspectors, using the browser Fullscreen API. Use Exit fullscreen or Escape to return. The host page must permit fullscreen for embedded charts; if the browser or notebook host denies it, the chart displays an explanation without losing state.
Capture chart opens a choice of Copy image or Download PNG.
Copy image places the PNG on the clipboard without saving a file; nothing is
downloaded automatically. It requires a secure context (HTTPS or localhost),
image-clipboard support, and browser/host permission. A denied or unsupported
clipboard operation shows an error and never silently downloads instead.
Download PNG saves finterion-chart.png with the current chart-area
viewport, at the display's pixel ratio. It includes rendered panels, titles,
axes, legends, visible pinned outputs, markers, and branding, but excludes the
toolbar, dialogs, and inspectors. Collapsed panels and scroll-clipped content
remain as displayed; it does not change zoom, visibility, or selections.
The button is available on non-interactive charts too. Capture and encoding
failures are shown on the chart; notebook hosts may require download permission.
Sandboxed inline notebook iframes can block capture or downloads. In that case,
export to a standalone HTML chart or use the browser's screenshot tools;
the capture action does not relax the notebook's security sandbox.
Custom cross-origin branding images require CORS permission.
For programmatic use, await chart.captureImage() returns a PNG Blob without
starting a download; await chart.copyImage() copies it to the clipboard.
Call clipboard copying directly from a user gesture such as a button click.
The HTML rasterizer is loaded on demand; the Python offline
embed includes it in its self-contained bundle.
Indicator outputs enables click-to-pin inspection. Click a candle in an
indicator pane (including price panes with indicator overlays) to show its outputs
beside the title. Another click replaces the selected pane/candle; moving or
leaving the chart does not change the values. The readout includes its timestamp,
keeps hidden outputs available, and scrolls horizontally so every value remains
accessible. On narrow panes it moves below the title. Missing values show n/a.
Turn the tool off to clear. Comparison and output inspection are mutually exclusive;
pan/zoom and visibility controls retain their normal behavior. Replacing the data
clears the pin; refreshing matching panel definitions keeps it.
For raw classifications or outputs not drawn as lines, supply PanelSpec.outputs
as { id, label, values: Float64Array } entries aligned to the OHLC buffer.
Without this field the tool uses plotted indicator series (including hidden
series and both band bounds, excluding showHoverValue: false decorations).
JSON specs accept inline arrays or column references in panels[].outputs;
Python builders expose IndicatorOutput and a keyword-only panel outputs list.
The runtime API also exposes chart.setOutputInspection(true | false).
Hosts can supply PanelSpec.outputSourceTime and outputAvailableAt as
OHLC-aligned Float64Array timestamps in milliseconds. These label the pinned
readout's source candle and availability time separately from the clicked
chart timestamp. JSON specs accept inline arrays or column references for both;
Python panels use keyword-only output_source_time and output_available_at.
The host is responsible for sampling only completed candles, retaining genuine
missing values, and keeping these metadata arrays aligned with its output values.
Click Compare points in that toolbar, then click timestamps in any price or indicator panel. Each click adds a numbered point across the shared time panels; clicking a selected timestamp again removes it. The right-hand side panel compares OHLCV, indicators, overlays and both bounds of bands, including hidden series. Add as many points as needed and scroll the table horizontally to compare them. Drag the comparison panel's left divider to widen or narrow it. Focus the divider and use Left/Right (Shift for larger steps), Home/End for minimum/maximum width, or double-click to restore the default. The width is retained when points change or the panel is closed and reopened. It can use the available chart width while reserving space for the plot; tables wider than the panel still scroll horizontally.
The first selected point is the baseline. Subsequent columns show the value,
absolute change and percentage change (100 * (value - baseline) / abs(baseline)).
A zero baseline has no percentage change; missing/non-finite values show N/A.
Removing the baseline makes the next selected point the baseline.
Non-time panels (such as scatter plots and heatmaps) are explicitly marked as
not timestamp-comparable.
Use Remove or Clear all to manage points. Close (or the toolbar button) returns to normal chart navigation and the decision inspector; reopening Compare restores the selection. Points stay anchored to timestamps during pan/zoom and data updates; timestamps removed from the data are discarded. Comparison is available in React, JSON-spec embeds and Python's bundled offline renderer without extra configuration, and is disabled for non-interactive charts.
# npm
npm install @finterion/charts-core @finterion/charts-react @finterion/charts-spec
# pnpm
pnpm add @finterion/charts-core @finterion/charts-react @finterion/charts-spec| Package | npm |
|---|---|
@finterion/charts-core |
|
@finterion/charts-react |
|
@finterion/charts-spec |
pip install finterion-chartsimport { Chart } from '@finterion/charts-react';
<Chart
data={ohlc}
panels={[{ id: 'price', kind: 'price', weight: 1, type: 'candles' }]}
theme="finterion-dark"
background="#131722"
gridColor="#494d57"
/>import { ChartFromSpec } from '@finterion/charts-react';
<ChartFromSpec spec={{
version: 1,
data: { bars: { time, open, high, low, close } },
display: { theme: 'finterion-dark', background: '#131722', gridColor: '#494d57' },
panels: [
{ id: 'price', kind: 'price', weight: 4, type: 'candles', title: 'AAPL' },
{
id: 'rsi', kind: 'indicator', weight: 1, title: 'RSI 14',
indicator: {
values: rsiArray, kind: 'line', color: '#a3ff12',
refLines: [30, 70], yRange: [0, 100],
},
},
],
}} />import { encodeSpec } from '@finterion/charts-spec';
const url = `https://charts.finterion.com/embed/#spec=${encodeSpec(spec)}`;
// <iframe src={url} width="640" height="320" loading="lazy" />packages/
core/ canvas engine, panels, renderers, themes
react/ <Chart>, <ChartFromSpec>
spec/ ChartSpec types, validator, compiler, JSON Schema, base64 codec
apps/
playground/ Vite demo (SuperTrend, portfolio tearsheet)
embed/ Static iframe renderer
docs/
overview.md, panels.md, spec.md, llm-prompt.md, embedding.md
scripts/
generate_supertrend.py sample data generator
pnpm install
pnpm dev # playground at http://localhost:5173
pnpm build # build all packages via turboPer-package:
pnpm --filter @finterion/charts-core build
pnpm --filter @finterion/charts-react build
pnpm --filter @finterion/charts-spec build
pnpm --filter @finterion/charts-embed-app dev # iframe app at :5174Typecheck without emit:
cd apps/playground && pnpm exec tsc --noEmitVersioning is managed with Changesets.
-
After making a change, add a changeset:
pnpm changeset
Pick the affected packages and a
patch/minor/majorbump. Commit the resulting markdown file in.changeset/. -
When merged to
main, thereleaseworkflow opens a "Version Packages" PR that bumps versions and updatesCHANGELOG.md. -
Merging that PR triggers
pnpm release, which builds and stages npm tarballs usingnpm stage publish. Published or already-staged versions are skipped so reruns can safely resume a partial release. Tarballs are packed with pnpm so internalworkspace:*dependencies become actual versions. -
A maintainer reviews the stage IDs in the workflow summary and approves each package from a local terminal with npm 11.21.1 or newer:
npm login npm stage list @finterion/charts-core npm stage list @finterion/charts-spec npm stage list @finterion/charts-react npm stage view <stage-id> npm stage approve <stage-id>
Approval prompts for 2FA. Approve core, then spec, then react so dependencies are available first. A successful staging workflow does not mean the packages are publicly available; it does not create published GitHub releases or git tags. Verify all three versions after approval with
npm view <package>@<version> version.
The workflow can also be started manually with Run workflow after a token update or to resume staging. Rerunning an older run uses its old workflow code; start a new run after merging workflow changes.
The three publishable packages (@finterion/charts-core, @finterion/charts-react,
@finterion/charts-spec) are versioned together (linked); the playground and
embed apps are private and never published.
- Add
NPM_TOKEN(a stage-only token with read/write access to all three packages) to the repo's GitHub Actions secrets. No 2FA bypass is needed. - Ensure the
@finterionnpm org exists and the token owner has package write access. Approving maintainers must have 2FA enabled on their npm accounts. - The first time the workflow runs, it stages the versions in
package.jsonwithout requiring an initial changeset. - Local
pnpm releasealso requires npm 11.21.1 or newer and npm authentication. See npm's staged publishing documentation.
- Overview — architecture, performance model, when to use what.
- Panel kinds —
price,indicator,heatmap,hbar,vbar,histogram,scatter,donut,boxplot. - ChartSpec — the JSON schema, format directives, column references.
- LLM prompt template — system prompt + worked examples for agents.
- Embedding — iframe, postMessage, base64 share links, markdown shortcodes.
- Canvas-only. SVG falls over past a few thousand nodes; Canvas handles hundreds of thousands.
- Typed arrays. OHLC stored columnar in
Float32Array/Float64Array. Cache-friendly, ~4× lower memory than object arrays. - Layered canvases. Per panel: base (grid), series (data), overlay (crosshair/tooltip). Hover only repaints the overlay.
- Viewport culling. Iterate only visible bars; pan/zoom is O(viewport), not O(history).
- Two surfaces. A typed imperative API (
@finterion/charts-core) and a JSON declarative API (@finterion/charts-spec). The JSON form is what LLMs and forum embeds use; it compiles to the typed form. - Tree-shakeable.
sideEffects: false, ESM-first.
Finterion Charts is released under the Finterion Community Licence v1.0 — a source-available licence (not OSI-approved "open source").
Free for everyone, with one condition:
- Use it in any project, including commercial products and internal tools
- Fork it, modify it, redistribute it
- Self-host the embed bundle, ship the Python wheel, vendor the source
- You must keep the "Powered by Finterion" badge visible on every rendered chart
- You may not strip the badge, market a fork as "Finterion Charts", or build a competing hosted charting service on top of it
Want to remove the badge? That's what the commercial licence is for. Email licensing@finterion.com.
Finterion's own websites and products operate under an implicit commercial licence and run the library with branding={false}. See LICENSE for the full text.