Getting started
This guide takes you from install to a live, updating chart in vanilla TypeScript, React, Vue, Svelte, and Angular.
Install
One package, not two. Install the wrapper for your framework — it depends on core and re-exports core's whole public surface, so @chartcraft/core does not need to be a second direct dependency:
npm install @chartcraft/react # React 18+
npm install @chartcraft/vue # Vue 3
npm install @chartcraft/svelte # Svelte 4 or 5
npm install @chartcraft/angular # Angular 20+
npm install @chartcraft/core # vanilla / no framework@chartcraft/core has zero runtime dependencies and ships ESM, CJS, and TypeScript declarations. Since 0.4 each wrapper re-exports every core value as well as every core type — createChart, version, lightTheme, darkTheme, categoricalPalette, sequentialPalette, sequentialRampFor, the four scale classes, downsampleLTTB and the four decorator functions — as named re-exports that tree-shake, so importing lightTheme from a wrapper is byte-identical to importing it from core. Import everything from the one package.
Your first chart (vanilla)
A chart needs a container element with a size. By default the chart fills the container and stays responsive via ResizeObserver.
<div id="chart" style="width: 640px; height: 360px;"></div>import { createChart } from '@chartcraft/core';
const chart = createChart(document.querySelector<HTMLElement>('#chart')!, {
type: 'bar',
title: 'Revenue by quarter',
subtitle: 'FY2025, USD millions',
data: {
categories: ['Q1', 'Q2', 'Q3', 'Q4'],
series: [
{ name: 'Product', data: [12.4, 13.1, 14.8, 16.2] },
{ name: 'Services', data: [6.1, 6.4, 7.0, 7.9] },
],
},
});That's a complete chart: axes are inferred from the data (category x, linear y), the legend appears automatically because there are two series, tooltips and keyboard navigation are on by default, and the theme follows the user's prefers-color-scheme (theme: 'auto').
createChart(container, options) returns a Chart instance — keep a reference to it; it is how you update, listen, and clean up.
Here is a live ChartCraft chart, rendered by this site with the same options API (more on the examples pages):
Your first chart (React)
The React wrapper spreads ChartOptions as props and adds className, style, and event props. Per-type components (LineChart, BarChart, PieChart, … one for each of the 39 chart types, e.g. HeatmapChart, GaugeChart, SankeyChart, ChoroplethChart) take the same props minus type.
import { BarChart } from '@chartcraft/react';
// Hoisted to module scope: a referentially stable `data` is a correctness
// requirement, not an optimisation — see the React guide.
const data = {
categories: ['Q1', 'Q2', 'Q3', 'Q4'],
series: [
{ name: 'Product', data: [12.4, 13.1, 14.8, 16.2] },
{ name: 'Services', data: [6.1, 6.4, 7.0, 7.9] },
],
};
export function RevenueChart() {
return (
<BarChart
title="Revenue by quarter"
subtitle="FY2025, USD millions"
data={data}
style={{ height: 360 }}
/>
);
}Prop changes call chart.update(...) (a diffed re-render, not a rebuild), and unmounting calls chart.destroy() for you. Option props are diffed by identity, so useMemo (or module scope) any object- or array-valued prop — an inline literal is a new object every render. See Memoise your option props.
Your first chart (Vue)
The Vue wrapper takes a single options object and deep-watches it.
<script setup lang="ts">
import { reactive } from 'vue';
import { Chart } from '@chartcraft/vue';
import type { ChartOptions } from '@chartcraft/vue';
const options = reactive<ChartOptions>({
type: 'bar',
title: 'Revenue by quarter',
data: {
categories: ['Q1', 'Q2', 'Q3', 'Q4'],
series: [
{ name: 'Product', data: [12.4, 13.1, 14.8, 16.2] },
{ name: 'Services', data: [6.1, 6.4, 7.0, 7.9] },
],
},
});
</script>
<template>
<Chart :options="options" style="height: 360px" @point-click="(ev) => console.log(ev.seriesName)" />
</template>Mutating options (it is deep-watched) triggers chart.update. See the Vue guide.
Your first chart (Svelte)
<script lang="ts">
import { Chart } from '@chartcraft/svelte';
import type { ChartOptions } from '@chartcraft/svelte';
let options: ChartOptions = {
type: 'bar',
title: 'Revenue by quarter',
data: {
categories: ['Q1', 'Q2', 'Q3', 'Q4'],
series: [
{ name: 'Product', data: [12.4, 13.1, 14.8, 16.2] },
{ name: 'Services', data: [6.1, 6.4, 7.0, 7.9] },
],
},
};
</script>
<div style="height: 360px">
<Chart {options} on:pointclick={(e) => console.log(e.detail.seriesName)} />
</div>Reassigning options triggers an update. See the Svelte guide.
Your first chart (Angular)
Standalone components, signal inputs and outputs, no NgModule and no zone.js requirement. Import the component you need and bind [options].
import { Component, signal } from '@angular/core';
import { CcBarChart } from '@chartcraft/angular';
import type { ChartSpec } from '@chartcraft/angular';
@Component({
selector: 'app-revenue',
imports: [CcBarChart],
template: `<cc-bar-chart [options]="options()" style="height: 360px" />`,
})
export class RevenueComponent {
readonly options = signal<ChartSpec>({
title: 'Revenue by quarter',
data: {
categories: ['Q1', 'Q2', 'Q3', 'Q4'],
series: [
{ name: 'Product', data: [12.4, 13.1, 14.8, 16.2] },
{ name: 'Services', data: [6.1, 6.4, 7.0, 7.9] },
],
},
});
}Assigning a new options object triggers chart.update (the input is watched by reference, like React's props — see immutable updates). See the Angular guide.
Updating data
Charts are updated, not recreated. chart.update(partial) deep-merges the partial into the current options, diffs, and re-runs only the affected pipeline stages — with animation interpolating between the old and new state.
// Replace the data (setData is shorthand for update({ data }))
chart.setData({
categories: ['Q1', 'Q2', 'Q3', 'Q4'],
series: [
{ name: 'Product', data: [12.9, 13.5, 15.1, 17.0] },
{ name: 'Services', data: [6.3, 6.6, 7.2, 8.1] },
],
});
// Or update anything else — theme, axes, title…
chart.update({ theme: 'dark', yAxis: { label: 'USD (millions)' } });Two rules of thumb:
- Keep series identity stable across updates. A series is identified by
id(defaulting toname). A series that keeps its identity keeps its color and animates smoothly; renaming without anidmakes it a new series. See Data model. - Prefer
updateover destroy-and-recreate. Recreating tears down the DOM, observers, and accessibility state and replays the entry animation. See Performance.
Listening to events
chart.on returns an unsubscribe function — the idiomatic cleanup pattern:
const off = chart.on('pointclick', (ev) => {
console.log(`${ev.seriesName} @ ${String(ev.x)}: ${ev.y}`);
});
// later
off();All events and payloads are listed in Interactions and the API reference.
Destroying
When the chart's host leaves the page, destroy it. This removes the canvas and the parallel accessibility DOM, disconnects the ResizeObserver, and removes all listeners:
chart.destroy();The framework wrappers do this automatically on unmount — only vanilla users call destroy() by hand.
Where next
- Data model — the three data shapes and when to use each
- Theming — light/dark/auto and custom themes
- Accessibility — what you get for free and what you should still do
- API reference — every option, type, and default