# Chartist v2: guides, API reference and packages Chartist draws responsive SVG charts and leaves their look to CSS. This is Chartist v2, a TypeScript library with no dependencies, available from npm (`chartist`) or as ES modules from a CDN, without a build step. When writing Chartist code: - Create charts with the functions: `lineChart(target, data, options)`, `barChart(…)` and `pieChart(…)` for the classic chart types, and `chart({ data, axes, layers })` for composed charts (bars and lines together, several axes). Import them from `chartist`, and the default styles with `import 'chartist/styles.css'`. - Don't write `new Chartist.Line(…)`, `new LineChart(…)` or `Chartist.Interpolation`: that's the 1.x and 0.x API. The 1.x classes are deprecated and leave `chartist` in 3.0; they still work, for compatibility only. - Style charts with CSS and custom properties (`--ct-series-a`, `--ct-line-width`, …), not with options. - Change what a chart draws with plugins: listeners on the pipeline's phase events (`compose` for scene nodes, `mount` for the DOM). 1.x `draw` events need the `compat` plugin from `chartist/compat`. - Size charts with their container (CSS), and use responsive options for what changes with the size. - For charts that update while people look at them, pass `renderer: liveDomRenderer()` from `chartist/live`: the chart keeps its SVG and changes it in place. Set up elements on the `add` changes in `mount`, not on every render. - Packages under `@chartist-js` (listed below) take the same data and options: `@chartist-js/terminal` draws charts as terminal text or HTML text art, `@chartist-js/essentials` adds layers such as `axisTitle()`, and `@chartist-js/tooltips` adds tooltips for mouse, touch and keyboard. ## Getting started > Your first chart in about two minutes. No build step, no bundler, no framework. An HTML file and a browser. > **Works without a bundler.** Chartist ships as a native ES module, so a ` ``` ### Open it in a browser That's it. Here's the whole file, ready to save as `index.html`: ```html My first chart
``` And this is what it draws, live. Press **Edit** and change a number. The editor knows Chartist's types: point at a name to read what it takes, press Ctrl+Space for suggestions, and a mistake gets a red underline before you run it. **My first chart** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; lineChart('#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [ [12, 9, 7, 8, 5], [2, 1, 3.5, 7, 3] ] }); ``` `index.html`: ```html
``` ### Import Chartist by name An import map tells the browser where `chartist` lives, so your code can import it by name, just like with npm. The same code then moves to a bundler unchanged, and plugins that import `chartist` find it too: ```html ``` The import map goes before the first module script. `chartist/compat` is only needed if you use the [1.x compatibility](https://chartist.dev/docs/coming-from-1x/); it shares the core of the `chartist` entry, since `compat.min.js` imports `index.min.js` next to it. ### Using npm Same API, installed from npm. It works with Vite, webpack, Rollup, esbuild and Astro, and it's written in TypeScript, so the types come with it. ```sh npm install chartist ``` ```ts import 'chartist/styles.css'; import { barChart } from 'chartist'; barChart( '#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [ [5, 4, 3, 7], [3, 2, 9, 5] ] }, { stackBars: true } ); ``` Chartist only adds what you import to your bundle, so a single line chart stays small. ### Make it yours with CSS There's no theme option. Charts are SVG with predictable classes, so you style them the way you style everything else. Custom properties cover the common cases, for the whole page, one container or one chart: ```css #chart { --ct-series-a: #9a6a45; /* first series */ --ct-line-width: 2px; --ct-grid-dasharray: none; } ``` > **Coming from Chartist 1.x?** Your code keeps working: the chart classes are > still there, and your charts look exactly the same. [Coming from > 1.x](https://chartist.dev/docs/coming-from-1x/) shows how to move to the functions, one chart at a > time. ### Where to next - Pick your chart: [line](https://chartist.dev/docs/line-charts/), [bar](https://chartist.dev/docs/bar-charts/), [pie and donut](https://chartist.dev/docs/pie-and-donut-charts/), or [bars and lines together](https://chartist.dev/docs/composed-charts/). - Shape your data and options: [Data and options](https://chartist.dev/docs/data-and-options/), [Sizing charts](https://chartist.dev/docs/sizing-charts/). - Make it yours: [Styling with CSS](https://chartist.dev/docs/styling-with-css/), then [Responsive sugar topping](https://chartist.dev/docs/responsive/). - Wondering whether Chartist fits? [Why Chartist?](https://chartist.dev/docs/why-chartist/) says what it's made for, and what it leaves to you. ## Why Chartist? > Chartist draws simple, responsive charts as SVG and leaves styling, interaction and the rest to the web platform. Here's what that means for you. Chartist has one job: turn your data into a clean, responsive SVG chart. Everything the browser already does well (styling, events, animation, layout) it leaves to the browser. That's a choice, and it shapes everything else on this site. ### SVG in the DOM A Chartist chart is an inline `` in your page, with groups and paths and class names you can read in the dev tools. Not a canvas, not a black box. Anything that works on the DOM works on a chart: CSS, `querySelector`, event listeners, screen reader attributes, your browser's inspector. ### Your stylesheet decides the look There's no theme engine and no color option. Colors, line widths, grid dashes and fonts live in CSS, next to the rest of your styles, where a designer can change them without touching JavaScript. Here's the proof. The script below only says what to draw. Everything you see comes from five custom properties; press **Edit** and change them: **Styled by CSS** (runs in the browser): `src/style.css`: ```css /* The whole look of this chart */ #chart { --ct-series-a: #b07800; --ct-line-width: 3px; --ct-point-size: 0; --ct-area-opacity: 0.3; --ct-grid-dasharray: none; } ``` `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { lineChart } from 'chartist'; // Cups of coffee, Monday to Sunday. No colors or sizes in here. lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], series: [[3, 4, 2, 5, 6, 2, 1]] }, { showArea: true } ); ``` `index.html`: ```html
``` When the brand color changes, you change one line. [Styling with CSS](https://chartist.dev/docs/styling-with-css/) has the full list of properties and classes. ### Tooltips, as a package Chartist's core doesn't ship tooltips, zoom, legends or click handlers. A tooltip is interaction in the browser, and a chart drawn on a server or in a terminal has nothing to hover, so the core leaves it out. [`@chartist-js/tooltips`](https://chartist.dev/packages/tooltips/) adds tooltips to the charts that need them, for mouse, touch and keyboard, styled with CSS. For anything else, the DOM already has events: a few lines of your own code or a plugin work the way the rest of your site does. If you need zoom, panning and crosshairs out of the box, or tens of thousands of points, Chartist isn't the right tool. A canvas library will serve you better. ### Small, and only what you use Chartist has no dependencies, and it's tree-shakable: your bundle gets the parts you import. A line chart with `lineChart()` comes to about 12 kB minified and gzipped, and the default stylesheet to under 1.5 kB. ### No build step required Chartist ships as a native ES module, so a `
``` - **`LineChart`, `BarChart` and `PieChart`** from `chartist`, with `update(data, options, override)`, `detach()`, `on()` and `off()`. The first render still happens on the next tick, and charts still redraw when the window resizes, and now also when their container does. - **Every other 1.x export**, from `chartist` and from the UMD global: `Svg`, `SvgList`, `SvgPath`, `easings`, `Interpolation`, the axis classes, `EventEmitter`, the helpers and the types (`LineChartData`, `BarChartOptions` and the rest). None of them was removed. The classes and what only they use are [deprecated](#deprecated-in-2x-removed-in-30), and still work through all of 2.x. - **The look.** Same class names, same default styles. v2 is tested against screenshots of 1.5.0, and the default stylesheet matches them. - **Your stylesheet imports.** `chartist/dist/index.css` and the Sass entry `chartist/dist/index.scss` are still in the package. The new short path is `chartist/styles.css`: ```ts import 'chartist/styles.css'; import { BarChart, LineChart, PieChart } from 'chartist'; ``` ### Events need the compat plugin This is the one change you may need to make. In 1.x, every chart emitted `data`, `optionsChanged`, `draw`, `created`, `animationBegin` and `animationEnd`. In v2 these 1.x events live in an opt-in plugin, so charts that don't use them don't carry the code. Add `compat` from `chartist/compat` to `plugins`, and your listeners run as before: ```ts import 'chartist/styles.css'; import { LineChart } from 'chartist'; import { compat } from 'chartist/compat'; const chart = new LineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[12, 9, 7, 8, 5]] }, { plugins: [compat] } ); chart.on('draw', data => { if (data.type === 'point' && data.index === 4) { data.element.addClass('is-friday'); } }); ``` Without `compat`, a listener for a 1.x event isn't called, and the console tells you once per event name what to add. In TypeScript it doesn't compile either: importing `chartist/compat` is what declares the 1.x events, so `data` in a `draw` listener is typed as before. With `compat`, `draw` events come in the 1.x order and with the 1.x payloads. They fire right after the chart is in the DOM, so `data.element` is a live element you can measure, animate or replace. One small difference: an element you append to `data.group` lands after the group's other elements. ### Your 1.x plugins A 1.x plugin is a function that gets the chart (and its options). On the classes, those plugins run when `compat` is in the list too: ```ts import 'chartist/styles.css'; import { LineChart } from 'chartist'; import { compat, type Plugin } from 'chartist/compat'; // A 1.x plugin: a function that gets the chart and its options. const caption: Plugin = (chart, options) => { chart.on('created', () => { chart.container.setAttribute('aria-label', options.text); }); }; new LineChart( '#chart', { series: [[1, 3, 2, 4]] }, { plugins: [compat, [caption, { text: 'Visitors per week' }]] } ); ``` Without `compat`, Chartist skips the plugin and logs a warning that explains how to turn it on. ### Deprecated in 2.x, removed in 3.0 The classes, and everything that exists only for them, are deprecated. They keep working through all of 2.x, and 3.0 removes them from `chartist`. Nothing changes at runtime and nothing is logged: your editor strikes the names through and its hint says what replaces them. - **`LineChart`, `BarChart`, `PieChart`:** use `lineChart()`, `barChart()` and `pieChart()`, or `chart()` for the rest. - **`LineChartOptions`, `BarChartOptions`, `PieChartOptions`:** the options of the functions are `LineChartPresetOptions`, `BarChartPresetOptions` and `PieChartPresetOptions`. - **`AutoScaleAxis`, `FixedScaleAxis`, `StepAxis`:** use `autoScaleAxis()`, `fixedScaleAxis()` and `stepAxis()`. - **`EventEmitter` and the classes' events types:** the handle a chart function returns has `on()` and `off()`. - **`DrawEvent`, `CreatedEvent`, `Plugin` and the other 1.x event types:** import the same types from `chartist/compat`. - **`labelInterpolationFnc`:** the option is now called `labelTransform`, on axes and pies alike. The old name keeps working; when both are set, `labelTransform` wins. The data types, `Svg`, `SvgPath`, `easings`, `Interpolation` and the helpers aren't deprecated: the functions use them too. If your linter treats deprecations as errors (for example `@typescript-eslint/no-deprecated`), this is where it shows up. Move one chart at a time, as the next section shows. ### Moving to the functions Each class has a function with the same arguments and the same options: `lineChart()`, `barChart()` and `pieChart()`. The functions are where the new features are, and they're the smaller bundle too. You can move one chart at a time; classes and functions work side by side on one page. #### 1. Swap the constructor ```ts nocheck // 1.x const chart = new LineChart('#chart', data, options, responsiveOptions); // v2 const chart = lineChart('#chart', data, options, responsiveOptions); ``` The data, the options and the media query pairs in `responsiveOptions` stay as they are. #### 2. Know what's different about the handle `lineChart()` returns a small handle instead of a class instance: | 1.x class | v2 function | | ------------------------------------------------ | -------------------------------------------------------------------------------- | | First render on the next tick | Renders right away, before the call returns | | Redraws when the window or its container resizes | Redraws when the container resizes (`ResizeObserver`) | | `chart.update(newData)` | `chart.update({ data: newData })`, see [Live updates](https://chartist.dev/docs/live-updates/) | | `chart.update(undefined, options, true)` | `chart.setOptions(options)` | | `chart.update(undefined, options)` | `chart.setOptions(options, { replace: true })` | | `chart.detach()` | `chart.detach()`, or `chart.destroy()` to also remove the SVG | | `chart.on('draw', …)` and other 1.x events | Pipeline events like `compose`, see [Events and plugins](https://chartist.dev/docs/events-and-plugins/) | #### 3. Rewrite draw handlers as plugins A 1.x `draw` handler changes elements after they're drawn. In v2, a plugin changes the chart's scene before it's rendered: a `compose` listener gets each node and returns a changed copy, or nothing to keep it. ```ts nocheck // 1.x chart.on('draw', data => { if (data.type === 'point' && data.index === 4) { data.element.addClass('is-friday'); } }); ``` ```ts import 'chartist/styles.css'; import { lineChart, type ChartPlugin } from 'chartist'; // v2 const markFriday: ChartPlugin = { name: 'mark-friday', install(chart) { chart.on('compose', node => node.role === 'point' && node.ctx.valueIndex === 4 ? node.addClass('is-friday') : undefined ); } }; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[12, 9, 7, 8, 5]] }, { plugins: [markFriday] } ); ``` Because it works on the scene, this version also runs when you [render on the server](https://chartist.dev/docs/server-rendering/). #### 4. Bring 1.x plugins along with `fromLegacyPlugin` The functions accept only v2 plugins, and throw if you pass a plain function. Wrap a 1.x plugin with `fromLegacyPlugin()` from `chartist/compat`. It turns on the 1.x events for that chart and calls your plugin with a 1.x-style chart object (`container`, `data`, `options`, `svg`, `on`, `off`, `update`, `detach`): ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; import { fromLegacyPlugin, type Plugin } from 'chartist/compat'; const caption: Plugin = (chart, options) => { chart.on('created', () => { chart.container.setAttribute('aria-label', options.text); }); }; lineChart( '#chart', { series: [[1, 3, 2, 4]] }, { plugins: [fromLegacyPlugin(caption, { text: 'Visitors per week' })] } ); ``` You don't need `compat` in the list as well. > **Register 1.x listeners in a plugin.** The functions render before they > return, so a `draw` listener added with `chart.on()` afterwards misses the > first render. Put it in a plugin instead: > `fromLegacyPlugin(chart => chart.on('draw', …))`. Listeners of the v2 phases > go in the `on` option: `lineChart('#chart', data, { on: { compose: … } })`. ### Community plugins Most community plugins, like the legends and labels on the [plugins page](https://chartist.dev/plugins/), were written for 0.x. They're scripts: they read the global `Chartist`, register on `Chartist.plugins`, and check the chart type with `chart instanceof Chartist.Line`. Importing `chartist/compat` sets that global up for them, in every build: the library, compat, and the 0.x names they use (`plugins`, `Line`, `Bar` and `Pie` for the chart classes, and `getDataArray`). It only adds what's missing, so plugins that already registered keep their place. These names live on the global only; they aren't exports. Then load the plugin as a script that runs after that import, and wrap it with `fromLegacyPlugin`. How depends on how you load Chartist. Tooltips and axis titles don't need a community plugin any more: [`@chartist-js/tooltips`](https://chartist.dev/packages/tooltips/) replaces the 1.x tooltip plugins on the functional API, and [`axisTitle()`](https://chartist.dev/packages/essentials/axis-titles/) in `@chartist-js/essentials` is a layer that reserves its own room. These plugins need a browser: their `draw` and `created` events fire when the chart is drawn in the page, so they don't run in [server rendering](https://chartist.dev/docs/server-rendering/). #### With modules, without a bundler The plugin script needs `defer` and has to come after the module that imports compat: deferred scripts and module scripts run in document order. Then your chart code runs after both. ```html
``` Import `index.min.js` with `compat.min.js`, or `index.js` with `compat.js`: each pair shares one copy of Chartist. #### With the UMD build With classic script tags, load the plugin after Chartist's UMD build and `compat.umd.js`, and use everything from the global: ```html
``` On the 1.x classes, list the plugin next to `compat` as before: `new Chartist.LineChart('#chart', data, { plugins: [Chartist.compat, Chartist.plugins.ctThreshold()] })`. #### With a bundler A bundler can't use these plugins as modules: it takes their CommonJS entry, which reads `require('chartist')` and expects the 0.x names on it. Import compat as usual, then load the plugin as a script at runtime, before the first chart that uses it: ```ts nocheck import { lineChart } from 'chartist'; import { fromLegacyPlugin } from 'chartist/compat'; function loadScript(src: string) { return new Promise((resolve, reject) => { const script = Object.assign(document.createElement('script'), { src }); script.onload = () => resolve(); script.onerror = () => reject(new Error(`Can't load ${src}`)); document.head.append(script); }); } await loadScript( 'https://cdn.jsdelivr.net/npm/chartist-plugin-threshold@0.0.2/dist/chartist-plugin-threshold.js' ); lineChart('#chart', data, { plugins: [fromLegacyPlugin(Chartist.plugins.ctThreshold({ threshold: 2.5 }))] }); ``` `chartist/compat` is marked as having side effects in its package, so bundlers keep the import even where nothing from it is used. In TypeScript, declare the global the plugins register on: ```ts nocheck declare const Chartist: { plugins: Record import('chartist').Plugin>; }; ``` #### Plugins that are ES modules A plugin written for 1.x as an ES module imports `chartist` itself, like `@stklcode/chartist-plugin-tooltips`. Import it as usual and wrap it with `fromLegacyPlugin`; it needs no global. For tooltips on the functional API, [`@chartist-js/tooltips`](https://chartist.dev/packages/tooltips/) replaces the 1.x tooltip plugins. #### One copy of Chartist Load Chartist once: as modules, or as the UMD build. If compat is imported as a module while the UMD build of another copy already owns the global `Chartist`, compat leaves that global alone and logs a warning. Two copies would break the plugins' type checks: a chart of one copy isn't an instance of the other copy's `LineChart`. ### What's new These come with the functions. The classes stay as they were in 1.x, so they don't get them: - **Composed charts:** bars and lines in one chart, several axes, with `chart()`. See [Composed charts](https://chartist.dev/docs/composed-charts/). - **Container queries** for responsive options, next to media queries (experimental). See [Responsive sugar topping](https://chartist.dev/docs/responsive/). - **Server rendering** with `toScene()` and `renderToString()`, and SVG text labels. See [Server rendering](https://chartist.dev/docs/server-rendering/). - **v2 plugins** that hook into every step of the pipeline. See [Events and plugins](https://chartist.dev/docs/events-and-plugins/). - **`setOptions()`** changes a chart's options after creation, and the **`on` option** listens from the first render. See [Live updates](https://chartist.dev/docs/live-updates/#new-options). - **Stricter types:** scene nodes narrow by role, event names and payloads are checked, and a misspelled event is a compile error instead of a listener that's never called. And for every chart, classes included: - **Custom properties** like `--ct-series-a` and `--ct-line-width` in the default stylesheet, and an opt-in accessible palette. See [Styling with CSS](https://chartist.dev/docs/styling-with-css/). ### Classic script tags The UMD build is still published for pages that load Chartist with a classic `
``` The 0.x file names, `dist/chartist.min.js` and `dist/chartist.min.css`, are copies of the same files, so old links keep loading. The 0.x API (`new Chartist.Line(…)`) went away in 1.0 and doesn't come back. Compat puts `Chartist.Line`, `Bar` and `Pie` on the global only so the type checks of [community plugins](#community-plugins) keep working; in your own code, use the functions or the 1.x classes. ### What changed Nothing was removed. The changes covered above: - **1.x events** need `compat` in `plugins`. Without it, listeners aren't called and the console warns. - **1.x plugins** need `compat` on the classes, or `fromLegacyPlugin()` on the functions. The functions throw for a plain plugin function. - **`draw` events** fire after the DOM render. Elements appended to `data.group` land after the group's other elements. - **The compat plugin** covers line, bar and pie charts, not composed charts made with `chart()`. It needs the DOM renderer, so it doesn't work with server rendering. A few 1.x behaviors were bugs, and v2 fixes them, on the classes too: - **An axis's own `low` and `high`** win over a bar chart's zero line: `axisY: { low: 10 }` starts the axis at 10. In 1.x, the axis always included zero. - **`fixedScaleAxis` with `divisor`** puts a tick at `high` too: `divisor: 4` gives five ticks. 1.x left out the last one. - **The donut ring width** comes from the stylesheet (`--ct-donut-width`, 60px) unless you set `donutWidth`, and in the page the ring is laid out for it. Without the Chartist stylesheet, set `donutWidth`, or the ring gets the browser's 1px stroke. When rendering on the server, set `donutWidth` if your stylesheet changes the width. - **Donut labels** follow `labelPosition`, as pie labels do. In 1.x they always sat on the ring. - **`stackMode: 'accumulate-relative'`** fits the axis to the stacks below zero too, so you no longer need a `low` for them. - **`roundWithPrecision(value, 0)`** and `SvgPath` with `accuracy: 0` round to whole numbers. In 1.x, 0 digits fell back to 8, or didn't round at all. `accuracy: false` turns rounding off. - **`animationBegin` and `animationEnd`** fire with `compat` when you call `data.element.animate()` in a `draw` listener. In 1.x they never reached your listeners. - **`off(name)`** without a listener removes only your listeners. In 1.x it also removed those of plugins. - **A slice that fills the circle**, like a single value, is drawn around the centre. In 1.x it could sit a few pixels off. - **x values in descending order** draw a line with the default smoothing. 1.x drew nothing. - **Holes written as `{ x, y: null }`** leave a gap with `Interpolation.step()` and `simple()` too. 1.x wrote `NaN` into the path. - **A chart-level `low` or `high` that leaves no range**, like a `low` equal to the stacked high, still gives an axis. 1.x ran until the stack overflowed. - **Labels next to a tick at the axis start** get their step's width. In 1.x, bar labels could land on top of each other. - **`referenceValue`** works on line charts too, for the default Y axis. In 1.x it only worked for bars. - **Series 16 to 26** (`ct-series-p` to `ct-series-z`) are coloured, repeating the first 15 colours. 1.x left them blank. - **Label spans** have no `xmlns` attribute, so a serialized chart parses as SVG. - **`SvgPath`** splits, joins, parses, scales and translates consistently. - **An object option over a number** works, like `chartPadding: { top: 20 }` on a pie. 1.x threw. - **Charts in an iframe or a JSDOM document** use that document's window, and options arrays made there stay arrays. - **`detach()` during the first render** sticks, and a missing target throws once, not again later. - **Printed charts** scale to the page instead of being cut off. - **With `reverseData`**, each value keeps its `meta` on bar and line charts. In 1.x, the meta stayed in the original order, so a bar got the meta of the value at the other end. In the API reference: [`chartist/compat`](https://chartist.dev/api/compat/) and [the 1.x classes](https://chartist.dev/api/classes/). Next: [draw a chart with the functions](https://chartist.dev/docs/getting-started/), or [see what Chartist leaves to CSS](https://chartist.dev/docs/why-chartist/). ## Line charts > Lines, areas and points with lineChart(), from smoothing and gaps in your data to scatter plots and edge-to-edge lines. A line chart draws each series as a line through its points, one point per label. That's the whole idea, and most of this page is about the switches around it: fill the area below, smooth the line or keep it straight, leave gaps where data is missing, or drop the line and keep the dots. `lineChart()` takes a container, your data and options. The data shapes (plain arrays, series objects, `{ x, y }` values) are the same for every chart type and live in [Data and options](https://chartist.dev/docs/data-and-options/). ### Lines, points and areas By default you get a smooth line with a point on every value. Three options switch the parts on and off: | Option | Default | What it draws | | ----------- | ------- | ---------------------------- | | `showLine` | `true` | The line, a `ct-line` path | | `showPoint` | `true` | A `ct-point` for every value | | `showArea` | `false` | The `ct-area` below the line | Turn on `showArea` and you have an area chart: **An area chart** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], series: [[5, 9, 7, 8, 5, 3, 5]] }, { showArea: true } ); ``` `index.html`: ```html
``` The area closes at `areaBase`, which is `0` by default. If `0` is outside the axis range, as in this example, the area closes at the edge of the chart instead. Its opacity, like every other color and size, is set in CSS: see [Styling with CSS](https://chartist.dev/docs/styling-with-css/). ### Smooth or straight Lines are smooth by default. The default curve is a monotone cubic spline: it passes through every point and never swings above or below your data between two points, so a smooth line doesn't invent peaks. Set `lineSmooth: false` for straight segments. Options also work per series. Give a series a `name`, and the `series` option takes overrides by that name for `lineSmooth`, `showLine`, `showPoint`, `showArea` and `areaBase`: **Smooth and straight lines** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; lineChart( '#chart', { labels: ['6am', '9am', '12pm', '3pm', '6pm', '9pm'], series: [ { name: 'smooth', data: [11, 15, 21, 23, 18, 14] }, { name: 'straight', data: [6, 9, 14, 15, 11, 8] } ] }, { series: { straight: { lineSmooth: false } } } ); ``` `index.html`: ```html
``` #### Pick your curve `lineSmooth` also takes an interpolation function, for a different curve or to tune one. They're all in the `Interpolation` namespace, and each is a factory that takes options: | Function | Options | The line | | ------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `Interpolation.monotoneCubic()` | `fillHoles` | Smooth and never past the data. The default for `lineSmooth: true`. | | `Interpolation.cardinal()` | `tension` (`1`), `fillHoles` | A Catmull-Rom spline: rounder, but it can overshoot when values change quickly. | | `Interpolation.simple()` | `divisor` (`2`), `fillHoles` | Curves with horizontal handles: hits every point, never overshoots, less flowing. | | `Interpolation.step()` | `postpone` (`true`), `fillHoles` | Steps. With `postpone`, the line holds each value until the next point; `false` jumps to the next value right away. | | `Interpolation.none()` | `fillHoles` | Straight segments. The same as `lineSmooth: false`. | ```ts import 'chartist/styles.css'; import { Interpolation, lineChart } from 'chartist'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[1, 3, 3, 2, 4]] }, { lineSmooth: Interpolation.step() } ); ``` The step line draws its corners as extra points, so with `showPoint` on you'll see a dot at every corner. The two splines need at least three values in a row; with fewer they draw straight segments. ### Gaps in your data Missing a value? Put `null` where it would be. The line stops before the gap and starts again after it, with no point in between. A series shorter than the labels ends early. **A gap in the data** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; // The sensor was offline on Wednesday and Thursday. lineChart('#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], series: [[4, 6, null, null, 5, 7, 6]] }); ``` `index.html`: ```html
``` That's honest: the chart doesn't pretend to know what happened on Wednesday. With `showArea`, each piece of the line gets its own area. If you'd rather bridge the gap, every interpolation function takes `fillHoles: true` and connects the values on either side: ```ts import 'chartist/styles.css'; import { Interpolation, lineChart } from 'chartist'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], series: [[4, 6, null, null, 5, 7, 6]] }, { lineSmooth: Interpolation.monotoneCubic({ fillHoles: true }) } ); ``` ### Scatter plots A scatter plot is a line chart without the line. Give each value an `x` and a `y`, use a scale for the X axis instead of the labels, and set `showLine: false`: ```ts import 'chartist/styles.css'; import { autoScaleAxis, lineChart } from 'chartist'; lineChart( '#chart', { series: [ [ { x: 1, y: 2 }, { x: 2.5, y: 7 }, { x: 4, y: 3 }, { x: 7, y: 8 }, { x: 10, y: 4 } ] ] }, { showLine: false, axisX: { type: autoScaleAxis() } } ); ``` Without `axisX.type`, the X axis has one step per label and the points sit in label order, whatever their `x`. The axis types and their options are explained in [Axes and scales](https://chartist.dev/docs/axes-and-scales/). ### From edge to edge The X axis divides the width into one step per label and puts each point at the start of its step, so the last point stops short of the right edge. `fullWidth: true` stretches the line across the whole width. The last label then sits on the edge, so give it room with `chartPadding`: **A full-width line chart** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; lineChart( '#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [ [5, 9, 7, 12], [3, 4, 6, 5] ] }, { fullWidth: true, // Room for the last label, which now sits on the right edge. chartPadding: { right: 40 } } ); ``` `index.html`: ```html
``` ### Low and high The Y axis fits your data. Set `low` and `high` to fix either end, for example to start at zero or to keep several charts on the same scale: ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[61, 64, 62, 67, 66]] }, { low: 0, high: 100 } ); ``` > **Zero or not?** A line chart that doesn't start at zero shows the change > better; one that does shows the size. Both are fine, as long as the axis > labels say which one you picked. Ticks, label formats and the other axis settings are in [Axes and scales](https://chartist.dev/docs/axes-and-scales/). ### Goals and ranges The `layers` option adds layers to the chart: `below` the lines or `above` them. Draw a range with `band()`, as in [Bands](https://chartist.dev/docs/bands/), or a goal with `targetLine()` from [`@chartist-js/essentials`](https://chartist.dev/packages/essentials/target-lines/). In the API reference: [`lineChart`](https://chartist.dev/api/charts/#lineChart) and [its options](https://chartist.dev/api/charts/#LineChartPresetOptions). Next: [bar charts](https://chartist.dev/docs/bar-charts/), or [make your lines respond to the screen](https://chartist.dev/docs/responsive/). ## Bar charts > Grouped, stacked, horizontal and distributed bars with barChart(), and how to space them. A bar chart takes the same data as a line chart: labels along one axis, a series per array. Each value becomes a bar that grows from zero, so the axis includes zero (unless you [set `low` or `high`](#low-and-high)) and negative values hang below it. `barChart()` takes a container, your data and options. The data shapes are shared by all chart types and explained in [Data and options](https://chartist.dev/docs/data-and-options/). ### Grouped bars Several series put their bars side by side, one group per label: **Grouped bars** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { barChart } from 'chartist'; barChart('#chart', { labels: ['Jan', 'Feb', 'Mar', 'Apr'], series: [ [5, 4, 3, 7], [3, 2, 9, 5], [1, 5, 8, 4] ] }); ``` `index.html`: ```html
``` A `null` value leaves its bar out and keeps its place in the group. ### Bar spacing Two settings shape a group, and they live in different places. The distance between the bars of a group is layout, so it's an option: `seriesBarDistance`, in pixels, `15` by default. The thickness of a bar is looks, so it's CSS: bars are SVG lines, and `--ct-bar-width` (`10px` by default) sets their stroke width. **Wider bars, further apart** (runs in the browser): `src/style.css`: ```css #chart { --ct-bar-width: 20px; } ``` `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { barChart } from 'chartist'; barChart( '#chart', { labels: ['Jan', 'Feb', 'Mar', 'Apr'], series: [ [5, 4, 3, 7], [3, 2, 9, 5] ] }, { seriesBarDistance: 24 } ); ``` `index.html`: ```html
``` Keep `seriesBarDistance` at least as large as the bar width, or the bars of a group overlap. More about the custom properties in [Styling with CSS](https://chartist.dev/docs/styling-with-css/#custom-properties). ### Stacked bars With `stackBars: true`, the series of a label stack into one bar, and the value axis grows to fit the totals. `seriesBarDistance` has no effect here. **Stacked bars** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { barChart } from 'chartist'; barChart( '#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [ [800, 1200, 1400, 1300], [200, 400, 500, 300], [100, 200, 400, 600] ] }, { stackBars: true } ); ``` `index.html`: ```html
``` #### Stack modes `stackMode` decides what happens when values have different signs: | `stackMode` | The stack | | ------------------------ | -------------------------------------------------------------------------------------------------- | | `'accumulate'` (default) | A running total: each bar starts where the previous one ended, so a negative value goes back down. | | `'accumulate-relative'` | Positive values stack up from zero, negative values stack down from zero. | | `'overlap'` or `true` | No stacking: every bar starts at zero and they overlap, the first series at the back. | `'accumulate-relative'` is the one for gains and losses: ```ts import 'chartist/styles.css'; import { barChart } from 'chartist'; barChart( '#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [ [5, 4, 6, 8], [-3, -1, -4, -2] ] }, { stackBars: true, stackMode: 'accumulate-relative' } ); ``` The value axis fits both sides: up to the highest stack above zero and down to the lowest one below it. ### Horizontal bars `horizontalBars: true` turns the chart on its side: the labels move to the Y axis and the values to the X axis. The axis options move with them, so the label settings now go in `axisY`. Long labels need a wider `offset`: **Horizontal bars** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { barChart } from 'chartist'; barChart( '#chart', { labels: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], series: [ [5, 4, 3, 7, 5], [3, 2, 9, 5, 4] ] }, { horizontalBars: true, // The labels are on the Y axis now: give them room. axisY: { offset: 70 } } ); ``` `index.html`: ```html
``` The chart gets the `ct-horizontal-bars` class next to `ct-chart-bar`, for styles that only apply to horizontal bars. ### Distributed series Sometimes every bar is its own thing: sizes, categories, a profile rather than values over time. Pass a flat array of values and set `distributeSeries: true`. Each value becomes its own series, `ct-series-a`, `ct-series-b` and so on, so every bar gets its own color: **Distributed series** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { barChart } from 'chartist'; // A flat array: one value per label, and each bar is its own series. barChart( '#chart', { labels: ['XS', 'S', 'M', 'L', 'XL'], series: [20, 60, 120, 200, 180] }, { distributeSeries: true } ); ``` `index.html`: ```html
``` ### Low and high `low` and `high` fix the ends of the value axis, as on line charts, and so do the value axis's own (`axisY: { low: 10 }`). Bars still start at zero: with a `low` above zero, the bars start at the bottom edge of the chart. The axis settings are in [Axes and scales](https://chartist.dev/docs/axes-and-scales/). ```ts import 'chartist/styles.css'; import { barChart } from 'chartist'; barChart( '#chart', { labels: ['Jan', 'Feb', 'Mar', 'Apr'], series: [[5, 4, 3, 7]] }, { high: 10 } ); ``` ### Goals and ranges The `layers` option adds layers to the chart: `below` the bars or `above` them, for a range with `band()` (see [Bands](https://chartist.dev/docs/bands/)) or a goal line with `targetLine()` from [`@chartist-js/essentials`](https://chartist.dev/packages/essentials/target-lines/). In the API reference: [`barChart`](https://chartist.dev/api/charts/#barChart) and [its options](https://chartist.dev/api/charts/#BarChartPresetOptions). Next: [line charts](https://chartist.dev/docs/line-charts/), or [make bars respond to the screen](https://chartist.dev/docs/responsive/). ## Pie and donut charts > Pies, donuts and gauges with pieChart(), from the data shape to label placement, slice colors and half-circle gauges. A pie shows parts of a whole. `pieChart()` draws one slice per value, sized by its share of the total, and turns into a donut or a gauge with a few options. Pies work best with a handful of slices. If you're comparing more than five or six values, a [bar chart](https://chartist.dev/docs/bar-charts/) is easier to read. ### Your first pie A pie takes a flat series: one array of numbers, not an array of arrays. The labels are optional and go on the slices in the same order: **A one-hour meeting** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { pieChart } from 'chartist'; // Where the minutes of a one-hour meeting go. pieChart('#chart', { labels: ['Agenda', 'Small talk', 'Decisions', 'Coffee'], series: [10, 25, 5, 20] }); ``` `src/style.css`: ```css /* Labels on colored slices read best in white. */ #chart { --ct-label-color: #fff; } ``` `index.html`: ```html
``` Every value becomes its own series group, `ct-series-a`, `ct-series-b` and so on, so every slice gets its own color from the stylesheet. Leave out the labels and the slices show their values instead. The white labels are one line of CSS, `--ct-label-color: #fff` in `src/style.css`: the default label color is a translucent black, made for axes on a light background. Labels [outside the pie](#labels) keep it. ### Donuts Set `donut: true` and the slices become arcs of a ring. The stylesheet sets how thick it is, 60 px by default: change it with `--ct-donut-width` in CSS, for the page, a container or one chart. The chart lays the ring out for that width, so it still fits the chart's edge. **Donut** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { pieChart } from 'chartist'; pieChart( '#chart', { labels: ['Agenda', 'Small talk', 'Decisions', 'Coffee'], series: [10, 25, 5, 20] }, { donut: true } ); ``` `src/style.css`: ```css /* Labels on colored slices read best in white, and the ring is 40 px. */ #chart { --ct-label-color: #fff; --ct-donut-width: 40px; } ``` `index.html`: ```html
``` A donut's SVG gets `ct-chart-donut` instead of `ct-chart-pie`, and its slices are `ct-slice-donut` paths drawn with a stroke, not a fill. Labels sit in the middle of the ring; `labelPosition` and `labelOffset` move them as on a pie. > **The width comes from CSS, or from `donutWidth`.** The chart reads the width > the stylesheet gives a slice on every render, so `--ct-donut-width: 30px` on a > container and a 1.x rule like `.ct-slice-donut { stroke-width: 30px }` both > work. Only px widths count: a percentage in CSS is laid out as 60 px. After a > CSS change that doesn't resize the chart, such as a theme switch, call > `update()`. Set `donutWidth` where there's no stylesheet to read: with > `renderToString()` the ring is laid out for 60 px, and without the Chartist > stylesheet it gets the browser's 1px stroke. `donutWidth` takes px or a > percentage of the radius, like `'30%'`, and is written as an inline > `stroke-width` that wins over the stylesheet. 1.x code that sets it keeps > working. ### Gauges Two options turn a donut into a gauge. `startAngle` is where the first slice starts, in degrees: `0` points up and the slices run clockwise. `total` fixes the value of a full circle; without it, the slices always add up to one. When your values sum to less than `total`, part of the circle stays empty. Start at `270` (the left side) and make the total twice the sum, and the slices fill the upper half: **Sprint gauge** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { pieChart } from 'chartist'; // Story points of a sprint: done, in progress, to do. The total is // twice their sum, so together they fill half the circle. pieChart( '#chart', { series: [12, 5, 3] }, { donut: true, startAngle: 270, total: 40, showLabel: false } ); ``` `src/style.css`: ```css /* A 40 px ring. */ #chart { --ct-donut-width: 40px; } ``` `index.html`: ```html
``` Gauges usually go without slice labels, so this one sets `showLabel: false`. A single-value gauge works the same way: `{ series: [72] }` with `total: 200` fills a little more than a third of the circle. ### Labels By default, labels sit halfway between the center and the edge of the slice, or in the middle of a donut's ring. Four options move them: | Option | Default | What it does | | ---------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `showLabel` | `true` | Draws the labels at all | | `labelPosition` | `'inside'` | `'inside'` (half the radius, or the middle of the ring), `'outside'` (on the outer edge) or `'center'` (the center point, plus `labelOffset`) | | `labelOffset` | `0` | Px added to that distance from the center; negative values move labels inward | | `labelDirection` | `'neutral'` | `'explode'` anchors labels so the text grows away from the center, `'implode'` toward it; `'neutral'` centers it | Labels outside the pie need room, so give the chart some `chartPadding` (the pie's default is `5`): **Labels outside** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { pieChart } from 'chartist'; pieChart( '#chart', { labels: ['Agenda', 'Small talk', 'Decisions', 'Coffee'], series: [10, 25, 5, 20] }, { labelPosition: 'outside', labelOffset: 16, labelDirection: 'explode', chartPadding: 30 } ); ``` `index.html`: ```html
``` #### Label text `labelTransform` gets each slice's label (or its value, when there are no labels) and its index, and returns the text to show. Return `null` to leave a slice without a label: ```ts import 'chartist/styles.css'; import { pieChart } from 'chartist'; const series = [10, 25, 5, 20]; const total = series.reduce((sum, value) => sum + value, 0); pieChart( '#chart', { series }, { // 17%, 42% and 33%; the 8% slice stays unlabeled. labelTransform: value => { const percent = Math.round((Number(value) / total) * 100); return percent < 10 ? null : `${percent}%`; } } ); ``` A `\n` in a label starts a new line, centered on the label's place, for a name with its share below: ```ts import 'chartist/styles.css'; import { pieChart } from 'chartist'; pieChart( '#chart', { labels: ['Apples\n60%', 'Pears\n40%'], series: [60, 40] }, { donut: true, donutWidth: 50 } ); ``` Small slices crowd their labels. `preventOverlappingLabelOffset` is a px step: when a label would overlap one drawn before it, it moves up and to the left by that step until it doesn't. ### Slices with names and colors A value can also be an object. `className` replaces the slice's letter class, so you pick its color in CSS. `name` and `meta` end up in the SVG as `ct:series-name` and `ct:meta` attributes, for plugins and your own scripts: ```ts import 'chartist/styles.css'; import { pieChart } from 'chartist'; pieChart('#chart', { labels: ['Yes', 'No'], series: [ { value: 62, className: 'vote-yes' }, { value: 38, className: 'vote-no' } ] }); ``` ```css .vote-yes { --ct-color: #59922b; } .vote-no { --ct-color: #d70206; } ``` More on series colors in [Styling with CSS](https://chartist.dev/docs/styling-with-css/#one-color-per-series). A `0` still gets a label and a sliver of a slice. Set `ignoreEmptyValues: true` to skip both. In the API reference: [`pieChart`](https://chartist.dev/api/charts/#pieChart) and [its options](https://chartist.dev/api/charts/#PieChartPresetOptions). Next: [put bars and a line on one chart](https://chartist.dev/docs/composed-charts/), or [make the pie respond to its container](https://chartist.dev/docs/responsive/). ## Composed charts > Build charts from axes and layers with chart(), for bars and a line on two Y axes, axes on any side and anything the presets don't cover. `lineChart()`, `barChart()` and `pieChart()` are presets: ready-made recipes over one function, `chart()`. When a recipe doesn't fit, you cook with the same parts yourself: a list of axes, and a list of layers that draw on them. ### Preset or compose? Use a preset when your chart is a line, bar or pie chart. Presets take the familiar options, handle responsive options and give you the classic markup in one call. Compose when you need something a preset can't do: - bars and a line in one chart, - two value scales, like millimeters on the left and degrees on the right, - several axes on one side, or an X axis at the top, - [your own layer](https://chartist.dev/docs/custom-layers/) next to the built-in ones. ### Bars and a line on two axes A climate chart: rain as bars on the left axis, temperature as a line on the right axis, each on its own scale. **Rain and temperature** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { autoScaleAxis, axisGrid, axisLabels, bar, cartesianAxes, chart, line, stepAxis } from 'chartist'; const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; chart('#chart', { data: { labels: months, series: [ // Salmon bars and a red line. { name: 'rain', className: 'ct-series-b', data: [64, 58, 70, 82, 104, 120] }, { name: 'temperature', className: 'ct-series-a', data: [1, 3, 8, 12, 16, 20] } ] }, axes: cartesianAxes([ { id: 'month', side: 'bottom', type: stepAxis() }, { id: 'mm', side: 'left', type: autoScaleAxis(), low: 0 }, { id: 'celsius', side: 'right', type: autoScaleAxis(), low: 0 } ]), layers: [ axisGrid({ axes: ['month', 'mm'] }), bar({ series: ['rain'], x: 'month', y: 'mm' }), line({ series: ['temperature'], x: 'month', y: 'celsius', align: 'center' }), axisLabels() ] }); ``` `index.html`: ```html
``` Three things make this work: - **Axes have ids.** `cartesianAxes()` takes a list of axes, each with an `id`, a `side` and a `type`. - **Layers pick their series and axes.** `bar({ series: ['rain'], x: 'month', y: 'mm' })` draws the series named `rain` against the `month` and `mm` axes. - **Each axis scales to its own series.** The `mm` axis only looks at the rain, the `celsius` axis only at the temperature. ### Axes Every axis in `cartesianAxes([...])` has: | Field | What it is | | ------ | ----------------------------------------------------------------------- | | `id` | The name layers use to draw on it. Ids must be unique | | `side` | `'bottom'` or `'top'` for an X axis, `'left'` or `'right'` for a Y axis | | `type` | `stepAxis()`, `autoScaleAxis()` or `fixedScaleAxis()` | On top of that, an axis takes the same options as `axisX` and `axisY` in the presets: `offset`, `low`, `high`, `onlyInteger`, `ticks`, `showGrid`, `showLabel`, `labelTransform` and the rest. [Axes and scales](https://chartist.dev/docs/axes-and-scales/) explains the types and their options. One thing goes beyond the presets: **several axes can share a side.** They stack outward in the order you list them, each with its own offset. ```ts nocheck cartesianAxes([ { id: 'month', side: 'bottom', type: stepAxis() }, // Both on the left: 'eur' next to the chart, 'usd' outside it. { id: 'eur', side: 'left', type: autoScaleAxis() }, { id: 'usd', side: 'left', type: autoScaleAxis(), offset: 50 } ]); ``` ### Layers Layers draw in the order you list them, each on top of the ones before. Put the grid first and the labels last. | Layer | Draws | Options | | -------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `line()` | Lines, points and areas | `series`, `x`, `y`, `align`, `lineSmooth`, `showLine`, `showPoint`, `showArea`, `areaBase` | | `bar()` | Bars | `series`, `x`, `y`, `horizontalBars`, `stackBars`, `stackMode`, `seriesBarDistance`, `distributeSeries`, `offset` | | `axisGrid()` | Grid lines (`g.ct-grids`) | `axes` (default: all), `background` | | `axisLabels()` | Axis labels (`g.ct-labels`) | `axes` (default: all), `labels` (`'html'` or `'svg'`) | | `band()` | A band between two edges (`g.ct-band`) | `series`, `low`, `high`, `x`, `y`, `align`, `lineSmooth`, `mark` | `series` picks series by `name` or by index; leave it out and the layer draws all of them. `x` and `y` are axis ids; leave them out and the layer uses the first X and the first Y axis. An unknown name or id throws an error that names the layer. Grid and label layers draw for every axis unless you name some: the example draws the grid of `month` and `mm` only, so the right axis doesn't add a second set of lines. Bands have [a guide of their own](https://chartist.dev/docs/bands/). Target lines, trend lines and axis titles are layers of [`@chartist-js/essentials`](https://chartist.dev/packages/essentials/), and work in composed charts the same way. Series keep their place in the data: the temperature is the second series, so it would be `ct-series-b` even though it's the only series its layer draws. Your [series colors](https://chartist.dev/docs/styling-with-css/#one-color-per-series) stay put when you move a series to another layer. (The example picks `ct-series-e` with `className`, the dark ink that stands apart from the red bars.) #### Lining up with bars Bars sit in the middle of each step, while line points sit at the start of it by default. `align: 'center'` moves the points to the bar centers, as in the example. Bars grow from zero on their value axis, so make sure zero is on it: `low: 0`, or `referenceValue: 0` to include zero and still scale to the data. #### Grouped stacks A bar layer with `stackBars` stacks all its series into one bar per label. For two stacks side by side, use two bar layers, one per group, and move them apart with `offset` (in px, along the label axis): **Grouped stacks** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { autoScaleAxis, axisGrid, axisLabels, bar, cartesianAxes, chart, stepAxis } from 'chartist'; // New and returning customers per quarter: one stack per year. chart('#chart', { data: { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [ { name: '2024 new', data: [5, 3, 4, 6] }, { name: '2024 returning', data: [2, 2, 3, 3] }, { name: '2025 new', data: [4, 6, 5, 7] }, { name: '2025 returning', data: [3, 4, 4, 5] } ] }, axes: cartesianAxes({ x: { type: stepAxis() }, y: { type: autoScaleAxis(), low: 0 } }), layers: [ axisGrid(), // Each layer stacks its own year; the offsets put the stacks side by side. bar({ series: ['2024 new', '2024 returning'], stackBars: true, offset: -8 }), bar({ series: ['2025 new', '2025 returning'], stackBars: true, offset: 8 }), axisLabels() ], // The bar chart class centers the X labels under the bars. className: 'ct-chart-bar' }); ``` `index.html`: ```html
``` Each layer stacks only the series it picks, and the value axis fits both stacks. ### The rest of the config Next to `data`, `axes` and `layers`, `chart()` takes: | Option | What it does | | ----------------- | ------------------------------------------------------------------------------------------------- | | `padding` | Space around the chart in px: a number, or `{ top, right, bottom, left }` (default 15, 15, 5, 10) | | `className` | Classes for the ``. By default the chart type's class, from the layers | | `width`, `height` | A fixed size; by default the chart fills its container | | `labels` | `'html'` or `'svg'` axis labels for every label layer | The class matters for CSS: the default stylesheet aligns labels by chart type. Without a `className`, a chart with a `bar()` layer gets `ct-chart-bar`, which centers the X labels under the bars, a pie gets `ct-chart-pie` (or `ct-chart-donut`), and anything else gets `ct-chart-line`. A `className` of your own replaces it, so add the chart type's class to it if you want that alignment. In the API reference: [`chart()`](https://chartist.dev/api/chart/), [layers](https://chartist.dev/api/layers/) and [scales and axes](https://chartist.dev/api/scales/). Next: [learn what each axis type does](https://chartist.dev/docs/axes-and-scales/), or [keep the chart up to date with new data](https://chartist.dev/docs/live-updates/). ## Data and options > What a chart's data looks like, from plain arrays to named series with metadata, and how options set defaults for every series or just one. Every chart function takes the same three things: where to draw, what to draw and how. The container comes first, the data second, the options third. This page is about the last two. Data is plain JavaScript: arrays of numbers, and objects when a series or a value needs a name or extra information. Options are plain objects too, merged over sensible defaults, so you only write down what you want to change. ### Labels and series The simplest data is a list of labels and a list of series, where each series is an array of numbers. On a line chart, every series is a line and the labels go along the X axis, one per value. ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; lineChart('#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [ [12, 9, 7, 8, 5], [2, 1, 3.5, 7, 3] ] }); ``` Series don't need the same length. Chartist pads the shorter ones with gaps up to the longest series, and the labels with empty strings, so nothing shifts out of place. Pie and donut charts take one flat series instead, a single array of numbers, one per slice. See [Pie and donut charts](https://chartist.dev/docs/pie-and-donut-charts/). ### Series objects When a series needs more than its numbers, write it as an object and put the values in `data`: ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; lineChart('#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [ { name: 'revenue', className: 'revenue', data: [5, 7, 6, 9] }, { name: 'costs', meta: { unit: 'kCHF' }, data: [3, 4, 4, 5] } ] }); ``` | Property | What it does | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The values, as in a plain array series | | `name` | Identifies the series for [per-series options](#options-per-series); also written to the series group as `ct:series-name` | | `className` | Replaces the letter class (`ct-series-a`, …) on the series group, see [one color per series](https://chartist.dev/docs/styling-with-css/#one-color-per-series) | | `meta` | Anything you want to keep with the series; written as `ct:meta` and passed to event listeners and plugins | Plain arrays and objects mix freely in one `series` list. ### Value objects The same goes for a single value: `{ value, meta }` instead of a number. The metadata ends up on that value's point or bar as `ct:meta`, which is where a tooltip plugin finds it. More in [Events and plugins](https://chartist.dev/docs/events-and-plugins/). ```ts import 'chartist/styles.css'; import { barChart } from 'chartist'; barChart('#chart', { labels: ['Jan', 'Feb', 'Mar'], series: [ [{ value: 4, meta: 'A slow start' }, 7, { value: 12, meta: 'Launch' }] ] }); ``` ### Gaps in the data Missing a value? Use `null`. Chartist treats `null`, `undefined` and `NaN` as a hole: a line breaks there and draws no point, a bar chart leaves the slot empty. The value keeps its place, so the labels still line up. ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; lineChart('#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[12, 9, null, 8, 5]] }); ``` To bridge the gap with a line instead, give the line chart an interpolation with `fillHoles`: ```ts import { Interpolation, lineChart } from 'chartist'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[12, 9, null, 8, 5]] }, { lineSmooth: Interpolation.monotoneCubic({ fillHoles: true }) } ); ``` [Line charts](https://chartist.dev/docs/line-charts/#gaps-in-your-data) shows how gaps look with each curve. ### Two-dimensional values Labels put values on evenly spaced steps. When the X position is data too (timestamps, measurements), give each value an `x` and a `y`, and make the X axis a scale instead of steps: ```ts import 'chartist/styles.css'; import { autoScaleAxis, lineChart } from 'chartist'; lineChart( '#chart', { series: [ [ { x: 1, y: 100 }, { x: 2, y: 50 }, { x: 4, y: 75 }, { x: 9, y: 20 } ] ] }, { axisX: { type: autoScaleAxis() } } ); ``` The points now sit at their X value, not at their position in the array. Axis types and their options have their own page: [Axes and scales](https://chartist.dev/docs/axes-and-scales/). ### The options argument Options are the third argument. Every chart type has defaults, and your options are merged over them, deeply: `axisX: { showGrid: false }` turns off the X grid and keeps every other X axis default. ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[12, 9, 7, 8, 5]] }, { low: 0, showArea: true, axisX: { showGrid: false } } ); ``` What you won't find in options: colors, line widths and fonts. Those belong in your stylesheet, see [Styling with CSS](https://chartist.dev/docs/styling-with-css/). Sizes are options and CSS both, see [Sizing charts](https://chartist.dev/docs/sizing-charts/). ### Options per series Sometimes one series should look different from the rest: a smooth line next to a jagged one, one area filled and one not. On a line chart, the `series` option holds overrides by series name. Each key is the `name` of a series, each value the options that series gets instead of the chart's. **Options per series** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], series: [ { name: 'visits', data: [12, 9, 7, null, 5, 8, 11] }, { name: 'signups', data: [2, 1, 3.5, 7, 3, 4, 6] } ] }, { // For every series low: 0, showArea: true, // Only for the series named "signups" series: { signups: { lineSmooth: false, showArea: false } } } ); ``` `index.html`: ```html
``` A series can override `lineSmooth`, `showLine`, `showPoint`, `showArea` and `areaBase`. Everything else (axes, padding, `low` and `high`) is chart-wide. Series without a `name`, or without an entry in `series`, use the chart's options. > **Responsive, per series.** Overrides work in responsive options too: a > `series` entry in a media query override changes one series on small screens. > See [Responsive sugar topping](https://chartist.dev/docs/responsive/). ### Where the full lists live Each chart type page lists its options with their defaults: [Line charts](https://chartist.dev/docs/line-charts/), [Bar charts](https://chartist.dev/docs/bar-charts/) and [Pie and donut charts](https://chartist.dev/docs/pie-and-donut-charts/). A generated API reference follows later. In the API reference: [the data types](https://chartist.dev/api/data/), and the options of [`lineChart`](https://chartist.dev/api/charts/#LineChartPresetOptions), [`barChart`](https://chartist.dev/api/charts/#BarChartPresetOptions) and [`pieChart`](https://chartist.dev/api/charts/#PieChartPresetOptions). Next: [give your charts the right size](https://chartist.dev/docs/sizing-charts/). ## Axes and scales > Step axes, auto-scaled and fixed axes, XY plots with two-dimensional data, label formatting, and how the presets pick their axes. An axis decides where a value lands. The line and bar charts come with sensible axes, and they cover a lot. When they don't, you swap an axis type or tune its options: plot values along X too, fix the ticks you care about, or keep the scale to whole numbers. ### Three kinds of axes Chartist has three axis types, each created by a function: | Type | Places a value by | Ticks | | ------------------ | ---------------------------------------- | ------------------------------------------------------------- | | `stepAxis()` | Its index: first, second, third… | One per step: the data's `labels`, or the `ticks` you give | | `autoScaleAxis()` | Its value, on a scale fitted to the data | Round numbers, at least `scaleMinSpace` px apart (default 20) | | `fixedScaleAxis()` | Its value, between `low` and `high` | The `ticks` you give, or `divisor` evenly spaced ones | A step axis doesn't look at values at all, which is right for categories like weekdays or quarters. The two scale axes are continuous: a value of 7 lands at 70% of an axis from 0 to 10. For values that span several orders of magnitude, [`@chartist-js/essentials`](https://chartist.dev/packages/essentials/) has a fourth type, [`logAxis()`](https://chartist.dev/packages/essentials/log-axis/): it places a value by its logarithm, so 10, 100 and 1,000 are evenly spaced. ### How the presets choose | Preset | X axis | Y axis | | ------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | `lineChart()` | A step axis over `labels`; `fullWidth: true` stretches it | An auto scale | | `barChart()` | A step axis over `labels` | An auto scale that includes zero, unless you set `low` or `high`, on the chart or the axis | | `pieChart()` | None: a pie has no axes | | With `horizontalBars: true`, the bar chart swaps them: the labels go down the Y axis and the values along X. The labels in your data only become ticks through that default step axis. Give `axisX` another `type` and the labels have nothing left to do. ### Tuning the auto scale The auto scale fits the data and picks round steps. These axis options steer it: | Option | What it does | | ---------------- | -------------------------------------------------------------------------------------------------- | | `low`, `high` | The range of the scale; an auto scale rounds its ends out to a step, `fixedScaleAxis()` keeps them | | `onlyInteger` | Steps of whole numbers only | | `scaleMinSpace` | The smallest distance between two ticks in px; larger values give fewer ticks | | `referenceValue` | A value the scale includes, like a zero line; `low` and `high` win over it | Small counts are where `onlyInteger` shines. Without it, this chart would put ticks between the whole numbers: **Only whole bugs** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; // Bugs found per day: nobody finds half a bug. lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[2, 3, 1, 4, 2]] }, { axisY: { onlyInteger: true, low: 0 } } ); ``` `index.html`: ```html
``` ### Label text Every axis takes a `labelTransform`. It gets each tick's value and index and returns the label to show. Return `null` and the tick is skipped, label and grid line both: ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; const days = Array.from({ length: 30 }, (_, i) => i + 1); lineChart( '#chart', { labels: days, series: [days.map(day => Math.round(12 + 6 * Math.sin(day / 4)))] }, { axisX: { // A label every five days. labelTransform: (value, index) => index % 5 === 0 ? `Day ${value}` : null }, axisY: { labelTransform: value => `${value} °C` } } ); ``` To keep a grid line but hide its label, return an empty string instead. #### Aligning labels SVG text labels (`labels: 'svg'`) sit in a box per tick, aligned as the default stylesheet aligns HTML labels. `labelAlign` on an axis moves them within it, with `horizontal` (`'start'`, `'middle'`, `'end'`) and `vertical` (`'top'`, `'middle'`, `'bottom'`): ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu'], series: [[3, 5, 4, 6]] }, { labels: 'svg', axisX: { labelAlign: { horizontal: 'middle' } }, axisY: { labelAlign: { horizontal: 'end' } } } ); ``` In a composed chart, `axisLabels({ align })` sets the alignment for all its axes, and an axis's own `labelAlign` wins. HTML labels ignore both: align them with CSS. ### A line chart is only 1-dimensional?! A step axis places values by index, so a plain series is all it needs. Under the hood, Chartist turns every value into a point with an empty `x`: ```ts nocheck [5, 2, 8]; // becomes [ { x: undefined, y: 5 }, { x: undefined, y: 2 }, { x: undefined, y: 8 } ]; ``` Which means you can fill in `x` yourself. Give the series `{ x, y }` points and the X axis a scale type, and the line chart becomes an XY plot. You don't need `labels` anymore: ```ts import 'chartist/styles.css'; import { autoScaleAxis, lineChart } from 'chartist'; lineChart( '#chart', { series: [ [ { x: 1, y: 2 }, { x: 2.5, y: 5 }, { x: 7, y: 3 }, { x: 10, y: 8 } ] ] }, { axisX: { type: autoScaleAxis(), onlyInteger: true } } ); ``` The points now sit at their real distances: the gap between 2.5 and 7 is three times the gap between 1 and 2.5, as it should be. ### Fixed stuff can be sweet too! Sometimes you know which ticks matter. `fixedScaleAxis()` puts the scale between `low` and `high` and draws exactly the `ticks` you give it. Here it gives a day its four quarters, with temperature readings taken at odd hours. `showLine: false` turns the line chart into a scatter plot: **Readings at odd hours** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { fixedScaleAxis, lineChart } from 'chartist'; // Temperature readings, taken whenever someone remembered to. lineChart( '#chart', { series: [ [ { x: 1, y: 9 }, { x: 4, y: 7 }, { x: 7, y: 8 }, { x: 8, y: 11 }, { x: 9, y: 13 }, { x: 12, y: 18 }, { x: 15, y: 21 }, { x: 16, y: 20 }, { x: 20, y: 15 }, { x: 23, y: 11 } ] ] }, { showLine: false, axisX: { type: fixedScaleAxis(), low: 0, high: 24, ticks: [0, 6, 12, 18, 24] } } ); ``` `index.html`: ```html
``` Without `low` and `high`, the scale runs from the smallest to the largest value in the data. Instead of `ticks`, you can give a `divisor`: it splits the range into that many equal parts, with a tick at each end. With `low: 0`, `high: 100` and `divisor: 4`, the ticks are 0, 25, 50, 75 and 100. > **The same works on Y.** `axisY: { type: fixedScaleAxis(), ticks: [0, 50, 100], low: 0, high: 100 }` keeps a percentage chart on a 0 to 100 scale with exactly three grid lines. ### Axes in composed charts The axis types and options are the same in [composed charts](https://chartist.dev/docs/composed-charts/), where you list the axes yourself, give them ids and put them on any side. In the API reference: [the axis options](https://chartist.dev/api/charts/#LineChartPresetOptions.axisX) and [scales and axes](https://chartist.dev/api/scales/). Next: [compose bars and a line on two axes](https://chartist.dev/docs/composed-charts/), or [animate a chart](https://chartist.dev/docs/animations/). ## Sizing charts > Give a chart a responsive aspect ratio with a class, a fixed size with options, or any size CSS can describe. Chartist fills the box and redraws when it changes. A chart fills its container. It measures the box, draws an SVG of that size, and draws again when the box changes. So sizing a chart means sizing its container, and you have three ways to do it: an aspect ratio class, fixed options, or your own CSS. ### Aspect ratios Images and videos on a responsive page keep their proportions as they scale: they're 4:3 or 16:9, not 320 by 240 pixels. Charts should too. Add a ratio class to the container, and the chart is as wide as its parent and as tall as the ratio says: ```html
``` No `width` or `height` in the options. Press **Edit**, then try another class in `index.html`: **Aspect ratio classes** (runs in the browser): `index.html`: ```html
``` `src/main.ts`: ```ts import 'chartist/styles.css'; import { barChart } from 'chartist'; barChart('#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [ [5, 4, 3, 7], [3, 2, 9, 5] ] }); ``` The classes come with `chartist/styles.css` and are named after musical intervals, from a square to four times as wide as tall: | Class | Width : height | | ------------------- | -------------- | | `ct-square` | 1 : 1 | | `ct-minor-second` | 16 : 15 | | `ct-major-second` | 9 : 8 | | `ct-minor-third` | 6 : 5 | | `ct-major-third` | 5 : 4 | | `ct-perfect-fourth` | 4 : 3 | | `ct-perfect-fifth` | 3 : 2 | | `ct-minor-sixth` | 8 : 5 | | `ct-golden-section` | 1.618 : 1 | | `ct-major-sixth` | 5 : 3 | | `ct-minor-seventh` | 16 : 9 | | `ct-major-seventh` | 15 : 8 | | `ct-octave` | 2 : 1 | | `ct-major-tenth` | 5 : 2 | | `ct-major-eleventh` | 8 : 3 | | `ct-major-twelfth` | 3 : 1 | | `ct-double-octave` | 4 : 1 | Need another ratio? With Sass, the `$ct-scales` and `$ct-scales-names` settings define the list (keep them the same length); see [Sass](https://chartist.dev/docs/styling-with-css/#sass). ### Fixed sizes Sometimes a chart has a fixed spot: a 300 by 200 pixel card, a print layout. Set `width` and `height` in the options: ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[12, 9, 7, 8, 5]] }, { width: 300, height: 200 } ); ``` A number is pixels. A string can be any CSS length, `'20rem'` or `'50%'`: Chartist sets it on the SVG and measures what the browser makes of it. You can fix one side and leave the other to the container. ### Your own CSS The ratio classes are one way to give the container a size; anything that does is fine. A height in CSS works: ```css #chart { height: 240px; } ``` So does CSS `aspect-ratio`, with one line you must not forget: ```css #chart { aspect-ratio: 16 / 9; min-height: 0; } ``` > **`aspect-ratio` needs `min-height: 0`.** To measure the container, Chartist > places a probe SVG of 100% width and height in it. An `aspect-ratio` box can > grow with its content, probe included, and then the chart comes out too tall. > `min-height: 0` stops that. The `ct-*` classes don't use `aspect-ratio` and > aren't affected. A container with no height of its own, like an empty `div`, gets a chart 150 pixels tall: the browser's default height for an SVG. If a chart comes out that height whatever you do, the container's height is the place to look. ### More than one chart on a page Every chart needs its own container. A selector string draws into the first element that matches it, so give each container an id: ```html
``` ```ts import 'chartist/styles.css'; import { barChart, lineChart } from 'chartist'; lineChart('#visits', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[12, 9, 7, 8, 5]] }); barChart('#revenue', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [[5, 4, 3, 7]] }); ``` Or pass the elements themselves, for example to draw one chart per card: ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; for (const element of document.querySelectorAll('.sparkline')) { lineChart(element, { series: [[3, 5, 2, 6, 4]] }); } ``` ### What happens on resize Nothing you need to wire up. Chartist watches its container with a `ResizeObserver` and redraws when the size changes, whether the window was resized, a sidebar collapsed or the layout moved the chart. A chart with a fixed `width` and `height` keeps its size. To change what the chart draws at a new size, not only how big it is, use [responsive options](https://chartist.dev/docs/responsive/). To stop watching, call `detach()` on the chart; see [Live updates](https://chartist.dev/docs/live-updates/#detach-and-destroy). In the API reference: [`width`](https://chartist.dev/api/charts/#LineChartPresetOptions.width), [`height`](https://chartist.dev/api/charts/#LineChartPresetOptions.height) and [`chartPadding`](https://chartist.dev/api/charts/#LineChartPresetOptions.chartPadding). Next: [style your charts with CSS](https://chartist.dev/docs/styling-with-css/). ## Styling with CSS > Chartist draws plain SVG with predictable classes, so you style charts with the CSS you already write, from one custom property to a full theme. Chartist has no theme option, and no color settings in JavaScript. Charts are SVG elements with predictable class names, and their look lives in CSS, next to the rest of your styles. When the brand color changes, you change one line in a stylesheet, not every chart's options. There are three ways in, from quick to complete: custom properties for the common things, plain selectors for everything else, and Sass if you want to build your own stylesheet. ### Custom properties The default stylesheet, `chartist/styles.css`, reads its values from custom properties and falls back to the classic Chartist look. Set one, and every chart in its scope follows. Press **Edit** and try other values: **Custom properties** (runs in the browser): `src/style.css`: ```css #chart { --ct-series-a: #9a6a45; --ct-series-b: #f4c63d; --ct-line-width: 2px; --ct-point-size: 8px; --ct-grid-dasharray: none; } ``` `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { lineChart } from 'chartist'; lineChart('#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [ [12, 9, 7, 8, 5], [2, 1, 3.5, 7, 3] ] }); ``` `index.html`: ```html
``` | Property | Default | What it sets | | --------------------------------- | --------------------- | ------------------------------------ | | `--ct-series-a` … `--ct-series-z` | the 15 classic colors | The color of each series, in order | | `--ct-line-width` | `4px` | Line thickness | | `--ct-line-dasharray` | `none` | Dashed lines | | `--ct-point-size` | `10px` | Point size | | `--ct-point-shape` | `round` | `round` or `square` points | | `--ct-area-opacity` | `0.1` | Opacity of the area below a line | | `--ct-bar-width` | `10px` | Bar thickness | | `--ct-donut-width` | `60px` | Donut ring thickness | | `--ct-grid-color` | `rgba(0, 0, 0, 0.2)` | Grid lines | | `--ct-grid-width` | `1px` | Grid line thickness | | `--ct-grid-dasharray` | `2px` | Grid dashes (`none` for solid lines) | | `--ct-grid-background-fill` | `none` | Fill behind the grid | | `--ct-grid-minor-color` | `rgba(0, 0, 0, 0.08)` | Minor grid lines, between the ticks | | `--ct-grid-minor-width` | `1px` | Minor grid line thickness | | `--ct-grid-minor-dasharray` | `none` | Minor grid dashes | | `--ct-label-color` | `rgba(0, 0, 0, 0.4)` | Axis and slice labels | | `--ct-label-size` | `0.75rem` | Label font size | A donut's `donutWidth` option wins over `--ct-donut-width`. Without it, the chart lays the ring out for the width the stylesheet draws. A server render has no stylesheet to read and lays it out for 60 px, so set `donutWidth` there. More in [Pie and donut charts](https://chartist.dev/docs/pie-and-donut-charts/#donuts). They're regular custom properties, so they cascade. Set them where they should apply: ```css /* The whole site */ :root { --ct-series-a: #c8102e; } /* Every chart in the sidebar */ .sidebar { --ct-line-width: 2px; } /* One chart */ #revenue { --ct-grid-dasharray: none; } ``` #### Dark pages Labels and grid lines follow the page's color scheme: where `color-scheme: dark` applies, they turn light. Declare it on the page or on the chart's container; the OS setting alone doesn't change anything, since a light page should keep its colors in dark mode: ```css :root { color-scheme: light dark; } ``` `--ct-label-color` and `--ct-grid-color` still win. The series colors stay as they are; for colors that are clear on both, use the [accessible theme](#colors-for-everyone). ### One color per series Each series is a group with a letter class: the first is `ct-series-a`, the second `ct-series-b`, up to `ct-series-z`, and the 27th starts again at `ct-series-a`. The letter class sets `--ct-color`, and the series' lines, points, bars, areas and slices use it as their stroke or fill. There are 15 colors, so from the 16th series they repeat: `ct-series-p` gets the first series' color and follows `--ct-series-a`, unless you set `--ct-series-p`. That gives you two levels to work at: ```css /* Recolor the second series everywhere */ :root { --ct-series-b: #f4c63d; } /* Or recolor one series in one chart */ #sales .ct-series-a { --ct-color: #9a6a45; } ``` Want a meaningful name instead of a letter? Give the series a `className`. It replaces the letter class, so the series has no default color and takes the one you give it: ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; lineChart('#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [ { className: 'revenue', data: [5, 7, 6, 9] }, { className: 'costs', data: [3, 4, 4, 5] } ] }); ``` ```css .revenue { --ct-color: #b07800; } .costs { --ct-color: #d70206; } ``` > **Stroke or fill?** SVG colors lines with `stroke` and shapes with `fill`. > Setting `--ct-color` covers both, which is why it's the easiest way to recolor > a series. ### Plain selectors for the rest Custom properties cover the common cases. For anything else, write CSS against the classes Chartist draws: | Element | Classes | | -------------------- | ----------------------------------------------------------------------------------------------------- | | The chart's SVG | `ct-chart-line`, `ct-chart-bar` (plus `ct-horizontal-bars`), `ct-chart-pie`, `ct-chart-donut` | | A series | `ct-series` and `ct-series-a` … (or its `className`) | | Lines, points, areas | `ct-line`, `ct-point`, `ct-area` | | Bars | `ct-bar` | | Slices | `ct-slice-pie`, `ct-slice-donut` | | Grid | `ct-grids` (the group), `ct-grid`, with `ct-horizontal` or `ct-vertical` | | Labels | `ct-labels` (the group), `ct-label`, with `ct-horizontal` or `ct-vertical` and `ct-start` or `ct-end` | Anything SVG and CSS can do works here: transitions, hover states, filters. ```css /* Square bar ends and a hover highlight */ .ct-bar { stroke-linecap: square; transition: opacity 0.2s; } .ct-series:hover .ct-bar { opacity: 0.6; } /* Only horizontal grid lines */ .ct-grid.ct-horizontal { display: none; } ``` ### Colors for everyone The classic colors are kept for compatibility, and some of their pairs are hard to tell apart for people with color vision deficiencies. Chartist ships an opt-in palette checked for that, with light and dark variants: ```ts import 'chartist/styles.css'; import 'chartist/themes/accessible.css'; ``` ```html … ``` Add the class to the page or to one chart's container. The palette follows the element's `color-scheme`, so it switches with your dark mode as long as your dark theme declares `color-scheme: dark`. > **Dark mode for labels and grid.** The default label and grid colors are > translucent black. In a dark theme, set `--ct-label-color` and > `--ct-grid-color` to light values alongside your other dark colors. ### Sass If your build already uses Sass, configure the stylesheet at its source instead of overriding it. Every setting is a `!default` variable: ```scss @use 'chartist/scss/index' with ( $ct-series-names: ( a, b, c ), $ct-series-colors: ( #9a6a45, #f4c63d, #c8102e ), $ct-line-width: 2px, $ct-grid-dasharray: false ); ``` With fewer colors than names, the colors repeat in order, as in the default stylesheet. The custom properties still work on top of the compiled CSS. Set `$ct-include-classes: false` to emit nothing and use the `ct-chart-*` mixins in your own selectors. In the API reference: [`classNames`](https://chartist.dev/api/charts/#LineChartPresetOptions.classNames), to use class names of your own. Next: [make charts respond to the screen and their container](https://chartist.dev/docs/responsive/), or [animate them with CSS](https://chartist.dev/docs/animations/). ## Responsive sugar topping > Change what a chart draws, not only its size, with media queries per chart, and experimentally with container queries. A chart that scales is a good start, and Chartist [does that by itself](https://chartist.dev/docs/sizing-charts/). But a chart that looks right at 900 pixels is often a mess at 320: labels collide, points crowd each other, the area hides the line behind it. Shrinking it isn't enough; on a small screen it should draw less. Media queries already do this for your styles. Responsive options do it for a chart's options: pairs of a condition and the options that apply while it matches. ### Media queries The chart functions take responsive options as a fourth argument, after the options. Each entry is `[mediaQuery, overrides]`: **Responsive options** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; lineChart( '#chart', { labels: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], series: [ { name: 'visits', data: [12, 9, 7, 8, 5] }, { name: 'signups', data: [2, 1, 3.5, 7, 3] } ] }, { low: 0, showArea: true }, [ // Up to 640px: short day names and no points [ '(max-width: 640px)', { showPoint: false, axisX: { labelTransform: value => String(value).slice(0, 3) } } ], // Up to 400px: both entries match, and this one comes last, // so its labels win. Points stay off from the first entry. [ '(max-width: 400px)', { axisX: { labelTransform: value => String(value)[0] }, series: { signups: { showArea: false } } } ] ] ); ``` `index.html`: ```html
``` The live preview is a page of its own, so its width is the "screen" these queries see. On a wide page it shows full day names. On a phone, or in a narrow preview, it drops the points and shortens the labels. Chartist checks the queries with `matchMedia()` and redraws when one starts or stops matching. The options that don't depend on the screen stay in the third argument; the fourth only holds what changes. ### Order and merging Every matching entry applies, not only the first one. They're merged over your options in the order you list them, so a later entry wins where two set the same option. Write them from the general to the specific: `(max-width: 640px)` first, `(max-width: 400px)` after it. Below 400 pixels both match, and the second only needs what's different. The merge is deep, like the merge of [options over defaults](https://chartist.dev/docs/data-and-options/#the-options-argument): `axisX: { labelTransform }` changes the labels and keeps the other X axis options. That includes [options per series](https://chartist.dev/docs/data-and-options/#options-per-series), which is how the example above drops the area of one series on small screens only. ### Responsive `chart()` `chart()` takes responsive overrides too, in its config as `responsive: responsive([...])`. The entries look the same, and the overrides are part of the config: padding, axes, layers, size. ```ts import 'chartist/styles.css'; import { autoScaleAxis, axisGrid, axisLabels, cartesianAxes, chart, line, responsive, stepAxis } from 'chartist'; const labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']; chart('#chart', { data: { labels, series: [[12, 9, 7, 8, 5]] }, axes: cartesianAxes([ { id: 'x', side: 'bottom', type: stepAxis() }, { id: 'y', side: 'left', type: autoScaleAxis(), low: 0 } ]), layers: [axisGrid(), line({ showArea: true }), axisLabels()], responsive: responsive([ [ '(max-width: 640px)', { padding: 5, layers: [axisGrid(), line({ showPoint: false }), axisLabels()] } ] ]) }); ``` Here the merge is shallow: an override replaces a top-level key as a whole. That's why the entry above lists all three layers, not only the line. Responsive support is opt-in through `responsive()`, so a `chart()` without it doesn't bundle the code that watches the queries. ### Container queries > **`container()` is experimental.** It needs `Element.matchContainer()`, which > no browser ships yet ([csswg-drafts#6205](https://github.com/w3c/csswg-drafts/issues/6205)). > Until then, load the [`match-container`](https://github.com/teetotum/match-container) > polyfill before your charts: `import 'match-container'` from npm, or a > ` ``` Container entries mix with media query strings in one list, and they merge the same way: in order, every matching entry. Chartist watches the container's size and redraws when an entry starts or stops matching. They work with the chart functions and with `chart()`; the 1.x classes ignore them. > **Why no live example?** The live examples run in a sandbox without the > polyfill, and this site loads nothing from other servers. The [responsive demo > on the home page](https://chartist.dev/#responsive) uses container queries, polyfill > included: drag its corner to see them work. In the API reference: [responsive options](https://chartist.dev/api/responsive/). Next: [style what you've drawn](https://chartist.dev/docs/styling-with-css/), or [animate it](https://chartist.dev/docs/animations/). ## Animations > Draw lines in, grow bars and reveal charts on scroll with CSS, reach for SMIL where CSS can't go, animate updates, and respect people who prefer less motion. Chartist has no animation option. Charts are SVG elements with classes, and CSS animates SVG well: keyframes, transitions, delays and scroll timelines all work on `.ct-line` like on any other element. Animations stay in your stylesheet, next to the rest of the chart's look. CSS covers most of it. For the few SVG attributes CSS can't reach, there's SMIL, for charts that change while people watch, there are update animations, and for everyone who'd rather not see things move, there's `prefers-reduced-motion`. ### Draw lines in The classic trick: give a line a dash as long as the line itself, push the dash out of view with `stroke-dashoffset`, and animate the offset back to zero. The catch is the length. Every line has a different one, and it changes with the chart's size. `pathLength` solves that. With `pathLength="1"`, the browser treats the path as one unit long, whatever its real length, so `stroke-dasharray: 1` and `stroke-dashoffset: 1` hide any line. A tiny [plugin](https://chartist.dev/docs/events-and-plugins/) adds the attribute while the chart is built. Press **Edit** and change the timings: **Draw lines in** (runs in the browser): `src/style.css`: ```css #chart .ct-line { stroke-dasharray: 1; stroke-dashoffset: 1; animation: draw 1.2s ease-out forwards; } /* The second line starts a little later. */ #chart .ct-series-b .ct-line { animation-delay: 0.3s; } /* Points pop in one after the other, once the lines are drawn. */ #chart .ct-point { transform-box: fill-box; transform-origin: center; animation: pop 0.4s ease-out both; animation-delay: calc(1s + var(--i) * 80ms); } @keyframes draw { to { stroke-dashoffset: 0; } } @keyframes pop { from { opacity: 0; transform: scale(0); } } @media (prefers-reduced-motion: reduce) { #chart .ct-line, #chart .ct-point { animation: none; stroke-dashoffset: 0; } } ``` `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { lineChart, type ChartPlugin } from 'chartist'; // pathLength="1" makes every line one unit long, whatever its real length. // --i numbers the points, so CSS can stagger them. const drawIn: ChartPlugin = { name: 'draw-in', install(chart) { chart.on('compose', node => { if (node.role === 'line') { return node.attr({ pathLength: 1 }); } if (node.role === 'point') { return node.attr({ style: `--i: ${node.ctx.valueIndex}` }); } }); } }; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [ [12, 9, 7, 8, 5], [2, 1, 3.5, 7, 3] ] }, { plugins: [drawIn] } ); ``` `index.html`: ```html
``` The same works for bars (each bar is an SVG `line`) and donut slices (`ct-slice-donut`, a stroked path): give them `pathLength: 1` too and animate their dash offset. > **Each render draws a new SVG, by default.** When the chart updates or its > container resizes, Chartist replaces the SVG, and your animations play again. > A chart drawn with `liveDomRenderer()` keeps its elements instead, so CSS > animations play once, when an element is added. See [Live > updates](https://chartist.dev/docs/live-updates/#keep-the-svg). ### Stagger Delays make a chart unfold instead of popping up all at once. For series, use their letter classes: ```css .ct-series-b .ct-line { animation-delay: 0.3s; } .ct-series-c .ct-line { animation-delay: 0.6s; } ``` For points and bars, number them. The plugin above sets `--i` from the value index of each point (`node.ctx.valueIndex`), and the CSS turns the number into a delay: ```css .ct-point { animation-delay: calc(1s + var(--i) * 80ms); } ``` ### Reveal on scroll A chart far down the page has finished its animation long before anyone sees it. Scroll-driven animations tie the animation to the element's way through the viewport instead of to time: `animation-timeline: view()` runs it while the element scrolls into view. ```css @supports (animation-timeline: view()) { @media (prefers-reduced-motion: no-preference) { .reveal .ct-line { stroke-dasharray: 1; animation-name: draw-in; animation-timing-function: linear; animation-fill-mode: both; animation-timeline: view(); animation-range: entry 20% cover 45%; } } } @keyframes draw-in { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } } ``` The lines still need `pathLength="1"` from the plugin above. Browsers without scroll timelines skip the whole block and show the chart as it is. > **Write `animation-timeline` as a longhand.** Don't put it into the > `animation` shorthand, and don't follow it with one: the shorthand resets the > timeline. And watch your minifier: given an `animation` shorthand next to the > timeline, it may fold the timeline into the shorthand, which browsers reject, > and nothing animates. Spell out `animation-name`, `animation-timing-function` > and the rest, as above. ### SMIL, for what CSS can't animate CSS animates properties, and some SVG attributes aren't properties. A bar is a `line`, and its end point (`y2`) is an attribute, so CSS can't grow it from the axis. SMIL can: an `` element inside the SVG animates any attribute, and Chartist's `Svg` wrapper writes one for you with `animate()`. The plugin waits for the `mount` event, when the chart's elements exist, wraps each bar's element and animates `y2` from the axis (`y1`) to the bar's end: **Bars grow with SMIL** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { barChart, findAll, Svg, type ChartPlugin } from 'chartist'; const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)'); // Bars grow from the axis with SMIL, one after the other. const growBars: ChartPlugin = { name: 'grow-bars', install(chart) { chart.on('mount', ({ scene, elementOf }) => { if (reducedMotion.matches) { return; } for (const bar of findAll(scene, { role: 'bar' })) { const element = elementOf(bar); if (!element) { continue; } new Svg(element).animate({ y2: { begin: bar.ctx.valueIndex * 120, dur: 700, from: bar.attrs.y1, to: bar.attrs.y2, easing: 'easeOutQuint' } }); } }); } }; barChart( '#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [ [5, 4, 3, 7], [3, 2, 9, 5] ] }, { plugins: [growBars] } ); ``` `index.html`: ```html
``` `animate()` takes one SMIL definition per attribute: `dur` and `begin` (numbers are milliseconds), `from`, `to`, and an `easing`: a name from `easings` (exported by `chartist`, from `easeInSine` to `easeInOutBack`), four numbers of a cubic Bézier curve, or a CSS easing such as `'ease-out'`, `'cubic-bezier(0.34, 1.56, 0.64, 1)'`, `linear()` or `steps()`. Easings that overshoot, like `easeOutBack`, run as sampled values, since browsers ignore SMIL curves that leave 0 to 1 (in 1.x, such animations never ran). By default it runs in guided mode: the attribute starts at `from` before the animation begins, the animation starts after `begin`, and when it ends the attribute keeps `to` and the `` element is removed. Pass `false` as the second argument to write the `` element as given. Charts [rendered to a string](https://chartist.dev/docs/server-rendering/) have no elements to wrap. There, add the `` element to the scene yourself, and it travels with the markup. Tools that don't run SMIL, like most image converters, show the line without the fade: ```ts import { node, type ChartPlugin } from 'chartist'; /** Fades each line in, also in SVG rendered on a server. */ export const fadeIn: ChartPlugin = { name: 'fade-in', install(chart) { chart.on('compose', current => current.role === 'line' ? current.append( node('animate', { attributeName: 'opacity', from: 0, to: 1, dur: '1s' }) ) : undefined ); } }; ``` ### Update animations Charts drawn with `liveDomRenderer()` from `chartist/live` can animate their updates: give the renderer `smilAnimations()`, and bars, points, lines, areas and pie slices move from their old values to the new ones. How long that takes and how it eases is the chart's look, so it lives in CSS: ```ts import { barChart } from 'chartist'; import { liveDomRenderer, smilAnimations } from 'chartist/live'; barChart( '#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [[5, 4, 3, 7]] }, { renderer: liveDomRenderer({ animations: smilAnimations() }) } ); ``` ```css #chart { --ct-update-duration: 400ms; --ct-update-easing: ease-out; } ``` Without either property, updates take 300 ms with `ease-out`; `--ct-update-duration: 0s` turns them off. `--ct-update-easing` takes any CSS easing, also curves that overshoot, `linear()` and `steps()`. Only updates and responsive changes animate, never the first render or a resize, and never for people who ask for less motion. The [live updates guide](https://chartist.dev/docs/live-updates/#animate-updates) has a running example and advice for streaming data. Plugins get the same timing in `mount`, to [animate what they draw](https://chartist.dev/docs/events-and-plugins/#animate-in-step) in step with the chart. ### Less motion Some people get dizzy or distracted from things moving on screen and ask their system for reduced motion. Respect it. For CSS, put the animations behind the media query, or turn them off inside it, as the examples above do: ```css @media (prefers-reduced-motion: reduce) { .ct-line, .ct-point, .ct-bar { animation: none; stroke-dashoffset: 0; } } ``` The media query doesn't stop SMIL, so check it in JavaScript before you add SMIL animations, like the bar plugin does: ```ts const reducedMotion = matchMedia('(prefers-reduced-motion: reduce)'); if (!reducedMotion.matches) { // add the animations } ``` Update animations from `smilAnimations()` check it themselves, and so does the `animation` timing plugins get in `mount`: it's absent when the chart doesn't animate. Make sure the end state is the one people see without motion: lines drawn, bars at full height. In the API reference: [`Svg`](https://chartist.dev/api/svg/#Svg), [`easings`](https://chartist.dev/api/svg/#easings), [`smilAnimations()`](https://chartist.dev/api/live/#smilAnimations) and [the scene](https://chartist.dev/api/scene/). Next: [write your own plugins](https://chartist.dev/docs/events-and-plugins/), or [update charts with new data](https://chartist.dev/docs/live-updates/). ## Bands > Shade ranges on line and bar charts with the band() layer, between two series, around a group or at fixed values, and mark the values in or out of them. A value on its own says little. Is 21 degrees fine? Against a comfort zone of 19 to 24, you see it at a glance. `band()` draws that range: a shaded area between two edges, from a group of series, two series, your own values or two numbers. It's part of the core, works on the presets and in composed charts, and you style it with CSS like the rest of the chart. For a single goal or limit, or the trend of a series, use the [target lines](https://chartist.dev/packages/essentials/target-lines/) and [trend lines](https://chartist.dev/packages/essentials/trend-lines/) of `@chartist-js/essentials`. They go well with a band. ### Band charts A band shows a range: from the lowest to the highest value of a group, between two series such as percentiles, or between two fixed values. With the group's average and one member drawn on top, you get a [band chart](https://www.clearlyandsimply.com/2011/04/an-underrated-chart-type-the-band-chart/): how does Bern do compared to the other stores? **A band chart** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { autoScaleAxis, axisGrid, axisLabels, band, cartesianAxes, chart, line, stepAxis } from 'chartist'; const weeks = ['W1', 'W2', 'W3', 'W4', 'W5', 'W6', 'W7', 'W8']; // Weekly sales of six stores, in thousands. const stores = [ { name: 'Basel', data: [42, 45, 40, 38, 44, 47, 50, 52] }, { name: 'Bern', data: [36, 38, 33, 28, 30, 41, 49, 55] }, { name: 'Chur', data: [25, 27, 30, 29, 31, 30, 33, 34] }, { name: 'Geneva', data: [50, 53, 55, 51, 49, 54, 58, 60] }, { name: 'Lucerne', data: [31, 30, 34, 36, 35, 37, 36, 40] }, { name: 'Zurich', data: [46, 44, 49, 52, 50, 48, 53, 57] } ]; const average = weeks.map( (_, week) => stores.reduce((sum, store) => sum + store.data[week], 0) / stores.length ); chart('#chart', { data: { labels: weeks, series: [ ...stores, { name: 'average', className: 'ct-series-e', data: average } ] }, axes: cartesianAxes({ x: { type: stepAxis() }, y: { type: autoScaleAxis(), low: 0 } }), layers: [ axisGrid(), // From the lowest to the highest store, every week. band({ series: stores.map(store => store.name), lineSmooth: false }), line({ series: ['average'], lineSmooth: false, showPoint: false }), line({ series: ['Bern'], lineSmooth: false }), axisLabels() ] }); ``` `index.html`: ```html
``` `band({ series })` takes the group by name or index and covers its minimum to its maximum at each label. The average is a series you compute yourself: one line of code, and you decide what "average" means. Layers draw in order, so the band comes before the lines. The edges of a band can be: - **values of their own:** `band({ low: [3, 4, 3, 5], high: [9, 11, 10, 12] })`, one per label, like a series that isn't in your data, - **two numbers:** `band({ low: 40, high: 60 })`, a target zone across the whole chart, - **two series** from your data: `band({ low: 'p10', high: 'p90' })`, - **a mix:** `band({ low: 0, high: forecast })`. A number is a constant, an array holds values, and a string names a series; pick a series by index with `{ series: 2 }`. Edges follow `lineSmooth` like lines do, smooth by default, and `align: 'center'` lines a band up with bars, as for [lines](https://chartist.dev/docs/composed-charts/#lining-up-with-bars). Where an edge has no value, the band breaks. #### On a preset Presets take extra layers in their `layers` option: `below` draws them behind the bars or lines, `above` on top. [Responsive options](https://chartist.dev/docs/responsive/) can change them too: a `layers: { below: [...] }` entry replaces the `below` list and keeps `above`. Give a preset's band its own values. The edges then stay out of your data, so the preset doesn't draw them as lines and the series keep their colors: ```ts import { band, lineChart } from 'chartist'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[5, 8, 6, 9, 7]] }, { layers: { below: [band({ low: [3, 4, 3, 5, 4], high: [9, 11, 10, 12, 11] })] } } ); ``` On a scale X axis (an XY chart), give the values as `{ x, y }`, like the data. ### In or out of the band With `mark: true`, the band marks the points and bars of the other layers: `ct-in-band` when the value is within the band, edges included, `ct-out-band` when it isn't. What "out" looks like is up to your CSS. **A comfort zone** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { band, lineChart } from 'chartist'; // Room temperature every two hours, and the range it should stay in. lineChart( '#chart', { labels: [ '0h', '2h', '4h', '6h', '8h', '10h', '12h', '14h', '16h', '18h', '20h', '22h' ], series: [[18, 17.5, 17, 17, 19, 21, 22.5, 24.5, 25, 23, 21, 19.5]] }, { low: 15, high: 27, layers: { below: [band({ low: 19, high: 24, mark: true })] } } ); ``` `src/style.css`: ```css /* Mustard yellow inside the band; outside, larger and in the series color. */ #chart .ct-point.ct-in-band { stroke: #f4c63d; } #chart .ct-point.ct-out-band { stroke-width: 14px; } ``` `index.html`: ```html
``` The band marks the points no matter where it's listed: it works from the data, after every layer has drawn. Marking classes whole points and bars; a line keeps one color where it crosses an edge. ### Styling A band draws in the text color by default, as a light fill, so it reads as context behind the data. Change it with custom properties, or give it a series color with `className: 'ct-series-c'`: ```css .ct-chart { --ct-band-color: #b07800; --ct-band-opacity: 0.2; } ``` The markup: a `g.ct-band` group with a `path.ct-band-area` per piece of the band. In listeners and plugins, find them by the role `band`, as in [events and plugins](https://chartist.dev/docs/events-and-plugins/). Next: add a goal with a [target line](https://chartist.dev/packages/essentials/target-lines/), [compose your own charts](https://chartist.dev/docs/composed-charts/) with several axes, or [style the chart](https://chartist.dev/docs/styling-with-css/) to match your page. ## Live updates > Give a chart new data through its handle, and let it keep its SVG with liveDomRenderer(), so elements stay, plugins keep what they added and updates can animate. `lineChart()`, `barChart()`, `pieChart()` and `chart()` draw the chart right away and return a handle. Keep it: it's how you give the chart new data, and how you tell it you're done with it. Here's a temperature sensor that reports every second. The chart drops the oldest reading, adds the new one and keeps its SVG while doing it. The line under the chart counts what each update did to the SVG: almost every element is kept, many of them with new positions, and only the oldest reading's elements are removed and the new one's added. The axis follows the readings, so when their range changes, its grid lines and labels change too. Press **Edit** to change the code: **A live sensor** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { lineChart, type SceneNode } from 'chartist'; import { liveDomRenderer } from 'chartist/live'; import { firstReadings, onReading } from './readings'; const readout = document.querySelector('#changes')!; const count = (node: SceneNode): number => 1 + node.children.reduce((sum, child) => sum + count(child), 0); const chart = lineChart('#chart', firstReadings, { // Keep the SVG, and know each reading by its label. renderer: liveDomRenderer({ key: 'label' }), showArea: true, on: { // What each render did to the SVG's elements. mount: ({ scene, changes, cause }) => { const total = count(scene); if (cause === 'create') { readout.textContent = `Drew ${total} elements.`; return; } const tally = { add: 0, update: 0, remove: 0 }; for (const change of changes) { tally[change.op] += 1; } readout.textContent = `Kept ${total - tally.add} of ${total} elements (${tally.update} changed), ` + `added ${tally.add}, removed ${tally.remove}.`; } } }); // A new reading every second: the oldest one goes, the new one comes. onReading(data => chart.update({ data })); ``` `src/readings.ts`: ```ts // A temperature sensor that reports every second. The readings come from // a seeded generator, so every run shows the same ones. let seed = 42; const random = () => { seed = (seed * 16807) % 2147483647; return (seed - 1) / 2147483646; }; let data = { labels: ['1s', '2s', '3s', '4s', '5s', '6s', '7s', '8s', '9s', '10s'], series: [[21.2, 21.4, 21.3, 21.7, 21.9, 21.8, 22.1, 22, 22.3, 22.2]] }; /** The last ten readings. */ export const firstReadings = data; /** Calls `listener` every second with the last ten readings. */ export function onReading(listener: (readings: typeof data) => void) { let second = 10; setInterval(() => { second += 1; const [values] = data.series; const next = values[values.length - 1] + random() - 0.5; const reading = Math.round(Math.min(23.5, Math.max(20.5, next)) * 10) / 10; // Drop the oldest reading and add the new one. data = { labels: [...data.labels.slice(1), `${second}s`], series: [[...values.slice(1), reading]] }; listener(data); }, 1000); } ``` `src/style.css`: ```css #changes { margin: 0.5rem 0 0; font: 0.8rem/1.4 ui-monospace, monospace; color: #6b6b6b; } ``` `index.html`: ```html

``` [`liveDomRenderer()`](#keep-the-svg), the `key` option, [animations](#animate-updates) and [what changed](#what-changed) are what the rest of this guide is about. More live charts are in the [examples](https://chartist.dev/examples/#live). The handle's methods: | Method | What it does | | ----------------- | ---------------------------------------------------------------------------------- | | `update(changes)` | Merges the changes into the config and draws the chart again | | `detach()` | Stops following the container's size; the chart stays as it is | | `destroy()` | Detaches, removes the SVG and removes every listener | | `on()`, `off()` | Adds and removes listeners, see [Events and plugins](https://chartist.dev/docs/events-and-plugins/) | | `setOptions()` | Presets only: changes the options and draws again, see [new options](#new-options) | ### New data Pass `update()` the parts of the config that changed. The merge is shallow: each key you pass replaces that key, the rest stays. `update({ data })` replaces all of the data, labels included, so pass the whole data object, not only the series that changed. The same goes for `axes` and `layers` in a `chart()` config. ```ts import { autoScaleAxis, cartesianAxes, chart, line, stepAxis } from 'chartist'; const labels = ['Mon', 'Tue', 'Wed']; const handle = chart('#chart', { data: { labels, series: [[1, 3, 2]] }, axes: cartesianAxes([ { id: 'x', side: 'bottom', type: stepAxis() }, { id: 'y', side: 'left', type: autoScaleAxis() } ]), layers: [line()] }); // New data; the axes and layers stay. handle.update({ data: { labels, series: [[2, 4, 3]] } }); // Another look; the data stays. handle.update({ layers: [line({ showArea: true, showPoint: false })] }); ``` Every update runs the whole pipeline again, and your plugins and listeners stay. They aren't part of what `update()` takes: add and remove listeners with `on()` and `off()`. ### New options `update()` takes the config of `chart()`, so on a preset chart it replaces the `data`, but options like `showArea` or `stackBars` aren't part of it. The presets' handles have `setOptions()` for those. The options you pass extend the current ones; with `{ replace: true }` they start from the defaults instead, and the options you gave before are gone: ```ts import { lineChart } from 'chartist'; const chart = lineChart( '#chart', { series: [[1, 3, 2, 5]] }, { showPoint: false } ); // Areas too; showPoint: false stays. chart.setOptions({ showArea: true }); // From the defaults: points again, and areas. chart.setOptions({ showArea: true }, { replace: true }); ``` [Responsive options](https://chartist.dev/docs/responsive/) still apply on top, and new data from `update({ data })` stays when they change. `plugins`, `on` and `renderer` are set at creation. ### Keep the SVG > **Experimental.** `chartist/live` is new with 2.0. Its API may still change in > a 2.x minor before it becomes stable; the rest of Chartist is stable. What a render does to the page depends on the chart's renderer: - **`domRenderer()`**, the default, draws a new SVG on every render and replaces the old one: on an update, a resize, a responsive change, or when web fonts finish loading. It's small, and right for charts that rarely change. But the elements are new each time: a hovered bar loses its `:hover`, a focused point its focus, what a plugin added to an element is gone, and [CSS animations](https://chartist.dev/docs/animations/) play again. - **`liveDomRenderer()`**, from `chartist/live`, draws the SVG once and then changes it in place. An element whose value is still in the data stays the same element, and only the attributes, classes and text that changed are set. New values get new elements, gone ones are removed. The result is the same SVG a fresh chart would draw. Opt in per chart with the `renderer` option, on the presets and on `chart()`: ```ts import { barChart } from 'chartist'; import { liveDomRenderer } from 'chartist/live'; barChart( '#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [[5, 4, 3, 7]] }, { renderer: liveDomRenderer() } ); ``` Reach for it when a chart updates while people look at it: dashboards, streams, charts with [tooltips](https://chartist.dev/packages/tooltips/) or other interactive plugins. A chart that's drawn once doesn't need it. > **1.x code always gets a new SVG.** The 1.x classes (`new LineChart()`) and > charts with the `compat` plugin or 1.x plugins converted with > `fromLegacyPlugin` draw a new SVG on every render, even with > `liveDomRenderer()`: their `draw` listeners expect new elements. To update > such a chart live, move its plugins to [v2 > plugins](https://chartist.dev/docs/events-and-plugins/#plugins-for-live-updates). #### Importing chartist/live With a bundler, import it next to `chartist`: ```ts import { lineChart } from 'chartist'; import { liveDomRenderer } from 'chartist/live'; lineChart('#chart', { series: [[1, 3, 2]] }, { renderer: liveDomRenderer() }); ``` Without one, load `live.min.js` from the same CDN folder as `index.min.js`. It imports `./index.min.js`, so both share one copy of Chartist: ```html ``` ### Which value is which To keep an element, the renderer has to know which value of the new data it stands for. The `key` option says what identifies a value: - **`'index'`**, the default: its position in its series. The third value stays the third value. Right when the data has the same values in the same order, with new numbers. - **`'label'`**: the label at its position. Right for a sliding window like the sensor above, where every reading moves one place to the left. With index keys, each point would stay where it is and take its neighbor's value; with label keys, the point of `5s` keeps its element and moves left. - **A function** that gets `{ seriesIndex, valueIndex, value, label, meta }` and returns a string or a number. Right when values carry an id, or when they change their order: ```ts import { barChart } from 'chartist'; import { liveDomRenderer } from 'chartist/live'; // Each value carries its team's id; the bars are sorted by score. barChart( '#chart', { labels: ['Otters', 'Owls', 'Ants'], series: [ [ { value: 12, meta: 'otters' }, { value: 9, meta: 'owls' }, { value: 4, meta: 'ants' } ] ] }, { renderer: liveDomRenderer({ key: ({ meta }) => String(meta) }) } ); ``` Series are matched by their index, and grid lines and axis labels by their tick value: when the scale changes from 0 to 10 to 0 to 15, the lines of 0, 5 and 10 move and the one of 15 is added. Values with the same key in a series are matched in their order, so repeated labels work like index keys. The key only decides which elements are kept, never what's drawn. ### What changed The `render` and `mount` events say why the chart rendered and what the render did, for every renderer: - `cause`: `create` for the first render, then `update` (from `update()` and `setOptions()`), `resize`, `responsive` or `fonts`; - `previous`: the previous render's scene, if there was one; - `changes`: one entry per node, in document order. `add` for a new node, `update` for a node that changed (with its `previous` node), `remove` for a node that's gone. In `mount`, `elementOf()` gives each entry's element, for a `remove` the one just taken out of the page. With `domRenderer()`, every render is a replacement: every previous node is removed and every new one added. With `liveDomRenderer()`, the changes are what it changed. That makes `add` entries the place to set up an element once. Listeners added in `mount` to every element would pile up on elements that stay, so add them to new elements only. This works the same with both renderers: ```ts import { barChart } from 'chartist'; import { liveDomRenderer } from 'chartist/live'; barChart( '#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [[5, 4, 3, 7]] }, { renderer: liveDomRenderer(), on: { mount: ({ cause, changes, elementOf }) => { for (const change of changes) { if (change.op === 'add' && change.node.role === 'bar') { elementOf(change.node)?.addEventListener('click', () => console.log('clicked a bar') ); } } console.log(`${cause}: ${changes.length} changes`); } } } ); ``` ### Cleaning up Some things can't be set up once: a `title` that shows a value changes with the value. A `mount` listener can return a function that undoes what it did. It runs right before the next render touches the SVG, and on `destroy()`: ```ts import { findAll, lineChart } from 'chartist'; import { liveDomRenderer } from 'chartist/live'; const SVG = 'http://www.w3.org/2000/svg'; lineChart( '#chart', { series: [[1, 3, 2, 4]] }, { renderer: liveDomRenderer(), on: { mount: ({ scene, elementOf }) => { // A tooltip with its value on every point. const titles = findAll(scene, { role: 'point' }).map(point => { const title = document.createElementNS(SVG, 'title'); title.textContent = String(point.attrs['ct:value']); elementOf(point)?.append(title); return title; }); // Gone before the next render adds new ones. return () => titles.forEach(title => title.remove()); } } } ); ``` Plugins can do the same, and their `install` can return a cleanup function too, for what they set up outside the chart. See [Events and plugins](https://chartist.dev/docs/events-and-plugins/#cleaning-up). ### Animate updates With `smilAnimations()`, updates move: bars grow and shrink, points and lines slide to their new place, pie slices turn. Updates take 300 ms with `ease-out`. The timing is part of the chart's look, so it's in CSS: set `--ct-update-duration` and `--ct-update-easing` on the chart or its container to change it, or `--ct-update-duration: 0s` to turn the animations off. **Animated updates** (runs in the browser): `src/style.css`: ```css #chart { /* Updates move the bars from their old values to the new ones. */ --ct-update-duration: 700ms; /* A curve that overshoots a little, then settles. */ --ct-update-easing: cubic-bezier(0.34, 1.56, 0.64, 1); } ``` `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { barChart } from 'chartist'; import { liveDomRenderer, smilAnimations } from 'chartist/live'; import { firstNumbers, onNumbers } from './numbers'; const chart = barChart( '#chart', firstNumbers, // How long the animations take is up to style.css. { renderer: liveDomRenderer({ animations: smilAnimations() }) } ); onNumbers(data => chart.update({ data })); ``` `src/numbers.ts`: ```ts // Numbers by region from a seeded generator, so every run shows the same. let seed = 7; const next = () => { seed = (seed * 16807) % 2147483647; return (seed - 1) / 2147483646; }; const labels = ['North', 'East', 'South', 'West']; const random = () => labels.map(() => Math.round(next() * 9) + 1); /** Two series by region. */ export const firstNumbers = { labels, series: [ [5, 4, 3, 7], [3, 2, 9, 5] ] }; /** Calls `listener` with new numbers every two seconds. */ export function onNumbers(listener: (numbers: typeof firstNumbers) => void) { setInterval(() => listener({ labels, series: [random(), random()] }), 2000); } ``` `index.html`: ```html
``` `--ct-update-easing` takes any CSS easing: the keywords (`ease-out` is the default), `cubic-bezier()` curves, including ones that overshoot like the one above, `linear()` and `steps()`. The animations run with SMIL, which reaches what CSS can't: bars and points are `` elements, and the ends of a line are attributes, not CSS properties. SMIL moves them, and paths, in every browser. A few things to know: - **The attributes hold the new values at once.** The animation is drawn on top of them, so whatever you read from the DOM is the final state. - **An update during an animation continues from where the elements are drawn**, without a jump. Pie slices start from their previous shape. - **Only `update` and `responsive` renders animate.** A resize, the first render and loaded fonts draw the new state at once. - **Nothing moves for people who ask for less motion.** Under `prefers-reduced-motion: reduce`, updates aren't animated, whatever the CSS says. - **Firefox doesn't animate HTML labels.** It runs no SMIL inside a `foreignObject`, so there the axis labels move to their new place at once. SVG text labels (`labels: 'svg'`) move in every browser. Pies animate too. Each slice turns to its new size around the pie, and its label moves around with it. With `key: 'label'`, a slice keeps its element even when the order of the parties changes: **Votes by district** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { pieChart } from 'chartist'; import { liveDomRenderer, smilAnimations } from 'chartist/live'; import { firstCount, onCount, parties } from './count'; let votes = firstCount; const total = () => votes.reduce((sum, value) => sum + value, 0); const chart = pieChart( '#chart', { labels: parties, series: votes }, { // Slices are known by their party, and turn to their new share. renderer: liveDomRenderer({ key: 'label', animations: smilAnimations() }), // Each slice shows its share. labelTransform: (label, index) => `${label}\n${Math.round((votes[index] / total()) * 100)}%` } ); // The next district's results. onCount(count => { votes = count; chart.update({ data: { labels: parties, series: votes } }); }); ``` `src/count.ts`: ```ts // The results of the districts as they report, from a seeded generator, so // every run counts the same votes. let seed = 3; const random = () => { seed = (seed * 16807) % 2147483647; return (seed - 1) / 2147483646; }; /** The parties, in the order of the slices. */ export const parties = ['Greens', 'Blues', 'Reds', 'Yellows']; /** A district's votes by party: each district has a mood of its own. */ const district = () => parties.map(() => Math.round(50 + random() * 450)); /** The first district's votes. */ export const firstCount = district(); /** Calls `listener` with the next district's votes every 1.5 seconds. */ export function onCount(listener: (votes: number[]) => void) { setInterval(() => listener(district()), 1500); } ``` `src/style.css`: ```css /* White labels on the slices, in light and dark mode. */ #chart .ct-label { fill: #fff; color: #fff; } ``` `index.html`: ```html
``` A gauge is a donut on half a ring. Its second slice is the rest of the scale, drawn as a faint track in CSS, so the load moves along the ring: **Live gauge** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { pieChart } from 'chartist'; import { liveDomRenderer, smilAnimations } from 'chartist/live'; import { firstLoad, onLoad } from './load'; // The load, and the rest of the scale as a track, on a half ring: a total // of 200 leaves the lower half empty. const series = (load: number) => [load, 100 - load]; const chart = pieChart( '#chart', { series: series(firstLoad) }, { renderer: liveDomRenderer({ animations: smilAnimations() }), donut: true, donutWidth: 40, startAngle: 270, total: 200, showLabel: false } ); onLoad(load => chart.update({ data: { series: series(load) } })); ``` `src/load.ts`: ```ts // A CPU's load in percent from a seeded generator, so every run shows the // same load. let seed = 75386; const random = () => { seed = (seed * 16807) % 2147483647; return (seed - 1) / 2147483646; }; /** The load when the chart starts. */ export const firstLoad = 35; /** Calls `listener` with the load every 1.2 seconds. */ export function onLoad(listener: (load: number) => void) { // Busy, then idle, then busy again: anywhere from 10% to 95%. setInterval(() => listener(Math.round(10 + random() * 85)), 1200); } ``` `src/style.css`: ```css /* The rest of the scale: a faint track in the text color, light or dark. */ #chart .ct-series-b .ct-slice-donut { stroke: currentColor; stroke-opacity: 0.12; } ``` `index.html`: ```html
``` How to animate your own layers and plugins: [keyframes](https://chartist.dev/docs/events-and-plugins/#keyframes) in the plugin guide, and [Animations](https://chartist.dev/docs/animations/#update-animations) for the rest of the animation story. #### Streaming For data that arrives every second or faster, keep the duration at or below the update interval. Every update starts new animations on top of the running ones, and when they come faster than they end, the chart is always on its way somewhere and never quite there. With a `linear` easing and the duration of the interval, a stream scrolls without stopping. Here, four network speed samples a second come in as touching bars, with their running average as a line on top. The samples are known by their number, so they all slide one bar to the left: **Network speed** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { autoScaleAxis, axisGrid, axisLabels, bar, cartesianAxes, chart, line, stepAxis } from 'chartist'; import { liveDomRenderer, smilAnimations } from 'chartist/live'; import { SAMPLES, firstSamples, onSample } from './samples'; // The bars are as wide as one sample, so they touch. let step = 0; const container = document.querySelector('#chart')!; const live = chart(container, { data: firstSamples, axes: cartesianAxes([ { id: 'time', side: 'bottom', type: stepAxis(), showGrid: false, showLabel: false }, { id: 'mbps', side: 'left', type: autoScaleAxis(), low: 0, high: 140 } ]), layers: [ axisGrid({ axes: ['mbps'] }), bar({ series: ['speed'], x: 'time', y: 'mbps' }), line({ series: ['average'], x: 'time', y: 'mbps', align: 'center', showPoint: false }), axisLabels() ], // Samples are known by their number, so they slide to the left. renderer: liveDomRenderer({ key: 'label', animations: smilAnimations() }), on: { layout: ({ area }) => void (step = area.width / SAMPLES), mount: () => container.style.setProperty('--ct-bar-width', `${step}px`) } }); onSample(data => live.update({ data })); ``` `src/samples.ts`: ```ts // Network speed samples, four per second, from a seeded generator, so every // run measures the same speeds. let seed = 11; const random = () => { seed = (seed * 16807) % 2147483647; return (seed - 1) / 2147483646; }; /** How many samples the chart shows. */ export const SAMPLES = 60; let sample = 0; let speed = 80; const measure = () => { // Wanders around 80 Mbit/s, with a dip now and then. speed += (80 - speed) * 0.2 + (random() - 0.5) * 30; if (random() < 0.04) { speed *= 0.3; } sample += 1; return { label: sample, mbps: Math.max(1, Math.round(speed)) }; }; let samples = Array.from({ length: SAMPLES }, measure); /** The samples by number, as a speed series and its running average. */ const dataOf = () => { const speeds = samples.map(s => s.mbps); const average = speeds.map((_, i) => { const recent = speeds.slice(Math.max(0, i - 7), i + 1); return recent.reduce((sum, value) => sum + value, 0) / recent.length; }); return { labels: samples.map(s => s.label), series: [ { name: 'speed', data: speeds }, { name: 'average', data: average } ] }; }; /** The last 60 samples. */ export const firstSamples = dataOf(); /** Calls `listener` with the last 60 samples, four times a second. */ export function onSample(listener: (data: typeof firstSamples) => void) { setInterval(() => { samples = [...samples.slice(1), measure()]; listener(dataOf()); }, 250); } ``` `src/style.css`: ```css #chart { /* Faster than the samples come in, and steady. */ --ct-update-duration: 200ms; --ct-update-easing: linear; --ct-line-width: 2px; } /* Salmon bars, and the average as a red line on top. */ #chart .ct-bar { stroke: var(--ct-series-b, #f05b4f); } #chart .ct-line { stroke: var(--ct-series-a, #d70206); } ``` `index.html`: ```html
``` The [seismograph](https://chartist.dev/examples/live/seismograph/) in the examples updates every 80 ms. ### Data and options at once `update()` takes any part of the config, so new data and new layers can arrive in one update. It's one render, so it's one animation: every bar moves from where it was to where it goes. These bars switch between grouped and stacked every two and a half seconds, with new numbers each time: **Grouped and stacked** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { autoScaleAxis, axisGrid, axisLabels, bar, cartesianAxes, chart, stepAxis } from 'chartist'; import { liveDomRenderer, smilAnimations } from 'chartist/live'; import { salesData } from './sales'; // The same bars, grouped or stacked: only the layer's option differs. const layers = (stackBars: boolean) => [ axisGrid(), bar({ stackBars }), axisLabels() ]; let stacked = false; const live = chart('#chart', { data: salesData(), axes: cartesianAxes([ { id: 'months', side: 'bottom', type: stepAxis() }, { id: 'sales', side: 'left', type: autoScaleAxis(), low: 0 } ]), layers: layers(stacked), renderer: liveDomRenderer({ animations: smilAnimations() }) }); // New numbers and the other layout, in one update: every bar moves from // where it was to where it goes. setInterval(() => { stacked = !stacked; live.update({ data: salesData(), layers: layers(stacked) }); }, 2500); ``` `src/sales.ts`: ```ts // Sales by month in three series, from a seeded generator, so every run // shows the same numbers. let seed = 21; const random = () => { seed = (seed * 16807) % 2147483647; return (seed - 1) / 2147483646; }; const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; const series = () => months.map(() => Math.round(2 + random() * 8)); /** New sales numbers for every month, in three series. */ export const salesData = () => ({ labels: months, series: [series(), series(), series()] }); ``` `src/style.css`: ```css #chart { --ct-update-duration: 600ms; --ct-update-easing: ease-in-out; } ``` `index.html`: ```html
``` The axis moves with them: its range grows for the stacks, and the grid lines and labels of the ticks that stay slide to their new place. On a preset, options change with `setOptions()` and data with `update()`, which are two renders: the second one picks up where the first one's animation is. ### What it costs Live updates are opt-in, in their own entry point, so charts that don't use them don't carry them. Minified and gzipped, `liveDomRenderer()` adds about 1.4 kB to a chart, and `smilAnimations()` about 4 kB more. What the core needs for them (the previous render for renderers, `cause` and `changes`, cleanup functions) is about 0.6 kB, in every chart. ### Redrawing on resize You don't need to listen to `resize` on the window. A chart watches its container with a `ResizeObserver` and lays itself out again when the container's size changes, whether the window, a sidebar or a CSS grid changed it. Only the layout and later phases run again; the config and data are reused. Two things make this work: the container needs a size, and the chart needs room to follow it. [Sizing charts](https://chartist.dev/docs/sizing-charts/) covers both. For different options at different sizes, see [Responsive sugar topping](https://chartist.dev/docs/responsive/). Charts with [SVG labels](https://chartist.dev/docs/server-rendering/#svg-or-html-labels) also lay out again when web fonts finish loading, since Chartist may have measured the labels before the font arrived. ### Detach and destroy `detach()` stops a chart from following its container (and, for presets, the media queries of their responsive options). The SVG and your listeners stay, and `update()` still draws. Use it to freeze a chart, for example before printing or while you animate its container. `destroy()` is for when the chart goes away: it detaches, runs the cleanup functions, removes the SVG from the container and removes every listener. Calling it twice is safe. In a component framework, destroy the chart where the component cleans up, so no observer keeps a removed element alive: ```ts import { lineChart, type ChartHandle } from 'chartist'; let handle: ChartHandle | undefined; export function mount(element: HTMLElement) { handle = lineChart(element, { series: [[1, 3, 2]] }); } export function unmount() { handle?.destroy(); handle = undefined; } ``` In the API reference: [the chart handle](https://chartist.dev/api/chart/#ChartHandle) and [chartist/live](https://chartist.dev/api/live/). Next: [animate charts with CSS](https://chartist.dev/docs/animations/), or [write a plugin](https://chartist.dev/docs/events-and-plugins/). ## Events and plugins > Every chart is built in phases, and each phase is an event you can listen to and change. Plugins package those listeners so you can share them. Chartist keeps its core small and leaves the extras to you. Tooltips, value labels, legends, a bar that turns red past a threshold: none of them are options. Instead, every step of drawing a chart is an event, and a listener can change what that step produces. A plugin is a set of such listeners with a name, ready to share. ### The pipeline A chart goes through seven phases, in this order. The first five build the chart's scene, a plain tree of SVG nodes, without touching the page; the last two put it there: Each phase emits an event with what it produced: | Phase | Payload | What it is | | --------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | | `resolve` | `{ config }` | The config with defaults applied: padding, coordinates, layers, axes | | `prepare` | `{ data }` | The data, each series as `{ values, name, className, meta }` | | `layout` | `{ width, height, padding, area, inset }` | The chart's size and the drawing area inside the padding and axes | | `compose` | a scene node | Each element the layers draw: series, lines, points, bars, slices, grid lines and labels | | `scene` | `{ scene }` | The whole tree, its root the `svg` | | `render` | `{ scene, output, cause, previous, changes }` | The renderer has written the SVG, or changed it in place | | `mount` | `{ scene, output, cause, previous, changes, elementOf, animation }` | The SVG is in the page; `elementOf(node)` gives you a node's element | A listener that returns a new payload changes the phase, and later phases build on the change. Return nothing to leave it as it is. By `render` and `mount`, the SVG exists: `render` listeners only watch, and a `mount` listener may return a function that undoes what it did (see [Cleaning up](#cleaning-up)). Other return values of theirs are ignored. `render` and `mount` also say why the chart rendered and what the render did: `cause` is `create`, `hydrate` (a chart that [took server markup over](https://chartist.dev/docs/ssr-hydration/)), `update`, `resize`, `responsive` or `fonts`, `previous` is the previous render's scene, and `changes` lists every node that was added, updated or removed. `mount` also has the `animation` timing when the chart [animates its update](https://chartist.dev/docs/live-updates/#animate-updates). [Live updates](https://chartist.dev/docs/live-updates/#what-changed) has the details. When the container resizes, the chart runs again from `layout`. The config and data haven't changed, so `resolve` and `prepare` don't run, and their listeners aren't called. #### The layout box `layout` measures the chart from the outside in, a bit like CSS's box model: the padding, the room layers reserve, the axes, and in the middle the area the layers draw in. - **Chart:** `width` × `height` in the payload. - **Padding:** `chartPadding`. - **Reserved room:** what layers reserve with `inset()`, reported as `inset` and only there when a layer reserved some. Each layer draws in its own boxes, its `insetArea`. `padding` in the payload covers the padding and this room together. - **Axis bands:** each axis's `offset`, holding its tick labels. Axes on the same side stack outward in the order they're listed, and so does the reserved room. - **Drawing area:** `area`, what's left. A `layout` listener can return a smaller one to make room for something of its own, but a layer does that better with `inset()`: see [Custom layers](https://chartist.dev/docs/custom-layers/#inset-room-outside-the-chart). ### Listening Pass listeners in the config of `chart()`, by phase name: ```ts import { autoScaleAxis, cartesianAxes, chart, line, stepAxis } from 'chartist'; chart('#chart', { data: { labels: ['Mon', 'Tue', 'Wed'], series: [[1, 3, 2]] }, axes: cartesianAxes([ { id: 'x', side: 'bottom', type: stepAxis() }, { id: 'y', side: 'left', type: autoScaleAxis() } ]), layers: [line()], on: { layout: ({ width, height }) => console.log(`laid out at ${width}×${height}`), mount: ({ scene }) => console.log('mounted', scene) } }); ``` The presets take the same listeners: ```ts import { lineChart } from 'chartist'; lineChart( '#chart', { series: [[1, 3, 2]] }, { on: { mount: () => console.log('drawn') } } ); ``` Or call `on()` and `off()` on the chart handle. Both return the handle, so they chain. `off(name, listener)` removes that listener. `off(name)` without one removes the listeners you added to that event, with `on()` or in the `on` config, and leaves the ones of plugins alone. The name `'*'` listens to all events, with the event name as the first argument: ```ts import { lineChart } from 'chartist'; const chart = lineChart('#chart', { series: [[1, 3, 2]] }); const log = (name: string, payload: unknown) => console.log(name, payload); chart.on('*', log); chart.update({}); // logs every phase of this render chart.off('*', log); ``` > **The first render happens right away.** `chart()` and the presets draw the > chart before they return, so a listener added with `on()` afterwards sees the > next render, not the first. For the first one, pass it in `on`, or use a > plugin. ### Scene nodes The `compose` event is where most plugins work. Its payload is a scene node: a plain description of an SVG element with its `name`, `attrs`, `classes` and `children`, plus what Chartist knows about it: - `role`: `series`, `line`, `point`, `area`, `bar`, `slice`, `grid`, `gridBackground` or `label` - `ctx`: its data, such as `seriesIndex`, `valueIndex`, `value` and `meta` - `key`: a stable id like `0/1/3` (layer, series, value) In TypeScript, checking the role narrows the node: after `node.role === 'point'`, `node.ctx.x` is a number and `node.ctx.path` is an error, since only lines, areas and slices have a path. `findAll(scene, { role: 'bar' })` returns bar nodes, and the builder methods keep the type. The live examples' editor shows it too: type `node.ctx.` inside a role check, and it lists that role's fields. If your plugin adds nodes with a role of its own, declare it in the `NodeRoles` interface, the same way as [custom events](#custom-events). Every node with a role passes through the listeners, children before their parents. So a series listener sees its points and lines as the other listeners left them. Nodes are immutable. Their methods (`attr()`, `addClass()`, `removeClass()`, `append()`, `remove()`, `replace()`, `text()`, `empty()`) return a new node, and you return that node from the listener. #### Dynamic coloring Color isn't only for stylesheets. Here a plugin reads each bar's value and gives it a color from mustard yellow to red: **Dynamic coloring** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { barChart, type ChartPlugin } from 'chartist'; const values = [3, 6, 9, 12, 9, 6, 3, 6, 9, 12, 9, 6]; const min = Math.min(...values); const max = Math.max(...values); // Colors each bar by its value: mustard yellow for low, red for high. const heat: ChartPlugin = { name: 'heat', install(chart) { chart.on('compose', node => { if (node.role !== 'bar') { return; } // Bar values are { x, y } (or a hole). const { value } = node.ctx; const y = value && typeof value === 'object' && 'y' in value ? value.y : min; const share = (y - min) / (max - min); const red = Math.round(share * 100); return node.attr({ style: `stroke: color-mix(in oklab, #c8102e ${red}%, #f4c63d)` }); }); } }; barChart( '#chart', { series: [values] }, { axisX: { showLabel: false }, plugins: [heat] } ); ``` `index.html`: ```html
``` #### Change, replace or drop To change a node, return it with new attributes or classes, as above. To replace it, return a different node; keep its `role`, `ctx` and `key` so later listeners still know what it is. To drop it, return `null`: ```ts import { node, type ChartPlugin } from 'chartist'; /** Circles instead of points, and no points for zero values. */ export const circles: ChartPlugin = { name: 'circles', install(chart) { chart.on('compose', current => { if (current.role !== 'point') { return; } const { x, y, value } = current.ctx; // Line values are { x, y } (or a hole). if (value && typeof value === 'object' && 'y' in value && value.y === 0) { return null; } return node('circle', { cx: x, cy: y, r: 5 }, 'ct-circle', { role: current.role, ctx: current.ctx, key: current.key }); }); } }; ``` The circle has none of the point's classes, so style it yourself, for example with `.ct-circle { fill: var(--ct-color); }`. #### Decorate To add elements, append them to a parent. A value above each bar goes into the bar's series, which comes through the listener after its bars: ```ts import { node, type ChartPlugin } from 'chartist'; export const valueLabels: ChartPlugin = { name: 'value-labels', install(chart) { chart.on('compose', series => { if (series.role !== 'series') { return; } return series.children .filter(child => child.role === 'bar') .reduce((result, bar) => { const { x2, y2 } = bar.attrs; const { value: v } = bar.ctx; const value = v && typeof v === 'object' && 'y' in v ? v.y : undefined; return result.append( node( 'text', { x: x2, y: y2 - 8, 'text-anchor': 'middle' }, 'ct-value' ).text(String(value)) ); }, series); }); } }; ``` ### Writing a plugin A plugin is an object with a `name` and an `install` function. `install` runs once, before the first render, with the chart handle, and registers listeners on it. They stay for every later render, after updates and resizes too. For options, write a function that returns the plugin: ```ts import { barChart, type ChartPlugin } from 'chartist'; /** Bars at or above the limit get the class ct-over. */ const threshold = (limit: number): ChartPlugin => ({ name: 'threshold', install(chart) { chart.on('compose', current => { if (current.role !== 'bar') { return; } // A number, { x, y } (with an X scale axis) or a hole. const { value } = current.ctx; const y = typeof value === 'number' ? value : value && 'y' in value ? value.y : undefined; return y !== undefined && y >= limit ? current.addClass('ct-over') : undefined; }); } }); barChart( '#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [[5, 4, 3, 7]] }, { plugins: [threshold(5)] } ); ``` Plugins go into `plugins`, in the config of `chart()` or the options of the presets. Plugins install in order, so with two listeners on the same event, the earlier plugin's runs first. `chart()` also takes a `[plugin, options]` pair as an entry, and `install` receives the options as its second argument. ### Cleaning up What a plugin sets up outside the chart has to go when the chart goes. `install` may return a function, and `destroy()` calls it once, after the chart's SVG is removed. `detach()` doesn't: a detached chart keeps its SVG and its plugins. ```ts import type { ChartPlugin } from 'chartist'; /** Logs the chart's size while the Shift key is down. */ export const shiftToMeasure: ChartPlugin = { name: 'shift-to-measure', install(chart) { const onKey = (event: KeyboardEvent) => { if (event.key === 'Shift') { const box = chart.container?.getBoundingClientRect(); console.log(`${box?.width} x ${box?.height}`); } }; document.addEventListener('keydown', onKey); return () => document.removeEventListener('keydown', onKey); } }; ``` A `mount` listener may return a function too, for what it did to the elements of one render. It runs before the next render touches the SVG, and on `destroy()`. Use it when the next render needs a clean slate, such as a `title` with a point's value. For what you set up once per element, like event listeners, act on the `add` changes instead. [Live updates](https://chartist.dev/docs/live-updates/#cleaning-up) shows both. ### Custom events Plugins can emit events of their own with `chart.emit(name, payload)`. Declare each event and its payload in the `ChartEvents` interface (TypeScript merges your declaration into Chartist's), list it in `provides`, and listen to it with `on()` like to any other event. A misspelled name or a wrong payload is then a type error, not a silent no-op. This plugin reports clicks on bars; the page listens and shows the clicked value. Press **Edit** and click a bar: **A custom event** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { barChart, type ChartEvents, type ChartPlugin } from 'chartist'; // The event and its payload, declared so `emit` and `on` are typed. declare module 'chartist' { interface ChartEvents { 'bar:select': { series: number; index: number; value?: number }; } } // Emits "bar:select" when someone clicks a bar. const selectBars: ChartPlugin = { name: 'select-bars', provides: ['bar:select'], install(chart) { // What each bar element stands for in the latest render. const bars = new WeakMap(); chart.on('mount', ({ changes, elementOf }) => { for (const { op, node: bar } of changes) { const element = elementOf(bar); if (op === 'remove' || bar.role !== 'bar' || !element) { continue; } bars.set(element, { series: bar.ctx.seriesIndex, index: bar.ctx.valueIndex, // Bar values are { x, y } (or a hole). value: bar.ctx.value && typeof bar.ctx.value === 'object' && 'y' in bar.ctx.value ? bar.ctx.value.y : undefined }); // New elements only: an element the renderer kept has its handler. if (op === 'add') { element.addEventListener('click', () => { const selected = bars.get(element); if (selected) { chart.emit('bar:select', selected); } }); } } }); } }; const labels = ['Q1', 'Q2', 'Q3', 'Q4']; const chart = barChart( '#chart', { labels, series: [ [5, 4, 3, 7], [3, 2, 9, 5] ] }, { plugins: [selectBars] } ); const output = document.querySelector('#selected'); chart.on('bar:select', ({ series, index, value }) => { if (output) { output.textContent = `Series ${series + 1}, ${labels[index]}: ${value}`; } }); ``` `src/style.css`: ```css #chart .ct-bar { cursor: pointer; stroke-width: 24px; } #chart .ct-bar:hover { opacity: 0.7; } ``` `index.html`: ```html

Click a bar.

``` The click handlers go on in `mount`, the first phase in which the elements exist, and only on elements that are new in the page: those of `add` changes. With the default renderer, every render draws a new SVG, so every element is new, and the old ones go away with their handlers. With [`liveDomRenderer()`](https://chartist.dev/docs/live-updates/#keep-the-svg), a bar can stay the same element for many renders, and a handler added on every render would fire once for each of them. What a kept bar stands for can change, so the plugin notes it per element from `add` and `update` changes, and the handler reads it when it fires. Phase names are reserved: emitting `compose` or `mount` throws. A prefix such as `bar:` keeps your names apart from other plugins'. ### Plugins for live updates A chart drawn with [`liveDomRenderer()`](https://chartist.dev/docs/live-updates/) keeps its elements from one render to the next. That's good news for plugins: what they add to an element stays, hover and focus survive an update, and their own elements can move with the chart instead of popping in again. It asks a little more of them, though. This tooltip plugin shows the value of the point under the pointer. It's here to show how a plugin works with live updates; for tooltips on your charts, use [`@chartist-js/tooltips`](https://chartist.dev/packages/tooltips/). The chart gets a new value every second, and the tooltip stays on its point, shows the point's new value and moves in step with the chart's update animation. Press **Edit** and hover a point: **A tooltip that stays** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { lineChart, type ChartPlugin, type UpdateAnimation } from 'chartist'; import { liveDomRenderer, smilAnimations } from 'chartist/live'; // Shows the value of the point under the pointer, and stays on it // while the chart updates. const tooltip: ChartPlugin = { name: 'tooltip', install(chart) { const tip = document.createElement('div'); tip.className = 'tooltip'; tip.hidden = true; chart.container?.append(tip); let point: Element | undefined; const show = (animation?: UpdateAnimation) => { if (!point?.isConnected) { tip.hidden = true; return; } const from = tip.hidden ? '' : tip.style.transform; const to = `translate(${point.getAttribute('x1')}px, ${point.getAttribute('y1')}px)`; tip.textContent = point.getAttribute('ct:value'); tip.style.transform = to; tip.hidden = false; // Move along with the point, with the chart's timing. if (animation && from && from !== to) { tip.animate({ transform: [from, to] }, animation); } }; chart.on('mount', ({ changes, elementOf, animation }) => { // New points only: kept ones have their listeners already. for (const change of changes) { const element = elementOf(change.node); if (change.op !== 'add' || change.node.role !== 'point' || !element) { continue; } element.addEventListener('pointerenter', () => { point = element; show(); }); element.addEventListener('pointerleave', () => { point = undefined; tip.hidden = true; }); } // The hovered point may have a new value and place. show(animation); }); // The tooltip isn't part of the chart's SVG: remove it on destroy(). return () => tip.remove(); } }; let visitors = [12, 15, 14, 18, 21, 19, 23, 22]; const chart = lineChart( '#chart', { series: [visitors] }, { renderer: liveDomRenderer({ animations: smilAnimations() }), plugins: [tooltip], low: 0 } ); // A new count of visitors on the site every second. setInterval(() => { const last = visitors[visitors.length - 1]; const next = Math.max(0, last + Math.round(Math.random() * 8 - 4)); visitors = [...visitors.slice(1), next]; chart.update({ data: { series: [visitors] } }); }, 1000); ``` `src/style.css`: ```css #chart { position: relative; --ct-update-duration: 300ms; --ct-update-easing: ease-out; } #chart .ct-point { stroke-width: 14px; } .tooltip { position: absolute; top: 0; left: 0; margin: -2.6em 0 0 -1.5em; min-width: 3em; padding: 0.25em 0.5em; border-radius: 4px; background: #222; color: #fff; font: 600 0.8rem/1.4 system-ui, sans-serif; text-align: center; pointer-events: none; } ``` `index.html`: ```html
``` #### New elements, kept elements `mount` comes after every render, and in a live chart most of its elements were there before. Add listeners on `add` changes only, as the tooltip does, or they pile up on kept elements. Read what an element shows when you need it (the tooltip reads `ct:value` when it shows), not when you add the listener: a kept point can get a new value. And return a cleanup function from `install` for what lives outside the SVG, like the tooltip's element. #### Keys for your own nodes Nodes a plugin adds are matched between renders too, so they keep their elements. Without a key, a node is matched by its element name, its first class and its place among siblings like it. Give it a key with `node()` when that's not enough, for nodes that come and go or change their order: ```ts import { node, type ChartPlugin } from 'chartist'; /** Writes the last value of each line next to its end. */ export const lastValue: ChartPlugin = { name: 'last-value', install(chart) { chart.on('compose', series => { if (series.role !== 'series') { return; } const points = series.children.filter(child => child.role === 'point'); const last = points.at(-1); if (!last) { return; } const { x1, y1 } = last.attrs; return series.append( node('text', { x: Number(x1) + 8, y: y1 }, 'ct-last-value', { key: `last-value/${series.ctx.seriesIndex}` }).text(String(last.attrs['ct:value'])) ); }); } }; ``` #### Keyframes With update animations, numbers move from their old to their new value, and paths with the same commands move point by point. That's right for most nodes. For a node that should move differently, such as a needle that turns around its center, give the plugin `keyframes()`. The renderer asks it once per update, with the progress points of the chart's easing: 0 is the old node, 1 the new one, and points beyond them come from an easing that overshoots. The plugin returns one set of attributes per point, or nothing for the default: ```ts import type { ChartPlugin } from 'chartist'; /** * Turns the needle a gauge plugin draws, instead of morphing its path. * The gauge gives the needle's path its angle in `ct:angle`. */ export const turningNeedle: ChartPlugin = { name: 'turning-needle', install() {}, keyframes(previous, next, progress) { if (!next.classes.includes('ct-needle')) { return undefined; } const from = Number(previous.attrs['ct:angle']); const to = Number(next.attrs['ct:angle']); return progress.map(p => ({ transform: `rotate(${from + (to - from) * p} 100 100)` })); } }; ``` Plugins are asked first, in their order, then the layer that drew the node, so a plugin can also change how a built-in node moves. [Custom layers](https://chartist.dev/docs/custom-layers/) can have `keyframes()` too. #### Animate in step When the chart animates an update, `mount` gets its `animation`: the duration in milliseconds and the easing as a CSS string. Animate what your plugin draws with it, and it moves with the chart. For HTML elements, pass it to the Web Animations API as the tooltip does, `element.animate(frames, animation)`. For SVG elements, `Svg#animate()` takes the CSS easing as it is. When the chart doesn't animate (a resize, `domRenderer()`, or a reader who prefers less motion), `animation` is absent, and your plugin doesn't animate either. > **1.x plugins get a new SVG every time.** A chart with a plugin converted with > `fromLegacyPlugin` draws a new SVG on every render, even with > `liveDomRenderer()`, because 1.x `draw` listeners expect new elements. For a > chart that updates live, write a v2 plugin like the one above. ### Sharing a plugin A plugin is plain data and functions, so sharing one is publishing a module. Export a function that takes the options and returns the plugin, and type it with `ChartPlugin`: ```ts import type { ChartPlugin } from 'chartist'; export interface HighlightOptions { className?: string; } export function highlightLast(options: HighlightOptions = {}): ChartPlugin { const className = options.className ?? 'ct-last'; return { name: 'highlight-last', install(chart) { chart.on('compose', current => { if (current.role !== 'series') { return; } const last = current.children.at(-1); return last && current.replace(last, last.addClass(className)); }); } }; } ``` A few things keep a plugin working everywhere: - Change the scene in `compose` and `scene`, not the DOM. Then it also works when the chart is [rendered to a string](https://chartist.dev/docs/server-rendering/), where `render` and `mount` never happen. - Touch elements only in `mount`, and only through `elementOf`. - Set elements up on their `add` change, or return a cleanup function from `mount`. Then the plugin works with both renderers; see [Plugins for live updates](#plugins-for-live-updates). - List `chartist` as a peer dependency, so your plugin uses the reader's copy. ### Publishing a plugin Publish your plugin the way Chartist v2 is published: as an ES module with types. ```json { "name": "chartist-plugin-highlight-last", "type": "module", "exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } }, "files": ["dist"], "keywords": ["chartist", "chartist-plugin"], "peerDependencies": { "chartist": "^2.0.0" }, "sideEffects": false } ``` - **Import, don't read globals.** Import what you need from `chartist`. Don't read the global `Chartist`: it only exists on pages that load Chartist's UMD build, or that import `chartist/compat` for old plugins. - **No UMD build.** Pages without a bundler load ES modules directly from a CDN, and an import map tells the browser where `chartist` is, for your plugin's imports too: ```html ``` Chartist still publishes a UMD build and a global `Chartist`, but only so that old pages and the plugins of 0.x keep working. They are for backwards compatibility, not something new plugins should build on. - **Name it `chartist-plugin-…`** and add the `chartist-plugin` keyword, so people find it on npm. - **Work in `compose` and `scene`** where you can. Then your plugin also draws when a chart is [rendered on a server](https://chartist.dev/docs/server-rendering/), and the plugins page can tag it as SSR-capable. **Don't forget to tell us about your plugin.** Once it's on npm, open a pull request that adds it to the [plugins page](https://chartist.dev/plugins/), where readers find the community's plugins (the list is [`apps/docs/src/data/plugins.ts`](https://github.com/chartist-js/chartist/blob/next/apps/docs/src/data/plugins.ts)), or show it in [GitHub Discussions](https://github.com/chartist-js/chartist/discussions). > **Coming from 1.x?** v2 charts don't emit `draw`, `created` or the other 1.x > events. Listening to one logs a hint. Your 1.x plugins still run: wrap them > with `fromLegacyPlugin` from `chartist/compat`. See [Coming from > 1.x](https://chartist.dev/docs/coming-from-1x/), and the [plugins page](https://chartist.dev/plugins/) for the > community's plugins running on v2. In the API reference: [events and plugins](https://chartist.dev/api/events-and-plugins/), [the scene](https://chartist.dev/api/scene/) and [chartist/live](https://chartist.dev/api/live/). Next: [render charts on a server](https://chartist.dev/docs/server-rendering/), or [animate them](https://chartist.dev/docs/animations/). ## Custom layers > Write your own layers for chart() and the presets, from drawing to binding axes and series, widening axes and refining what other layers drew. Every chart in Chartist is drawn by layers: `line()`, `bar()`, `pie()`, the grid and the labels. They're plain objects with a few functions, and yours work exactly like the built-in ones. A layer is a good way to add a new kind of mark to a chart, and to share it: it's a function that returns an object, with no setup and nothing to install. Layers and [plugins](https://chartist.dev/docs/events-and-plugins/) do different jobs. A **layer draws** something of its own: lollipops, a heat strip, error bars. A **plugin changes** what's drawn and reacts to the chart's events: tooltips, legends, animations. If your idea is "a new kind of chart", it's a layer. ### A first layer A lollipop chart: a stem from zero and a dot for each value. The layer is in `src/lollipop.ts`; the chart uses it next to the grid and the labels. **A lollipop layer** (runs in the browser): `src/lollipop.ts`: ```ts import { alphaNumerate, node, type Layer } from 'chartist'; // The roles this layer gives its nodes, so listeners and plugins can find // them with a typed context. declare module 'chartist' { interface NodeRoles { lollipop: { ctx: { readonly seriesIndex: number; readonly valueIndex: number; readonly value: number; }; attrs: { readonly cx: number; readonly cy: number; readonly r: number }; }; } } export interface LollipopOptions { /** Series to draw, by name or index; default: all. */ readonly series?: readonly (number | string)[]; /** Axis ids; default: the first X and the first Y axis. */ readonly x?: string; readonly y?: string; } /** A stem from zero and a dot for each value. */ export function lollipop(options: LollipopOptions = {}): Layer { return { type: 'lollipop', // Drawn where bars are, so the chart gets the bar chart's class, which // centers the X labels. chartType: 'bar', coordinates: ['cartesian'], axes: { x: options.x, y: options.y }, series: options.series, // The stems start at zero, so the Y axis must include it. extent: ({ axes }) => (axes.y ? { [axes.y]: { low: 0, high: 0 } } : {}), compose({ area, axes, normalized, seriesIndexes }) { const { x, y } = axes; if (!x || !y || !normalized) { throw new Error('Layer "lollipop" needs an x and a y axis'); } // Centered in each step, where bars sit. const shift = x.kind === 'step' ? (x.step ?? 0) / 2 : 0; const base = area.y1 - y.project(0); return normalized.series.map((values, local) => { // The series' index in the whole chart, for its class and color. const seriesIndex = seriesIndexes?.[local] ?? local; let group = node( 'g', {}, `ct-series ct-series-${alphaNumerate(seriesIndex)}` ); values.forEach((value, valueIndex) => { // Values are normalized to { x, y }, or undefined for a hole. const v = value && typeof value === 'object' && 'y' in value ? value.y : undefined; if (v === undefined) { return; } const cx = area.x1 + shift + x.project(value, valueIndex, values); const cy = area.y1 - y.project(value, valueIndex, values); group = group .append( node('line', { x1: cx, y1: base, x2: cx, y2: cy }, 'ct-stem') ) .append( node('circle', { cx, cy, r: 6 }, 'ct-lollipop', { role: 'lollipop', ctx: { seriesIndex, valueIndex, value: v } }) ); }); return group; }); } }; } ``` `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { autoScaleAxis, axisGrid, axisLabels, cartesianAxes, chart, stepAxis } from 'chartist'; import { lollipop } from './lollipop'; chart('#chart', { data: { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [{ name: 'visits', data: [12, 9, 14, 7, 11] }] }, axes: cartesianAxes({ x: { type: stepAxis() }, y: { type: autoScaleAxis() } }), layers: [axisGrid(), lollipop(), axisLabels()] }); ``` `src/style.css`: ```css /* The series group sets --ct-color; stems and dots take it. */ .ct-stem { stroke: var(--ct-color); stroke-width: 2px; } .ct-lollipop { fill: var(--ct-color); } ``` `index.html`: ```html
``` Use it like any layer: in `layers` of [`chart()`](https://chartist.dev/docs/composed-charts/), or on a preset with `layers: { below: [...], above: [...] }`. ### What a layer is made of | Field | What it does | | ------------- | --------------------------------------------------------------------------------------------------- | | `type` | A name for error messages, such as `lollipop` | | `coordinates` | Where it can draw: `['cartesian']`, `['polar']` or both. Using it elsewhere throws | | `compose` | Draws the layer: gets the data, axes and chart area, returns scene nodes | | `axes` | The ids of the X and Y axis to draw on; by default the first of each | | `series` | The series to draw, by name or index; by default all of them | | `extent` | Widens the axes beyond the layer's values, such as to include zero | | `inset` | Reserves room outside the drawing area, such as for a title or a legend | | `refine` | Changes nodes that any layer drew, after all of them have drawn | | `annotate` | Draws from the finished nodes of the other layers, such as a label at each bar's end | | `chartType` | The chart class it gives `chart()` without a `className`: `'bar'` centers the X labels, for example | Only `type`, `coordinates` and `compose` are required. A layer is data: create it with a function that takes options, and keep no state in it. The same layer object may draw several charts, and every resize draws again. ### compose: drawing `compose(context)` runs on every render, after the axes are created. It returns the layer's top-level nodes, usually one group per series. Its context has: | Field | What it is | | --------------- | ---------------------------------------------------------------------------------------------- | | `area` | The drawing area in px: `x1`, `x2` (left, right), `y1` (bottom), `y2` (top), `width`, `height` | | `axes` | The bound `x` and `y` axes, and `all` axes of the chart | | `normalized` | The layer's series as the axes see them: `{ x, y }` values, `undefined` for holes | | `data` | The layer's series as given, with names, classes and meta | | `seriesIndexes` | Each series' index in the whole chart, when the layer selects some | | `layerIndex` | The layer's position in `layers` | | `measureText` | Measures text with the page's fonts, for layers that lay out labels | | `insetArea` | The boxes of the room the layer reserved with `inset`, one per side | **From values to px.** `axis.project(value, index, series)` gives the distance in px from the start of the axis. Y grows upward and SVG downward, so a Y position is `area.y1 - y.project(value)`, and an X position `area.x1 + x.project(value)`. A step axis places by index instead of value; its `step` is the width of one step. **Nodes.** Create them with `node(element, attributes, classes, { role, ctx })` and nest them with `append()`. A node with a `role` and a `ctx` (its data context) is what plugins and listeners see: they find it by role and read its values. Declare your roles in `NodeRoles`, as the example does, and `node()` and the listeners check their context. **Colors.** Put the series' nodes in a group with `ct-series ct-series-` (`alphaNumerate(seriesIndex)` gives the letter): the default stylesheet sets `--ct-color` on it, and your CSS reads it. Use the chart-wide index from `seriesIndexes`, so a series keeps its color whichever layer draws it. Keys come for free: nodes with a series and value index get one from the layer index, so the scene stays stable between renders. ### Axes and series A layer draws on one X and one Y axis. Give it `x` and `y` options and pass them on as `axes`, as the built-in layers do, so users can bind it to any axis by id. The `series` option works the same way: pass it on as `series`, and `compose` gets only those series, with their chart-wide indexes in `seriesIndexes`. The selection also decides what the axes fit: **each axis covers the series of the layers bound to it.** A layer that draws no series (a reference line, an annotation) sets `series: []`, so it doesn't shape any axis. **extent** adds to that. It runs before the axes are created and returns, by axis id, the range the layer needs: ```ts import type { Layer } from 'chartist'; export const withZero = (layer: Layer): Layer => ({ ...layer, // Keep zero on the layer's Y axis, wherever its values are. extent: ({ axes }) => (axes.y ? { [axes.y]: { low: 0, high: 0 } } : {}) }); ``` The axis covers every layer's extent and the data together. An axis's own `low` and `high` still win. ### refine: changing what others drew Some layers need to change other layers' nodes: the [band](https://chartist.dev/docs/bands/#in-or-out-of-the-band) marks the points inside it. That's `refine(node, context)`. Once every layer has composed, it's called for each node with a role, from all layers, before the `compose` events reach plugins: ```ts import type { Layer } from 'chartist'; /** Marks the highest point of every line with ct-max. */ export const highlightMax = (): Layer => ({ type: 'highlightMax', coordinates: ['cartesian'], // Draws nothing of its own. compose: () => [], refine(current, { normalized }) { if (current.role !== 'point' || !normalized) { return undefined; } const ys = normalized.series .flat() .flatMap(value => value && typeof value === 'object' && 'y' in value ? [value.y] : [] ); const { value } = current.ctx; const y = value && typeof value === 'object' && 'y' in value ? value.y : undefined; return y === Math.max(...ys) ? current.addClass('ct-max') : undefined; } }); ``` Refining has rules, so layers can't break each other: - **One node at a time.** `refine` gets the node it's called for, not the scene. To compare with other values, work from the data in the context, as above. - **No structural changes.** Return the node with changed attributes, classes or context, or `undefined` to leave it. Whatever you return keeps the node's key and children, and refining can't drop a node. Removing and rearranging nodes is a plugin's job. - **Your own context.** The context is the one your `compose` got: your layer's data and axes. A layer with `series: []` sees no data here either; leave `series` out, as above, to see all of it. - **Order.** Every layer composes first, so a layer can refine the nodes of layers listed after it. Refining layers run in the order of `layers`, and plugins see the result in their `compose` events, where they can still change it. - **No state.** Don't count or remember between calls: the same layer object may refine several charts. Leave `refine` out when you don't need it: charts without a refining layer skip the step. ### annotate: drawing from what others drew `refine` changes nodes but can't add any. To draw something where another layer's nodes ended up, such as a label at each bar's end after stacking, use `annotate(drawn, context)`. It runs once the other layers' nodes are composed, refined and through the plugins' `compose` events, and gets those finished nodes. What it returns is drawn at the layer's place in `layers`: ```ts import { findAll, getMultiValue, node, type Layer } from 'chartist'; // Writes each bar's value past its end. export const barValues: Layer = { type: 'barValues', coordinates: ['cartesian'], series: [], compose: () => [], annotate: drawn => drawn.flatMap(top => findAll(top, { role: 'bar' }).map(bar => node('text', { x: bar.ctx.x2, y: bar.ctx.y2 - 4 }, 'ct-bar-value').text( String(getMultiValue(bar.ctx.value, 'y')) ) ) ) }; ``` A bar a plugin dropped isn't in `drawn`, and a moved one is where it ends up. Annotating layers don't see each other's nodes. A bar's context also says whether it's `horizontal` and `stacked`. [`valueLabels()`](https://chartist.dev/packages/essentials/value-labels/) of `@chartist-js/essentials` is built this way. ### inset: room outside the chart Titles, notes and legends sit next to the chart, not on it. A layer that draws there reserves the room it needs with `inset(context)`, in px per side. Chartist adds the room to the padding before the axes take theirs, so the drawing area shrinks and the axis labels stay next to it. You don't touch `chartPadding`: **A source note below the chart** (runs in the browser): `src/source.ts`: ```ts import { node, textAscent, type Layer } from 'chartist'; declare module 'chartist' { interface NodeRoles { source: { ctx: { readonly text: string }; attrs: { readonly x: number; readonly y: number }; }; } } /** A note on where the data comes from, below the chart, right-aligned. */ export function source(text: string, gap = 8): Layer { const classes = ['ct-source']; return { type: 'source', coordinates: ['cartesian'], // Draws no series, so it doesn't shape any axis. series: [], // Room for one line below the X axis labels, measured with the page's font. inset: ({ measureText }) => ({ bottom: measureText(text, { classes }).height + gap }), compose({ insetArea, measureText }) { const box = insetArea?.bottom; if (!box) { return []; } const { fontSize = 12 } = measureText(text, { classes }); // The line starts `gap` px below the labels; the baseline sits // `textAscent` of a line lower. const x = box.x2; const y = box.y2 + gap + textAscent * fontSize; return [ node('text', { x, y, 'text-anchor': 'end' }, 'ct-source', { role: 'source', ctx: { text } }).text(text) ]; } }; } ``` `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { lineChart } from 'chartist'; import { source } from './source'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[12, 9, 14, 7, 11]] }, { low: 0, layers: { above: [source('Source: web analytics, week 20')] } } ); ``` `src/style.css`: ```css .ct-source { fill: var(--ct-label-color, rgba(0, 0, 0, 0.4)); font-size: 0.7rem; font-style: italic; } ``` `index.html`: ```html
``` `inset` runs on every layout, before the axes are created, so the room can depend on the chart's size. Its context has the chart's `width` and `height`, the `padding` option, the layer's series in `data`, the axis definitions in `axes` (with their ids, sides and offsets) and `measureText`, so the room follows the stylesheet's font. `compose` then gets the room as `insetArea`: one box per side the layer reserved, an `Area` like the drawing area. A box sits just outside the axes on its side and runs along the drawing area. Layers that reserve room on the same side stack outward in the order of `layers`, the way axes do. The `layout` event shows the total as `inset`, and charts without such a layer lay out as before. [Axis titles](https://chartist.dev/packages/essentials/axis-titles/) in `@chartist-js/essentials` are built this way. ### Sharing a layer A layer is a function, so a package of layers is a module of functions. Depend on `chartist` as a peer dependency, export a function per layer with typed options, and ship a small stylesheet with your classes, using `--ct-color` and the other `--ct-*` properties so the user's theme applies. What you get for free: - **Presets:** users add your layer to `lineChart()` and `barChart()` with `layers`, no `chart()` needed. - **Server rendering:** layers are pure, so they work in [`toScene()` and `renderToString()`](https://chartist.dev/docs/server-rendering/). - **The terminal:** `@chartist-js/terminal` draws nodes of unknown roles by their element (lines, paths, circles, text). - **Plugins:** your nodes go through the `compose` events like the built-in ones. In the API reference: [`Layer`](https://chartist.dev/api/layers/#Layer), [`LayerContext`](https://chartist.dev/api/layers/#LayerContext) and [`node`](https://chartist.dev/api/scene/#node). Next: [events and plugins](https://chartist.dev/docs/events-and-plugins/), to change charts instead of drawing on them, or the [band layer](https://chartist.dev/docs/bands/). ## Server rendering > Build a chart's scene without a browser and turn it into SVG markup, for server-rendered pages, PDFs, email and images. A Chartist chart doesn't need a browser until the very end. The pipeline builds a scene, a plain tree of SVG elements, and only the last step writes it into the page. Stop before that step, and you can render the scene to a string anywhere JavaScript runs: in Node.js, in a server bundle, in a worker. ### Two outputs What you render depends on where the markup goes: - **A self-contained SVG image**, from `toScene()` and `renderToString()`: a picture of the chart for files, PDFs, email, social images or a static chart in a page. It carries its inline styles, since there's often no stylesheet where it ends up. This page is about it. - **Hydration markup**, from `renderForHydration()`: a page fragment that the browser takes over and keeps running, with updates, plugins and animations. It has no inline styles, needs Chartist's stylesheet in the page and passes a strict `style-src` policy. See [SSR with hydration](https://chartist.dev/docs/ssr-hydration/). ### Two functions > **Experimental.** The server API of `chartist/server` (`renderToString()`, > `renderForHydration()`, `computeStyles()` and `applyStyles()`) is new with > 2.1. It may still change in a 2.x minor before it becomes stable; `toScene()` > and the rest of Chartist are stable. `toScene(config, size)` runs the pipeline from `resolve` to `scene` and returns the scene. It takes the same config as `chart()`, with your plugins and listeners, plus the size to lay out at. It touches no DOM, timers or globals. `renderToString(scene)` turns a scene into standalone SVG markup, namespaces included. Both come from `chartist/server`, the entry point for rendering without a DOM; `toScene()` is the one from `chartist`, re-exported there so server code needs one import. In 2.0, `renderToString()` came from `chartist`: import it from `chartist/server` now. ```ts import { lineChartConfig } from 'chartist'; import { renderToString, toScene } from 'chartist/server'; export function weekChart(values: number[]): string { const scene = toScene( lineChartConfig({ labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [values] }), { width: 600, height: 300 } ); return renderToString(scene); } ``` The presets (`lineChart()` and friends) draw into an element. For their charts as a string, `lineChartConfig()`, `barChartConfig()` and `pieChartConfig()` build the config a preset draws, from the same data and options: `toScene(lineChartConfig(data, options), size)`. `toScene()` takes any config of `chart()`, so [composed charts](https://chartist.dev/docs/composed-charts/) with their own axes and layers render the same way. The browser can run the same code. This example builds the scene with `toScene()` and shows its markup from `renderToString()` below the chart. The chart itself comes from the same scene through `domRenderer()`: in a page, the DOM renderer creates the elements, so nothing goes through `innerHTML`, which [a strict Content Security Policy](https://chartist.dev/docs/security-and-csp/) can block. **Rendered to a string** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { domRenderer, lineChartConfig, toScene } from 'chartist'; import { renderToString } from 'chartist/server'; // The same code runs on a server: no DOM needed up to here. const scene = toScene( lineChartConfig( { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [ [12, 9, 7, 8, 5], [2, 1, 3.5, 7, 3] ] }, { viewBox: { width: 400, height: 250 } } ), { width: 400, height: 250 } ); // On a server, the scene becomes SVG markup: a string for a response or // a file. const markup = renderToString(scene); // In the browser, the DOM renderer draws the same scene without parsing // markup, so the page needs no innerHTML (which a strict Content Security // Policy can block). The string is shown as text below the chart. const container = document.querySelector('#chart'); if (container) { domRenderer().render(scene, container); } const source = document.querySelector('#markup'); if (source) { source.textContent = markup; } ``` `src/style.css`: ```css /* The markup has a fixed size and a viewBox, so CSS can scale it. */ #chart svg { display: block; width: 100%; height: auto; } /* The same chart as SVG markup: a few lines of the string. */ #markup { max-height: 8em; margin: 1em 0 0; overflow: auto; font-size: 11px; white-space: pre-wrap; word-break: break-all; } ``` `index.html`: ```html

  

```

### Size

A server has no container to measure, so you pass the size: the scene is laid out at `width` × `height`, and the root `svg` gets those as its `width` and `height`. To let the markup scale with its container, add a `viewBox` of the same size to the config, as the example does, and size the SVG with CSS:

```css
.chart svg {
  width: 100%;
  height: auto;
}
```

### Measuring text

SVG labels (see [below](#svg-or-html-labels)) are wrapped into lines and aligned by Chartist, so it needs to know how wide text is. In the browser, it measures the text with the page's fonts. On a server, `toScene()` estimates it: each character is 0.6 em wide and a line 1.2 em high, at 12px unless told otherwise. That's close enough for numbers and short labels.

When you know your fonts, pass a `measureText` function in the size, next to `width` and `height`. It gets the text and options with the `fontSize` (and the label's `classes`), and returns a `width` and `height` in px:

```ts
import type { MeasureText } from 'chartist';

// Wider glyphs than the estimate, for a wide font.
export const measureText: MeasureText = (text, options = {}) => {
  const fontSize = options.fontSize ?? 12;
  return { width: text.length * fontSize * 0.7, height: fontSize * 1.25 };
};
```

Then call `toScene(config, { width: 600, height: 300, measureText })`.

### SVG or HTML labels

Chartist draws axis labels in one of two ways, set with the `labels` option:

- `html`: a `` inside a ``, as 1.x did. CSS lays them out, wraps long text and aligns it with flexbox. Only browsers draw them.
- `svg`: an SVG ``, wrapped and aligned by Chartist. Every SVG viewer draws these.

The renderer picks the default: `chart()` and the presets draw HTML labels in the page, and `toScene()` draws SVG labels, because string output often ends up in tools without HTML support. Set `labels` to choose for yourself, in the config (`labels: 'html'`) or in the size of `toScene()`. The config wins.

`axisLabels()` takes `labels` too, for one layer, and so do `lineChart()` and `barChart()` as an option. The 1.x classes always draw HTML labels, and pie and donut labels are always SVG text.

### Where the markup goes

The markup has the same classes as a chart in the page, but no styles. Where it ends up decides how it gets them.

**Server-rendered pages.** Put the string into your HTML and load `chartist/styles.css` (or your own chart styles) on the page, like for any chart. Since the page is HTML, HTML labels work, if you want them. If the chart should run in the page once its script loads, render it for [SSR with hydration](https://chartist.dev/docs/ssr-hydration/) instead.

**PDFs.** If you print HTML to PDF with a headless browser, it's the same as a web page. Other PDF tools read the SVG on its own: there, use SVG labels and write the styles into the SVG.

**Email and images.** Many email clients don't show SVG at all, so send a PNG. Convert the markup with an SVG rasterizer, for example in a Node.js service, and use SVG labels with the styles written into the SVG. Rasterizers don't draw HTML, so HTML labels would be missing.

#### Styles in the SVG

Tools that read SVG on its own know less CSS than browsers. Chartist's stylesheet colors each series through a custom property (`stroke: var(--ct-color)`, set on the series group) and the grid and labels with `light-dark()`. Rasterizers such as resvg support neither, and draw the lines without their color. So don't copy the stylesheet into the SVG. Write it into the scene with `applyStyles()`:

```ts
import { readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { lineChartConfig } from 'chartist';
import { applyStyles, renderToString, toScene } from 'chartist/server';

// Chartist's stylesheet, from the package, and your own after it.
const require = createRequire(import.meta.url);
const css = [
  readFileSync(require.resolve('chartist/styles.css'), 'utf8'),
  '.ct-series-a { --ct-color: #0b7285; }'
];

const scene = toScene(
  lineChartConfig({
    labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
    series: [[12, 9, 7, 8, 5]]
  }),
  { width: 600, height: 300 }
);

export const markup = renderToString(applyStyles(scene, css));
```

`applyStyles(scene, css)` matches the rules against the scene as a browser would and writes the values each element gets as SVG attributes, such as `stroke="#0b7285"` on the line. Custom properties are resolved per element, inherited through the scene, and `light-dark()` takes its light value. Pass `{ colorScheme: 'dark' }` for the dark values and the `@media (prefers-color-scheme: dark)` rules. The classes stay, and the scene you pass in isn't changed.

#### How Chartist reads your CSS

`applyStyles()` uses `computeStyles()`, a CSS engine of its own: deliberately small, made for the CSS charts are styled with, and without dependencies. It isn't a browser, and some CSS it leaves out. What it reads:

- type, class and attribute selectors, `:root`, `:not()`, `:is()` and `:where()`, and the descendant and child combinators;
- the cascade: `!important`, an element's own `style`, specificity, order;
- custom properties, inherited through the scene, with `var()` fallbacks, and `light-dark()`;
- `@media (prefers-color-scheme)` for the color scheme you pass, and `@supports` and `@layer` blocks, in place (without layer order);
- the page around the chart: rules for `:root`, `html` and `body` apply, so a theme's custom properties on `:root` reach the chart, and inherited properties such as `color` and `font-family` set there reach the svg.

What it doesn't:

| CSS                                                                          | What happens                                                                                |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| Sibling and structural selectors (`+`, `~`, `:nth-child()`), ids, `:hover`   | The rule is left out.                                                                       |
| Nested rules                                                                 | Left out.                                                                                   |
| The `font` shorthand                                                         | Left out: write `font-family`, `font-size` and the others.                                  |
| At-rules other than the ones above (`@media print`, `@container`, `@import`) | Their rules are left out.                                                                   |
| `calc()`, relative units (`rem`, `em`, `%`), newer color functions           | Written as they are. Browsers read them, many rasterizers don't: use `px` and plain colors. |
| HTML labels (`foreignObject`)                                                | Left as they are. Rasterizers don't draw them: use SVG labels.                              |

Pass `onSkip` to `computeStyles()` to find out what it left out, with the reason:

```ts
import { lineChartConfig } from 'chartist';
import { applyStyles, computeStyles, toScene } from 'chartist/server';

const css = '.ct-label { font: 14px Georgia; } .ct-line:hover { stroke: red; }';
const scene = toScene(lineChartConfig({ series: [[5, 2, 4]] }), {
  width: 600,
  height: 300
});

const styles = computeStyles(scene, css, {
  // shorthand: font: 14px Georgia
  // selector: .ct-line:hover
  onSkip: ({ reason, text }) => console.warn(`${reason}: ${text}`)
});
export const styled = applyStyles(scene, styles);
```

#### Another CSS engine

For CSS beyond that, compute the styles with something that knows all of it: a browser. `applyStyles()` takes any function that gives a scene node its values (a `ComputedStyles`), not only the one from `computeStyles()`. This one renders the markup with your stylesheet in headless Chromium, with [Playwright](https://playwright.dev/), and reads each element's computed style:

```ts nocheck
import type { SceneNode } from 'chartist';
import { chromium } from 'playwright';
import {
  applyStyles,
  renderToString,
  type ComputedStyles
} from 'chartist/server';

// The properties to read: the ones your stylesheet sets.
const properties = ['fill', 'stroke', 'stroke-width', 'opacity', 'font-size'];

export async function browserStyles(
  scene: SceneNode,
  css: string
): Promise {
  const browser = await chromium.launch();
  try {
    const page = await browser.newPage();
    await page.setContent(`${renderToString(scene)}`);
    // The computed values of every element, in document order.
    const values = await page.$$eval(
      'svg, svg *',
      (elements, names) =>
        elements.map(element => {
          const style = getComputedStyle(element);
          return Object.fromEntries(
            names.map(name => [name, style.getPropertyValue(name)])
          );
        }),
      properties
    );
    // The scene's nodes are in the same order.
    const nodes: SceneNode[] = [];
    const visit = (node: SceneNode) => {
      nodes.push(node);
      node.children.forEach(visit);
    };
    visit(scene);
    const byNode = new Map(nodes.map((node, index) => [node, values[index]]));
    return node => byNode.get(node);
  } finally {
    await browser.close();
  }
}

// const styled = applyStyles(scene, await browserStyles(scene, css));
```

It costs a browser on your server, and it's slower: use it when the small engine doesn't cover your CSS.

> **Pass only your own CSS.** The values end up in the markup, `url()` included.
> They're written as escaped attribute values, so CSS can't add elements, but
> treat a stylesheet like code: pass only the ones your application controls,
> never one from a user.

> **Plugins run on the server too.** `toScene()` installs the config's plugins
> and calls their listeners, up to the `scene` phase. Plugins that change the
> scene work there unchanged; listeners of `render` and `mount` never run, and
> calling `update()`, `detach()` or `destroy()` throws. 1.x plugins converted
> with `fromLegacyPlugin` don't run there: their `draw` and `created` events
> come from the `mount` phase. See [Events and plugins](https://chartist.dev/docs/events-and-plugins/).

In the API reference: [rendering](https://chartist.dev/api/rendering/) and [chartist/server](https://chartist.dev/api/server/).

Next: keep a server-rendered chart running in the page with [SSR with hydration](https://chartist.dev/docs/ssr-hydration/).


## SSR with hydration

> Render a chart into the server's HTML and let the browser take it over, keeping its elements, with live updates, plugins and animations from there on.

A chart in the server's HTML shows up with the page's first paint, before any script loads. With hydration, the browser then takes that chart over instead of drawing it again: the chart keeps the server's elements and gets updates, plugins and animations from there on.

The server renders the chart with `renderForHydration()`, and the browser creates the chart in the same container with `liveDomRenderer()`. For a static picture of a chart (files, PDFs, email), use the self-contained SVG image from [Server rendering](https://chartist.dev/docs/server-rendering/) instead.



> **Only `liveDomRenderer()` hydrates.** It comes from `chartist/live` and
> keeps the server's elements. With the default renderer, the chart draws a
> new svg over the server's: that works, but the elements are new, so a CSS
> animation that started with the page starts again.
> `liveDomRenderer({ adopt: false })` draws a new svg too. 1.x classes and
> charts with plugins converted with `fromLegacyPlugin` always draw a new svg.

### On the server and in the browser

On the server, render the chart's markup into its container:

```ts
import { lineChartConfig } from 'chartist';
import { renderForHydration } from 'chartist/server';

const data = {
  labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
  series: [[12, 9, 7, 8, 5]]
};

// On the server, inside the chart's container: 
…
export const markup = renderForHydration(lineChartConfig(data), { width: 600, height: 300 }); ``` In the browser, create the chart in that container, with the same data and options: ```ts import { lineChart } from 'chartist'; import { liveDomRenderer } from 'chartist/live'; const data = { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[12, 9, 7, 8, 5]] }; // The chart keeps the server's svg and its elements. const chart = lineChart('#chart', data, { renderer: liveDomRenderer() }); chart.update({ data: { ...data, series: [[3, 9, 7, 8, 11]] } }); ``` The browser doesn't read the data from the markup: it builds the same scene again from its own config, at the size the server recorded, and binds it to the server's elements. From there on, it's a chart like any other. The example's page holds the server markup in its `index.html`, so you see the server's chart first. **Hydrate** creates the chart, which a real page does right away. Below the chart, the example counts what each render did and how many of the server's elements are still there. **New values** updates the chart: values move, and when the axis needs other ticks, grid lines and labels are added and removed. **SSR with hydration** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { lineChart, type ChartHandle, type SceneChange } from 'chartist'; import { liveDomRenderer } from 'chartist/live'; import { data, options } from './chart'; const status = document.querySelector('#status')!; const renders: string[] = []; // The elements the server rendered, before the chart runs. const server = Array.from( document.querySelectorAll('#chart svg, #chart svg *') ); /** How many changes of each kind a render made. */ function count(changes: readonly SceneChange[]) { const ops = { add: 0, update: 0, remove: 0 }; changes.forEach(change => ops[change.op]++); return `${ops.add} added, ${ops.update} changed, ${ops.remove} removed`; } const hydrateButton = document.querySelector('#hydrate')!; const updateButton = document.querySelector('#update')!; let chart: ChartHandle | undefined; // #chart holds the server's markup. On a real page, the chart is created // right away; here, Hydrate creates it, so you can look at the markup first. // The chart takes the server's elements over instead of drawing new ones, // and changes them on every later render. hydrateButton.addEventListener('click', () => { chart = lineChart('#chart', data, { ...options, renderer: liveDomRenderer(), on: { mount: ({ cause, changes, hydration }) => { const kept = server.filter(element => element.isConnected).length; const render = cause === 'hydrate' ? `hydrate: took ${changes.length} elements over, ${hydration?.repaired} repaired` : `${cause}: ${count(changes)}`; // The last three renders, and what's left of the server's elements. renders.push(render); renders.splice(0, renders.length - 3); status.textContent = `${renders.join('\n')}\n${kept} of the server's ${server.length} elements are still in the chart.`; } } }); hydrateButton.disabled = true; updateButton.disabled = false; }); const random = () => Math.round(Math.random() * 120) / 10; updateButton.addEventListener('click', () => { chart?.update({ data: { ...data, series: data.series.map(series => series.map(random)) } }); }); ``` `src/chart.ts`: ```ts import { lineChartConfig } from 'chartist'; import { renderForHydration } from 'chartist/server'; // The chart, as the server and the browser both draw it. export const data = { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [ [12, 9, 7, 8, 5], [2, 1, 3.5, 7, 3] ] }; export const options = { low: 0, showArea: true }; // On the server: the markup in index.html, laid out at 600 × 300. A docs // script writes it there from this function, so it stays what the current // build renders. export function serverMarkup(): string { return renderForHydration(lineChartConfig(data, options), { width: 600, height: 300 }); } ``` `src/style.css`: ```css /* The server's size, so the markup takes its space before the chart runs. */ #chart { aspect-ratio: 2 / 1; } #chart svg { display: block; } #status { margin: 0.5em 0 0; white-space: pre-line; font-size: 13px; } .controls { display: flex; gap: 0.5em; margin: 0.5em 0 0; } ``` `index.html`: ```html
MonTueWedThuFri02.557.51012.5

The server's markup: no chart runs yet.

``` ### The hydrate render The takeover is the chart's first render: `render` and `mount` run with the cause `hydrate`, and `mount` gets the server's elements as added, so plugins work on them as on a first render. The events also carry `hydration.repaired`: how many attributes, classes and texts of the markup differed from what the browser built, and were set. It's 0 when the server and the browser drew the same data and options. Log it in development to catch the two drifting apart: ```ts import { lineChart } from 'chartist'; import { liveDomRenderer } from 'chartist/live'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed'], series: [[12, 9, 7]] }, { renderer: liveDomRenderer(), on: { mount: ({ cause, hydration }) => { if (cause === 'hydrate' && hydration?.repaired) { console.warn(`The server drew ${hydration.repaired} differences`); } } } } ); ``` #### What causes repairs Anything that makes the server and the browser draw different values: - **Data that changed in between.** The browser fetched it again, or the data has a timestamp or a random value. - **Formatting that depends on the environment.** A `labelTransform` with `toLocaleString()` or `Intl` formats for the server's locale and time zone, the browser for the reader's: the label texts differ. - **Options or plugins on one side only.** A class, a `low` or a plugin that changes the scene only in the browser. (Plugins that act in `mount` don't count: they run after the takeover.) - **A custom `measureText` on the server**, with SVG labels: the browser replays with the built-in estimate, so label positions differ. The chart shows the browser's values, so readers see the right chart either way. Styles don't count: the markup leaves them out on purpose (see [Styles and text](#styles-and-text)). A different structure can't be repaired: another number of points or series, or an axis with other ticks because the values span another range. Then the chart warns, names the first element that differs and draws a new svg in place of the server's. ### Choosing the server's size A server has no container to measure, so it lays the chart out at the size you give it and records that size. The markup's svg is `100%` wide and high (unless the config sets a size), with a `viewBox` of the server's size, so the drawing scales into its container until the chart runs. Then the chart lays itself out at the container's real size. How close the two sizes are decides what readers see at the takeover: - **The same size:** nothing is drawn again. The chart keeps the server's layout, and the takeover is invisible. - **A narrower container:** before the chart runs, the whole drawing is scaled down, text and lines included: a 600 px wide layout in a 343 px phone column shows 12 px labels at about 7 px. At the takeover, the chart lays out again at 343 px, so the labels get their real size and the points, grid lines and labels move. An auto-scaled axis can also get fewer ticks, since it keeps them at least `scaleMinSpace` px apart. - **A wider container:** the same, scaled up before the takeover. The elements stay either way: the chart changes them instead of drawing new ones. That relayout isn't animated, even with `smilAnimations()`, since the drawing's scale changes in the same step. Responsive options apply only from the takeover on too, since the server draws without them. #### The server can't know the width A request doesn't say how wide the reader's window is, so the server has to pick one size for everyone: - **The width your layout gives the chart most often**, for example its column on a desktop, or a phone's width if most of your readers come on phones. A static site, built once for every reader, can only do this. - **A size per device**, if your server renders per request. [Client hints](https://developer.mozilla.org/en-US/docs/Web/HTTP/Client_hints) like `Sec-CH-Viewport-Width` say it, but only Chromium browsers send them, and only after the server asked with `Accept-CH`, so not on a first visit. A cookie from an earlier visit works in every browser, from the second visit on. Either way, the page then varies per reader, so caches need to know (`Vary`). HTML labels that move at the takeover count as a small layout shift (CLS), around 0.002 to 0.03 in our tests; SVG labels don't. #### Give the container a size Give the container a height or an `aspect-ratio` that matches the server's size, and make the svg a block, as for every chart that fills its container: ```css #chart { aspect-ratio: 2 / 1; } #chart svg { display: block; } ``` With a matching `aspect-ratio`, the server's drawing fills the container exactly; with another one, it sits letterboxed inside it until the takeover. Without a height, the server markup looks right, but the chart collapses to 150 px once it runs, as it would without server rendering. See [Sizing charts](https://chartist.dev/docs/sizing-charts/). ### Styles and text Hydration markup has no `style` attributes. Chartist's stylesheet sizes the label boxes and the root's attributes size the svg; a donut's `donutWidth` becomes a `stroke-width` attribute. So the markup looks the same before and after the takeover, and it passes a policy of `style-src 'self'` (see [Security and CSP](https://chartist.dev/docs/security-and-csp/)). Load Chartist's stylesheet in the page, as for every chart. Styles that your `scene` listeners add appear only once the chart runs: for what should show before, use classes and CSS. Use the built-in text estimate on the server, without a `measureText`: the browser replays the server's layout with it. ### Render the markup once The chart owns its svg after the takeover. Code that writes the server markup into the container again, as some frameworks' raw-HTML features do when a page updates, leaves the chart drawing into an svg that's no longer on the page. Chartist warns on its next render and draws a new svg, but the fix is to render the markup into the container only once. [Charts in frameworks](https://chartist.dev/docs/frameworks/) shows how, in React, Vue, Angular and Svelte. In the API reference: [rendering](https://chartist.dev/api/rendering/). Next: [charts in frameworks](https://chartist.dev/docs/frameworks/). ## Charts in frameworks > Use Chartist in React, Vue, Angular and Svelte, with the chart rendered on the server and taken over in the browser. Chartist doesn't need a framework wrapper: a chart is a container element and a handle. (The community has written [wrappers](#community-wrappers) anyway, for React, Angular, Vue and more.) A component creates the chart once, passes new data to the handle and destroys it when it goes away. With server rendering, the component also puts the chart's [hydration markup](https://chartist.dev/docs/ssr-hydration/) into the server's HTML, and the chart takes it over in the browser. ### The rule The framework owns the container, Chartist owns everything inside it: - Render the server markup into the container **once**, and never again. In the browser, the framework doesn't touch the container's children. - Create the chart after the framework's hydration, with `liveDomRenderer()` from `chartist/live`, so it keeps the server's elements. - Pass new data and options to the chart's handle: `update()`, or a preset's `setOptions()`. - Destroy the chart when the component unmounts. Every framework's raw-HTML feature writes the markup again in some situation, without an error. The chart then draws into an svg that's no longer in the page. Chartist warns about it on the next render (`other code removed the chart's svg`), and each recipe below names its framework's trap. Without a framework, that's this lifecycle. Mount, update and unmount the chart: **The lifecycle of a chart** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { lineChart, type ChartHandle } from 'chartist'; import { liveDomRenderer } from 'chartist/live'; const container = document.querySelector('#chart')!; const log = document.querySelector('#log')!; const labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']; const random = () => labels.map(() => Math.round(Math.random() * 10)); // What a component does with a chart: one handle while it's mounted. let chart: ChartHandle | undefined; function mount() { // Created once, after the framework put the container in the page. chart ??= lineChart( container, { labels, series: [random()] }, { low: 0, renderer: liveDomRenderer(), on: { mount: ({ cause, changes }) => { log.textContent = `${cause}: ${changes.length} changes`; } } } ); } function update() { // New data goes to the handle; the chart keeps its svg. chart?.update({ data: { labels, series: [random()] } }); } function unmount() { // Removes the svg and everything the chart listened to. chart?.destroy(); chart = undefined; log.textContent = 'destroyed'; } document.querySelector('#mount')!.addEventListener('click', mount); document.querySelector('#update')!.addEventListener('click', update); document.querySelector('#unmount')!.addEventListener('click', unmount); mount(); ``` `src/style.css`: ```css #chart { aspect-ratio: 2 / 1; } #chart svg { display: block; } .controls { display: flex; flex-wrap: wrap; gap: 0.5em; margin: 0.5em 0 0; } #log { margin: 0.5em 0 0; font-size: 13px; } ``` `index.html`: ```html

``` Give the container a height or an `aspect-ratio` that matches the size the server lays the chart out at, and make the svg a block. The recipes leave that to your stylesheet: ```css .chart { aspect-ratio: 2 / 1; } .chart svg { display: block; } ``` ### React Freeze the markup when the component mounts: React writes `dangerouslySetInnerHTML` into the container again whenever its `__html` string changes. Create the chart in `useLayoutEffect`, before the browser paints: ```tsx 'use client'; import { lineChart, lineChartConfig, type ChartHandle, type LineChartData } from 'chartist'; import { liveDomRenderer } from 'chartist/live'; import { renderForHydration } from 'chartist/server'; import { useEffect, useLayoutEffect, useRef, useState } from 'react'; export function LineChart({ data }: { data: LineChartData }) { const container = useRef(null); const chart = useRef(undefined); // Frozen: a new string would make React write the container again. const [html] = useState(() => ({ __html: renderForHydration(lineChartConfig(data), { width: 600, height: 300 }) })); useLayoutEffect(() => { const handle = lineChart(container.current!, data, { renderer: liveDomRenderer() }); chart.current = handle; return () => handle.destroy(); }, []); // New data from the parent; the first run is the chart's own data. const drawn = useRef(data); useEffect(() => { if (drawn.current !== data) { drawn.current = data; chart.current?.update({ data }); } }, [data]); return (
); } ``` In development, Strict Mode runs the effect twice: the first chart is destroyed with its svg, and the second draws a new one. That's harmless, and production builds take the server's svg over. ### Vue Give `v-html` the markup on the server only. In the browser, Vue writes any `v-html` string into the container while it hydrates, even the same one, without a warning; `undefined` it leaves alone: ```vue ``` `import.meta.server` is Nuxt's. With Vue's own server rendering through Vite, use `import.meta.env.SSR`. ### Angular Leave the template empty and write the markup into the host element on the server. A template binding is the trap here: `[innerHTML]` strips the SVG through Angular's sanitizer, `bypassSecurityTrustHtml()` gets it set again on the first change detection, and `ngSkipHydration` renders the component again. Create the chart in `afterNextRender`: ```ts nocheck import { isPlatformServer } from '@angular/common'; import { afterNextRender, Component, DestroyRef, effect, ElementRef, inject, input, PLATFORM_ID, untracked, ViewEncapsulation } from '@angular/core'; import { lineChart, lineChartConfig, type ChartHandle, type LineChartData } from 'chartist'; import { liveDomRenderer } from 'chartist/live'; import { renderForHydration } from 'chartist/server'; @Component({ selector: 'line-chart', // Angular owns the host element, Chartist what's inside it. template: '', encapsulation: ViewEncapsulation.None, styles: 'line-chart, line-chart > svg { display: block; }' }) export class LineChartComponent { readonly data = input.required(); private readonly host: HTMLElement = inject(ElementRef).nativeElement; private chart?: ChartHandle; private drawn?: LineChartData; constructor() { if (isPlatformServer(inject(PLATFORM_ID))) { // An effect, so data that arrives later in the server render counts. effect(() => { this.host.innerHTML = renderForHydration(lineChartConfig(this.data()), { width: 600, height: 300 }); }); return; } afterNextRender(() => { this.drawn = untracked(this.data); this.chart = lineChart(this.host, this.drawn, { renderer: liveDomRenderer() }); }); effect(() => { const data = this.data(); if (this.chart && data !== this.drawn) { this.drawn = data; this.chart.update({ data }); } }); inject(DestroyRef).onDestroy(() => this.chart?.destroy()); } } ``` With Zone.js, create the chart in `NgZone.runOutsideAngular()`, so its resize observer doesn't run change detection. ### Svelte `{@html}` writes the markup again when its value changes, and an empty value in the browser is worse: Svelte throws a `hydration_mismatch` and mounts the whole app again. So the browser gets a constant placeholder that isn't empty, and Svelte keeps the server's markup. Create the chart in an attachment that runs once: ```svelte
{@html markup}
``` `$state.snapshot()` reads the data deeply, so changes inside it count, and hands Chartist plain objects instead of Svelte's proxies. ### What goes into updates `update()` shallow-merges config changes into the chart, and a preset's `setOptions()` changes its options. Pass the data and options a parent component gives you. Leave out what's set when the chart is created: - `plugins` and `on`: they stay installed for the chart's life; - `renderer`: the chart keeps the one it was created with. Updates from a memoized or computed value keep the chart from redrawing when nothing changed. Give Chartist plain objects: `toRaw()` in Vue, `$state.snapshot()` in Svelte. > **Same data on both sides.** The browser builds the scene from its own config, > not from the markup. Log `hydration.repaired` in development, as in [the > hydrate render](https://chartist.dev/docs/ssr-hydration/#the-hydrate-render), to catch a server and a > browser that draw different data. ### Community wrappers If you'd rather use a component than the recipes above, these wrappers come from the community. Thank you to everyone who built and kept them going! {WRAPPERS.map(wrapper => ( ))}
Stack Chartist Wrapper
{wrapper.stack} {wrapper.chartist} {wrapper.pkg} {' '} ({wrapper.registry}): {wrapper.note}
Wrappers built for 1.x use the chart classes, which v2 keeps. Until a wrapper allows `chartist@2` in its peer dependencies, npm stops the install with `ERESOLVE`. Tell npm that the wrapper may use your Chartist: ```json { "overrides": { "react-chartist": { "chartist": "$chartist" } } } ``` If you listen to 1.x events like `draw`, add `compat` to the chart's options, as [Coming from 1.x](https://chartist.dev/docs/coming-from-1x/#events-need-the-compat-plugin) shows. Wrappers built for 0.x use the 0.x API, which went away in 1.0, so they need Chartist 0.x. Next: [Security and CSP](https://chartist.dev/docs/security-and-csp/), for pages with a strict policy. ## Security and CSP > Run Chartist on pages with a strict Content Security Policy or Trusted Types, and keep the data in your charts from injecting markup. Charts often show what people typed: names, product titles, comments. Chartist keeps that text as text, and it runs on pages that lock down what scripts may do. This page covers what your policy needs to allow and what is up to you. ### The policy Chartist needs Nothing beyond your own origin. A policy like this one works: ```html ``` Chartist sets the few inline styles it needs (the chart's size, the boxes of HTML labels, a donut's width) through `element.style`, which a policy allows. It never sets a `style` attribute from a script, which `style-src` without `'unsafe-inline'` blocks. It also evaluates no strings as code and adds no style sheets, so `script-src` needs no `'unsafe-eval'` either. Loading Chartist from a CDN? Add the CDN to `script-src` and `style-src`, for example `https://cdn.jsdelivr.net`. The [terminal package](https://chartist.dev/packages/terminal/)'s stylesheet brings its own font as a `data:` URL, so a page that shows text art also needs `font-src data:`. Without it, the browser falls back to the reader's fonts, and the braille dots and grid lines may show gaps. > **Inline styles in your own markup** are blocked too. The examples on this > site size their containers with `style="height: 50vh"` for brevity; under a > strict policy, move such sizes into your stylesheet. ### Server-rendered markup The two server outputs differ here. The self-contained SVG image from `renderToString()` carries `style` attributes. In a page, a strict style policy blocks every one of them and reports it as a violation, and the chart loses what they set, such as a donut's width. For a page with such a policy, render the chart with `renderForHydration()`. Its markup has no `style` attributes, looks the same with Chartist's stylesheet, and once the chart [takes it over](https://chartist.dev/docs/ssr-hydration/), the chart sets its styles through `element.style` like every chart. ### Your data stays data Labels, series names, class names, meta and what your label functions return: Chartist writes all of it as text or as escaped attribute values, in the page, in `renderToString()` output and in the terminal's text art. A label that contains markup shows its characters: **Markup in labels** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { barChart } from 'chartist'; // Product names as people typed them into a form, markup and all. const products = [ 'Tea & biscuits', 'Bold coffee', '' ]; // Chartist writes them as text: the labels show these characters, and // nothing runs. barChart('#chart', { labels: products, series: [[42, 31, 18]] }); ``` `index.html`: ```html
``` ### Without innerHTML Pages that require [Trusted Types](https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API) (`require-trusted-types-for 'script'`) can't assign strings to `innerHTML`. Chartist's DOM renderer creates elements instead, so `chart()` and the presets need nothing. The same goes for `liveDomRenderer()` from `chartist/live`: it creates and changes elements the way the DOM renderer does, `style` through `element.style` included, and its update animations are SMIL elements, not style sheets. Two outputs are strings on purpose. SVG markup from `renderToString()` is for servers and files (see [Server rendering](https://chartist.dev/docs/server-rendering/)). In the browser, draw the same scene with `domRenderer()`: ```ts import { domRenderer, lineChartConfig, toScene } from 'chartist'; const scene = toScene(lineChartConfig({ series: [[1, 3, 2]] }), { width: 400, height: 200 }); const container = document.querySelector('#chart'); if (container) { domRenderer().render(scene, container); } ``` And text art from the terminal package comes as an element with `format: 'element'`: ```ts import { lineChart } from '@chartist-js/terminal'; document .querySelector('#chart') ?.replaceChildren(lineChart({ series: [[1, 3, 2]] }, { format: 'element' })); ``` ### What's up to you - **1.x `Svg#foreignObject()`** takes a string as HTML, as it did in 1.x. Give it text you trust, or a DOM node (under Trusted Types, only a node works). - **Plugins and listeners** are your page's code. What they add to a chart, such as an `onclick` attribute or a link, is theirs, and they follow your policy like any other script. - **Strings you insert yourself:** if you put `renderToString()` output into a page with `innerHTML`, it's escaped, but Trusted Types still ask for a policy of your own. Next: render charts on a server in [Server rendering](https://chartist.dev/docs/server-rendering/), or write plugins in [Events and plugins](https://chartist.dev/docs/events-and-plugins/). ## Charts (Chartist API) > lineChart, barChart and pieChart: a chart from data and options, with the 1.x options and output. ### lineChart ```ts function lineChart(target: string | Element, data: LineChartData, options?: LineChartPresetOptions, responsiveOptions?: PresetResponsiveOptions): PresetHandle ``` Creates a line chart with the 1.x options and output, rendered synchronously. Plugins must be v2 plugins; convert 1.x plugins with `fromLegacyPlugin` from `chartist/compat`. Parameters: - `target: string | Element` - `data: LineChartData` - `options?: LineChartPresetOptions` - `responsiveOptions?: PresetResponsiveOptions` ### barChart ```ts function barChart(target: string | Element, data: BarChartData, options?: BarChartPresetOptions, responsiveOptions?: PresetResponsiveOptions): PresetHandle ``` Creates a bar chart with the 1.x options and output, rendered synchronously. Plugins must be v2 plugins; convert 1.x plugins with `fromLegacyPlugin` from `chartist/compat`. Parameters: - `target: string | Element` - `data: BarChartData` - `options?: BarChartPresetOptions` - `responsiveOptions?: PresetResponsiveOptions` ### pieChart ```ts function pieChart(target: string | Element, data: PieChartData, options?: PieChartPresetOptions, responsiveOptions?: PresetResponsiveOptions): PresetHandle ``` Creates a pie or donut chart with the 1.x options and output, rendered synchronously. Plugins must be v2 plugins; convert 1.x plugins with `fromLegacyPlugin` from `chartist/compat`. Parameters: - `target: string | Element` - `data: PieChartData` - `options?: PieChartPresetOptions` - `responsiveOptions?: PresetResponsiveOptions` ### LineChartData ```ts type LineChartData = Data<(Series | SeriesObject)[]> ``` The data of a line chart: series of values, and labels for the X axis. Properties: - `labels?: Label[]` One label per value position: the steps of the X axis in line and bar charts, the slices in pie charts. - `series: T` The values: an array per series (or a series object with a name, class name and meta), or one flat array for pies and distributed bars. ### LineChartPresetOptions ```ts type LineChartPresetOptions = Omit & { … } ``` Options of `lineChart`: the 1.x `LineChart` options; axis types may also be v2 axis factories. Properties: - `classNames?: { … }` The class names of the chart's elements. Change them to style the chart with your own CSS instead of Chartist's. - `classNames.chart?: string` The chart's SVG element. Default: `'ct-chart-line'`. - `classNames.label?: string` Each axis label. Default: `'ct-label'`. - `classNames.labelGroup?: string` The group of an axis's labels. Default: `'ct-labels'`. - `classNames.series?: string` The group of each series. Default: `'ct-series'`. - `classNames.line?: string` Each line. Default: `'ct-line'`. - `classNames.point?: string` Each point. Default: `'ct-point'`. - `classNames.area?: string` Each area. Default: `'ct-area'`. - `classNames.grid?: string` Each grid line. Default: `'ct-grid'`. - `classNames.gridGroup?: string` The group of the grid lines. Default: `'ct-grids'`. - `classNames.gridBackground?: string` The grid background (with `showGridBackground`). Default: `'ct-grid-background'`. - `classNames.vertical?: string` Labels and grid lines of the vertical axis. Default: `'ct-vertical'`. - `classNames.horizontal?: string` Labels and grid lines of the horizontal axis. Default: `'ct-horizontal'`. - `classNames.start?: string` Labels at the start of their axis (left or top). Default: `'ct-start'`. - `classNames.end?: string` Labels at the end of their axis (right or bottom). Default: `'ct-end'`. - `showLine?: boolean` Draws the lines. Default: `true`. - `showPoint?: boolean` Draws a point at each value. Default: `true`. - `showArea?: boolean` Fills the area between each line and `areaBase`. Default: `false`. - `areaBase?: number` The value the areas close at, usually 0. Default: `0`. - `lineSmooth?: boolean | LineInterpolation` How a line runs between its points: `true` smooths it with `Interpolation.monotoneCubic`, `false` draws straight segments (`Interpolation.none`), or pass an interpolation such as `Interpolation.cardinal()`, `Interpolation.step()` or your own. Default: `true`. - `showGridBackground?: boolean` Fills the grid area with a background (class `ct-grid-background`). Default: `false`. - `fullWidth?: boolean` Stretches the X axis so the last value lands on the right edge, without a last grid line. The last label may need chart padding to fit. Default: `false`. - `reverseData?: boolean` Reverses the labels, the order of the series and each series' values. Default: `false`. - `width?: string | number` The chart's width: px as a number, or a CSS length such as `'100%'`. Without it, the chart takes the width of its container. - `height?: string | number` The chart's height: px as a number, or a CSS length such as `'300px'`. Without it, the chart takes the height of its container. - `low?: number` The lowest value the value axis shows, instead of the data's lowest. Values below it are drawn outside the drawing area. - `high?: number` The highest value the value axis shows, instead of the data's highest. Values above it are drawn outside the drawing area. - `referenceValue?: number` A value the value axis always includes, such as 0, so it shows even when all values are above or below it. Bar charts use 0. - `chartPadding?: number | Partial` Space in px between the chart's edges and its drawing area (labels sit in it): one number for every side, or an object with some sides. Default: `{ top: 15, right: 15, bottom: 5, left: 10 }`. - `viewBox?: ViewBox` A `viewBox` for the SVG, so the drawing scales with its box instead of being drawn for its size. Text scales too: adjust it with responsive options. - `viewBox.width: number` The viewBox width, in SVG units. - `viewBox.height: number` The viewBox height, in SVG units. - `plugins?: readonly PluginEntry[]` v2 plugins; convert 1.x plugins with `fromLegacyPlugin`. - `on?: PhaseListeners` Phase listeners by phase name (or `'*'` for all phases), as `on` in the config of `chart()`. They're registered before the first render, so they see it too. - `on.*?: AllPhasesListener` Listens to every phase, with the phase name as first argument. - `renderer?: Renderer` Turns the scene into output, as `renderer` in the config of `chart()`: the DOM renderer by default, or `liveDomRenderer()` from `chartist/live` for a chart that changes in place on updates. It can't change after the chart is created. - `renderer.render: (scene: SceneNode, target: Element, update?: RenderUpdate) => TOutput` Renders a scene into the chart's container, after the `scene` phase and on every update and resize. From the second render on, it gets the previous render as `update`: a renderer that changes its previous output in place returns `patched: true` and its `changes`; one that ignores `update` replaces its output, as the DOM renderer does. - `renderer.measureText?: MeasureText` Measures text for layout, in the container so its CSS applies; without it, Chartist estimates text sizes. - `renderer.measureStyle?: MeasureStyle` Reads computed styles for layout, in the container so its CSS applies; without it, layout uses its defaults (a donut's ring is laid out for 60 px). - `renderer.labels?: LabelMode` The default label mode of this renderer; `html` if omitted. - `renderer.adopt?: (container: Element, replay: (layout: { readonly width: number; readonly height: number; readonly labels: LabelMode }) => SceneNode) => TOutput & { readonly repaired: number } | undefined` Takes over markup from `renderForHydration()` in the chart's container, instead of rendering: finds it and reads the layout its marker records, gets the scene for that layout from `replay`, binds the scene's nodes to the markup's elements and makes them what `render()` would draw. A chart calls it once, before its first render, unless it always draws a new svg; later renders go through `render()`, with the adopted output as the previous one. Returns nothing when there's no markup to take over or it doesn't match the scene, and the chart then renders as usual. `liveDomRenderer()` from `chartist/live` has it; the DOM renderer doesn't, since it replaces its svg on every render. - `layers?: PresetLayers` Extra layers below or above the chart's lines. - `layers.below?: readonly Layer[]` Layers drawn above the grid and below the chart's series. - `layers.above?: readonly Layer[]` Layers drawn above the chart's series and below the axis labels. - `series?: Readonly>` Options per series name: only these five apply per series. - `axisX?: PresetAxisOptions & Omit, 'type'>` Options of the X axis; its `type` may also be a v2 axis factory. - `axisX.low?: number` The lowest value this axis shows, instead of the data's lowest. - `axisX.high?: number` The highest value this axis shows, instead of the data's highest. - `axisX.referenceValue?: number` A value this axis always includes, such as 0. - `axisX.offset?: number` Room in px for the axis labels, between the drawing area and the chart's edge. Default: `30`. - `axisX.position?: 'end' | 'start'` Where the labels go: `start` (left or top) or `end` (right or bottom). Default: `'end'`. - `axisX.labelOffset?: { … }` Moves the labels by `x` and `y` px from where they'd go. Default: `{ x: 0, y: 0 }`. - `axisX.showLabel?: boolean` Draws the axis labels. Default: `true`. - `axisX.labelAlign?: Partial` Aligns this axis's SVG text labels in their box, over the `align` of [`axisLabels`](https://chartist.dev/api/layers/#axisLabels). HTML labels ignore it (align them with CSS). - `axisX.showGrid?: boolean` Draws the grid lines of the axis. Default: `true`. - `axisX.labelTransform?: (value: Label, index: number) => Label | null | undefined` Turns each label (and its index) into the text to show. Return `null` or `undefined` to leave a label out. Wins over `labelInterpolationFnc` when both are set. - `axisX.labelInterpolationFnc?: (value: Label, index: number) => Label | null | undefined` The 1.x name of [`AxisOptions.labelTransform`](https://chartist.dev/api/classes/#AxisOptions.labelTransform), which wins when both are set. - `axisX.scaleMinSpace?: number` The minimum space in px between two grid lines of a scale: the scale takes fewer, larger steps to keep it. - `axisX.onlyInteger?: boolean` Only whole numbers on the scale. - `axisX.ticks?: Label[]` The ticks: the labels of a step axis, or the values of a fixed scale axis. - `axisX.stretch?: boolean` Step axis: spreads the ticks over the whole axis, so the last one lands on its end (line charts with `fullWidth` set it). - `axisX.divisor?: number` Fixed scale axis without `ticks`: the number of equal steps between its low and high, with a tick at each end. - `axisX.highLow?: { … }` The value range of a scale as `{ high, low }`, instead of the one computed from the data. - `axisX.type?: AxisSetup['type']` A v2 axis factory or a 1.x axis class. - `axisY?: PresetAxisOptions & Omit, 'type'>` Options of the Y axis; its `type` may also be a v2 axis factory. - `axisY.low?: number` The lowest value this axis shows, instead of the data's lowest. - `axisY.high?: number` The highest value this axis shows, instead of the data's highest. - `axisY.referenceValue?: number` A value this axis always includes, such as 0. - `axisY.offset?: number` Room in px for the axis labels, between the drawing area and the chart's edge. Default: `40`. - `axisY.position?: 'end' | 'start'` Where the labels go: `start` (left or top) or `end` (right or bottom). Default: `'start'`. - `axisY.labelOffset?: { … }` Moves the labels by `x` and `y` px from where they'd go. Default: `{ x: 0, y: 0 }`. - `axisY.showLabel?: boolean` Draws the axis labels. Default: `true`. - `axisY.labelAlign?: Partial` Aligns this axis's SVG text labels in their box, over the `align` of [`axisLabels`](https://chartist.dev/api/layers/#axisLabels). HTML labels ignore it (align them with CSS). - `axisY.showGrid?: boolean` Draws the grid lines of the axis. Default: `true`. - `axisY.labelTransform?: (value: Label, index: number) => Label | null | undefined` Turns each label (and its index) into the text to show. Return `null` or `undefined` to leave a label out. Wins over `labelInterpolationFnc` when both are set. - `axisY.labelInterpolationFnc?: (value: Label, index: number) => Label | null | undefined` The 1.x name of [`AxisOptions.labelTransform`](https://chartist.dev/api/classes/#AxisOptions.labelTransform), which wins when both are set. - `axisY.scaleMinSpace?: number` The minimum space in px between two grid lines of a scale: the scale takes fewer, larger steps to keep it. Default: `20`. - `axisY.onlyInteger?: boolean` Only whole numbers on the scale. Default: `false`. - `axisY.ticks?: Label[]` The ticks: the labels of a step axis, or the values of a fixed scale axis. - `axisY.stretch?: boolean` Step axis: spreads the ticks over the whole axis, so the last one lands on its end (line charts with `fullWidth` set it). - `axisY.divisor?: number` Fixed scale axis without `ticks`: the number of equal steps between its low and high, with a tick at each end. - `axisY.highLow?: { … }` The value range of a scale as `{ high, low }`, instead of the one computed from the data. - `axisY.type?: AxisSetup['type']` A v2 axis factory or a 1.x axis class. - `labels?: LabelMode` HTML (1.x) or SVG text labels; default: the renderer's mode. ### BarChartData ```ts type BarChartData = Data ``` The data of a bar chart: series of values, and labels for the bar groups. Properties: - `labels?: Label[]` One label per value position: the steps of the X axis in line and bar charts, the slices in pie charts. - `series: T` The values: an array per series (or a series object with a name, class name and meta), or one flat array for pies and distributed bars. ### BarChartPresetOptions ```ts type BarChartPresetOptions = Omit & { … } ``` Options of `barChart`: the 1.x `BarChart` options; axis types may also be v2 axis factories. Properties: - `classNames?: { … }` The class names of the chart's elements. Change them to style the chart with your own CSS instead of Chartist's. - `classNames.chart?: string` The chart's SVG element. Default: `'ct-chart-bar'`. - `classNames.horizontalBars?: string` The chart's SVG element, with `horizontalBars`. Default: `'ct-horizontal-bars'`. - `classNames.label?: string` Each axis label. Default: `'ct-label'`. - `classNames.labelGroup?: string` The group of an axis's labels. Default: `'ct-labels'`. - `classNames.series?: string` The group of each series. Default: `'ct-series'`. - `classNames.bar?: string` Each bar. Default: `'ct-bar'`. - `classNames.grid?: string` Each grid line. Default: `'ct-grid'`. - `classNames.gridGroup?: string` The group of the grid lines. Default: `'ct-grids'`. - `classNames.gridBackground?: string` The grid background (with `showGridBackground`). Default: `'ct-grid-background'`. - `classNames.vertical?: string` Labels and grid lines of the vertical axis. Default: `'ct-vertical'`. - `classNames.horizontal?: string` Labels and grid lines of the horizontal axis. Default: `'ct-horizontal'`. - `classNames.start?: string` Labels at the start of their axis (left or top). Default: `'ct-start'`. - `classNames.end?: string` Labels at the end of their axis (right or bottom). Default: `'ct-end'`. - `seriesBarDistance?: number` The distance in px between the bars of a group (one bar per series). Default: `15`. - `stackBars?: boolean` Stacks the bars of a group on each other; `stackMode` says how. Default: `false`. - `horizontalBars?: boolean` Draws horizontal bars. The axes swap: the Y axis shows the labels and the X axis the values, so swap their options too. Default: `false`. - `distributeSeries?: boolean` Treats one flat array of values as one series per value, each in its own color: a profile rather than values over time. Default: `false`. - `reverseData?: boolean` Reverses the labels, the order of the series and each series' values. Default: `false`. - `showGridBackground?: boolean` Fills the grid area with a background (class `ct-grid-background`). Default: `false`. - `width?: string | number` The chart's width: px as a number, or a CSS length such as `'100%'`. Without it, the chart takes the width of its container. - `height?: string | number` The chart's height: px as a number, or a CSS length such as `'300px'`. Without it, the chart takes the height of its container. - `low?: number` The lowest value the value axis shows, instead of the data's lowest. Values below it are drawn outside the drawing area. - `high?: number` The highest value the value axis shows, instead of the data's highest. Values above it are drawn outside the drawing area. - `referenceValue?: number` A value the value axis always includes, such as 0, so it shows even when all values are above or below it. Bar charts use 0. Default: `0`. - `chartPadding?: number | Partial` Space in px between the chart's edges and its drawing area (labels sit in it): one number for every side, or an object with some sides. Default: `{ top: 15, right: 15, bottom: 5, left: 10 }`. - `viewBox?: ViewBox` A `viewBox` for the SVG, so the drawing scales with its box instead of being drawn for its size. Text scales too: adjust it with responsive options. - `viewBox.width: number` The viewBox width, in SVG units. - `viewBox.height: number` The viewBox height, in SVG units. - `plugins?: readonly PluginEntry[]` v2 plugins; convert 1.x plugins with `fromLegacyPlugin`. - `on?: PhaseListeners` Phase listeners by phase name (or `'*'` for all phases), as `on` in the config of `chart()`. They're registered before the first render, so they see it too. - `on.*?: AllPhasesListener` Listens to every phase, with the phase name as first argument. - `renderer?: Renderer` Turns the scene into output, as `renderer` in the config of `chart()`: the DOM renderer by default, or `liveDomRenderer()` from `chartist/live` for a chart that changes in place on updates. It can't change after the chart is created. - `renderer.render: (scene: SceneNode, target: Element, update?: RenderUpdate) => TOutput` Renders a scene into the chart's container, after the `scene` phase and on every update and resize. From the second render on, it gets the previous render as `update`: a renderer that changes its previous output in place returns `patched: true` and its `changes`; one that ignores `update` replaces its output, as the DOM renderer does. - `renderer.measureText?: MeasureText` Measures text for layout, in the container so its CSS applies; without it, Chartist estimates text sizes. - `renderer.measureStyle?: MeasureStyle` Reads computed styles for layout, in the container so its CSS applies; without it, layout uses its defaults (a donut's ring is laid out for 60 px). - `renderer.labels?: LabelMode` The default label mode of this renderer; `html` if omitted. - `renderer.adopt?: (container: Element, replay: (layout: { readonly width: number; readonly height: number; readonly labels: LabelMode }) => SceneNode) => TOutput & { readonly repaired: number } | undefined` Takes over markup from `renderForHydration()` in the chart's container, instead of rendering: finds it and reads the layout its marker records, gets the scene for that layout from `replay`, binds the scene's nodes to the markup's elements and makes them what `render()` would draw. A chart calls it once, before its first render, unless it always draws a new svg; later renders go through `render()`, with the adopted output as the previous one. Returns nothing when there's no markup to take over or it doesn't match the scene, and the chart then renders as usual. `liveDomRenderer()` from `chartist/live` has it; the DOM renderer doesn't, since it replaces its svg on every render. - `layers?: PresetLayers` Extra layers below or above the chart's bars. - `layers.below?: readonly Layer[]` Layers drawn above the grid and below the chart's series. - `layers.above?: readonly Layer[]` Layers drawn above the chart's series and below the axis labels. - `stackMode?: 'accumulate' | 'accumulate-relative' | 'overlap' | boolean` How stacked bars stack: `accumulate` (default), `accumulate-relative` (positives up, negatives down), or `overlap` (also `true`): every bar from zero. Default: `'accumulate'`. - `axisX?: PresetAxisOptions & Omit, 'type'>` Options of the X axis; its `type` may also be a v2 axis factory. - `axisX.low?: number` The lowest value this axis shows, instead of the data's lowest. - `axisX.high?: number` The highest value this axis shows, instead of the data's highest. - `axisX.referenceValue?: number` A value this axis always includes, such as 0. - `axisX.offset?: number` Room in px for the axis labels, between the drawing area and the chart's edge. Default: `30`. - `axisX.position?: 'end' | 'start'` Where the labels go: `start` (left or top) or `end` (right or bottom). Default: `'end'`. - `axisX.labelOffset?: { … }` Moves the labels by `x` and `y` px from where they'd go. Default: `{ x: 0, y: 0 }`. - `axisX.showLabel?: boolean` Draws the axis labels. Default: `true`. - `axisX.labelAlign?: Partial` Aligns this axis's SVG text labels in their box, over the `align` of [`axisLabels`](https://chartist.dev/api/layers/#axisLabels). HTML labels ignore it (align them with CSS). - `axisX.showGrid?: boolean` Draws the grid lines of the axis. Default: `true`. - `axisX.labelTransform?: (value: Label, index: number) => Label | null | undefined` Turns each label (and its index) into the text to show. Return `null` or `undefined` to leave a label out. Wins over `labelInterpolationFnc` when both are set. - `axisX.labelInterpolationFnc?: (value: Label, index: number) => Label | null | undefined` The 1.x name of [`AxisOptions.labelTransform`](https://chartist.dev/api/classes/#AxisOptions.labelTransform), which wins when both are set. - `axisX.scaleMinSpace?: number` The minimum space in px between two grid lines of a scale: the scale takes fewer, larger steps to keep it. Default: `30`. - `axisX.onlyInteger?: boolean` Only whole numbers on the scale. Default: `false`. - `axisX.ticks?: Label[]` The ticks: the labels of a step axis, or the values of a fixed scale axis. - `axisX.stretch?: boolean` Step axis: spreads the ticks over the whole axis, so the last one lands on its end (line charts with `fullWidth` set it). - `axisX.divisor?: number` Fixed scale axis without `ticks`: the number of equal steps between its low and high, with a tick at each end. - `axisX.highLow?: { … }` The value range of a scale as `{ high, low }`, instead of the one computed from the data. - `axisX.type?: AxisSetup['type']` A v2 axis factory or a 1.x axis class. - `axisY?: PresetAxisOptions & Omit, 'type'>` Options of the Y axis; its `type` may also be a v2 axis factory. - `axisY.low?: number` The lowest value this axis shows, instead of the data's lowest. - `axisY.high?: number` The highest value this axis shows, instead of the data's highest. - `axisY.referenceValue?: number` A value this axis always includes, such as 0. - `axisY.offset?: number` Room in px for the axis labels, between the drawing area and the chart's edge. Default: `40`. - `axisY.position?: 'end' | 'start'` Where the labels go: `start` (left or top) or `end` (right or bottom). Default: `'start'`. - `axisY.labelOffset?: { … }` Moves the labels by `x` and `y` px from where they'd go. Default: `{ x: 0, y: 0 }`. - `axisY.showLabel?: boolean` Draws the axis labels. Default: `true`. - `axisY.labelAlign?: Partial` Aligns this axis's SVG text labels in their box, over the `align` of [`axisLabels`](https://chartist.dev/api/layers/#axisLabels). HTML labels ignore it (align them with CSS). - `axisY.showGrid?: boolean` Draws the grid lines of the axis. Default: `true`. - `axisY.labelTransform?: (value: Label, index: number) => Label | null | undefined` Turns each label (and its index) into the text to show. Return `null` or `undefined` to leave a label out. Wins over `labelInterpolationFnc` when both are set. - `axisY.labelInterpolationFnc?: (value: Label, index: number) => Label | null | undefined` The 1.x name of [`AxisOptions.labelTransform`](https://chartist.dev/api/classes/#AxisOptions.labelTransform), which wins when both are set. - `axisY.scaleMinSpace?: number` The minimum space in px between two grid lines of a scale: the scale takes fewer, larger steps to keep it. Default: `20`. - `axisY.onlyInteger?: boolean` Only whole numbers on the scale. Default: `false`. - `axisY.ticks?: Label[]` The ticks: the labels of a step axis, or the values of a fixed scale axis. - `axisY.stretch?: boolean` Step axis: spreads the ticks over the whole axis, so the last one lands on its end (line charts with `fullWidth` set it). - `axisY.divisor?: number` Fixed scale axis without `ticks`: the number of equal steps between its low and high, with a tick at each end. - `axisY.highLow?: { … }` The value range of a scale as `{ high, low }`, instead of the one computed from the data. - `axisY.type?: AxisSetup['type']` A v2 axis factory or a 1.x axis class. - `labels?: LabelMode` HTML (1.x) or SVG text labels; default: the renderer's mode. ### PieChartData ```ts type PieChartData = Data ``` The data of a pie chart: one value per slice, and labels for the slices. Properties: - `labels?: Label[]` One label per value position: the steps of the X axis in line and bar charts, the slices in pie charts. - `series: T` The values: an array per series (or a series object with a name, class name and meta), or one flat array for pies and distributed bars. ### PieChartPresetOptions ```ts type PieChartPresetOptions = Omit & { … } ``` Options of `pieChart`: the 1.x `PieChart` options, with v2 plugins and phase listeners. Properties: - `classNames?: { … }` The class names of the chart's elements. Change them to style the chart with your own CSS instead of Chartist's. - `classNames.chartPie?: string` The chart's SVG element, for pies. Default: `'ct-chart-pie'`. - `classNames.chartDonut?: string` The chart's SVG element, for donuts. Default: `'ct-chart-donut'`. - `classNames.series?: string` The group of each series. Default: `'ct-series'`. - `classNames.slicePie?: string` Each pie slice. Default: `'ct-slice-pie'`. - `classNames.sliceDonut?: string` Each donut slice. Default: `'ct-slice-donut'`. - `classNames.label?: string` Each axis label. Default: `'ct-label'`. - `startAngle?: number` Where the first slice starts, in degrees clockwise from the top. Default: `0`. - `total?: number` The value a full circle stands for. With a total above the sum of the values, the pie stays open, as for a gauge. - `donut?: boolean` Draws a donut: rings instead of filled slices. Default: `false`. - `donutWidth?: string | number` The width of a donut's ring: px as a number, or a share of the radius as a string (`'30%'`). Without it, the stylesheet sets the width (`--ct-donut-width`, 60px by default), and in the browser the ring is laid out for the width the stylesheet gives a slice. Without a DOM (`renderToString()`), it's laid out for 60 px. - `showLabel?: boolean` Draws a label for each slice. Default: `true`. - `labelOffset?: number` Moves the labels away from the center (positive) or toward it (negative), in px. Default: `0`. - `labelPosition?: RadialLabelPosition` Where labels go: `inside` (halfway out on a pie, on the middle of a donut's ring, plus `labelOffset`), `outside` (at the outer edge) or `center` (in the middle; move them with `labelOffset`). Default: `'inside'`. - `labelTransform?: (value: Label, index: number) => Label | null | undefined` Turns each slice's label (or value) and index into the text to show. Return `null` or `undefined` to leave a label out. Wins over `labelInterpolationFnc` when both are set. - `labelInterpolationFnc?: (value: Label, index: number) => Label | null | undefined` The 1.x name of `labelTransform`, which wins when both are set. - `labelDirection?: LabelDirection` Which way labels anchor: `neutral`, `explode` (away from the center, for labels outside the pie) or `implode` (toward it). Default: `'neutral'`. - `ignoreEmptyValues?: boolean` Leaves out the slices and labels of empty values. Default: `false`. - `preventOverlappingLabelOffset?: number` Moves labels apart so they don't overlap, using this offset in px. The overlap is judged by the length of the drawn label text. 0 leaves them where they are. Default: `0`. - `width?: string | number` The chart's width: px as a number, or a CSS length such as `'100%'`. Without it, the chart takes the width of its container. - `height?: string | number` The chart's height: px as a number, or a CSS length such as `'300px'`. Without it, the chart takes the height of its container. - `low?: number` The lowest value the value axis shows, instead of the data's lowest. Values below it are drawn outside the drawing area. - `high?: number` The highest value the value axis shows, instead of the data's highest. Values above it are drawn outside the drawing area. - `referenceValue?: number` A value the value axis always includes, such as 0, so it shows even when all values are above or below it. Bar charts use 0. - `chartPadding?: number | Partial` Space in px between the chart's edges and its drawing area (labels sit in it): one number for every side, or an object with some sides. Default: `{ top: 5, right: 5, bottom: 5, left: 5 }`. - `viewBox?: ViewBox` A `viewBox` for the SVG, so the drawing scales with its box instead of being drawn for its size. Text scales too: adjust it with responsive options. - `viewBox.width: number` The viewBox width, in SVG units. - `viewBox.height: number` The viewBox height, in SVG units. - `plugins?: readonly PluginEntry[]` v2 plugins; convert 1.x plugins with `fromLegacyPlugin`. - `on?: PhaseListeners` Phase listeners by phase name (or `'*'` for all phases), as `on` in the config of `chart()`. They're registered before the first render, so they see it too. - `on.*?: AllPhasesListener` Listens to every phase, with the phase name as first argument. - `renderer?: Renderer` Turns the scene into output, as `renderer` in the config of `chart()`: the DOM renderer by default, or `liveDomRenderer()` from `chartist/live` for a chart that changes in place on updates. It can't change after the chart is created. - `renderer.render: (scene: SceneNode, target: Element, update?: RenderUpdate) => TOutput` Renders a scene into the chart's container, after the `scene` phase and on every update and resize. From the second render on, it gets the previous render as `update`: a renderer that changes its previous output in place returns `patched: true` and its `changes`; one that ignores `update` replaces its output, as the DOM renderer does. - `renderer.measureText?: MeasureText` Measures text for layout, in the container so its CSS applies; without it, Chartist estimates text sizes. - `renderer.measureStyle?: MeasureStyle` Reads computed styles for layout, in the container so its CSS applies; without it, layout uses its defaults (a donut's ring is laid out for 60 px). - `renderer.labels?: LabelMode` The default label mode of this renderer; `html` if omitted. - `renderer.adopt?: (container: Element, replay: (layout: { readonly width: number; readonly height: number; readonly labels: LabelMode }) => SceneNode) => TOutput & { readonly repaired: number } | undefined` Takes over markup from `renderForHydration()` in the chart's container, instead of rendering: finds it and reads the layout its marker records, gets the scene for that layout from `replay`, binds the scene's nodes to the markup's elements and makes them what `render()` would draw. A chart calls it once, before its first render, unless it always draws a new svg; later renders go through `render()`, with the adopted output as the previous one. Returns nothing when there's no markup to take over or it doesn't match the scene, and the chart then renders as usual. `liveDomRenderer()` from `chartist/live` has it; the DOM renderer doesn't, since it replaces its svg on every render. - `layers?: PresetLayers` Extra layers below or above the pie's slices, in polar coordinates. - `layers.below?: readonly Layer[]` Layers drawn above the grid and below the chart's series. - `layers.above?: readonly Layer[]` Layers drawn above the chart's series and below the axis labels. ### barChartConfig ```ts function barChartConfig(data: BarChartData, options?: BarChartPresetOptions): ChartConfig ``` Builds the chart config that `barChart()` renders for the data and options, without a container: the bar layer with its axes, grid and labels, horizontal or stacked as the options say. Pass it to `toScene()` to render the preset chart with another renderer. Responsive options and the inline size style of the root svg are left out; both need a container. Parameters: - `data: BarChartData` - `options?: BarChartPresetOptions` ### lineChartConfig ```ts function lineChartConfig(data: LineChartData, options?: LineChartPresetOptions): ChartConfig ``` Builds the chart config that `lineChart()` renders for the data and options, without a container: the line layer with its axes, grid and labels. Pass it to `toScene()` to render the preset chart with another renderer. Responsive options and the inline size style of the root svg are left out; both need a container. Parameters: - `data: LineChartData` - `options?: LineChartPresetOptions` ```ts import { lineChartConfig } from 'chartist'; import { renderToString, toScene } from 'chartist/server'; const config = lineChartConfig( { labels: ['Mon', 'Tue', 'Wed'], series: [[5, 2, 4]] }, { showArea: true } ); export const svg = renderToString( toScene(config, { width: 600, height: 300 }) ); ``` ### pieChartConfig ```ts function pieChartConfig(data: PieChartData, options?: PieChartPresetOptions): ChartConfig ``` Builds the chart config that `pieChart()` renders for the data and options, without a container: the pie layer with its labels. Pass it to `toScene()` to render the preset chart with another renderer. Responsive options and the inline size style of the root svg are left out; both need a container. Parameters: - `data: PieChartData` - `options?: PieChartPresetOptions` ### BarPresetState ```ts type BarPresetState = PresetState ``` What the bar preset passes to plugins as `ResolvedConfig.preset`. Properties: - `kind: TKind` Which preset: `line`, `bar` or `pie`. - `options: TOptions` The current options: the given ones over the defaults, with the matching responsive options applied. - `data: TData` The current data. - `update: (data?: TData, options?: TOptions, override?: boolean) => void` Changes the data, the options or both and redraws, as 1.x `update()`: the options extend the current ones, or with `override`, the defaults. - `detach: () => void` Stops the chart's responsive updates, as 1.x `detach()`. ### LinePresetState ```ts type LinePresetState = PresetState ``` What the line preset passes to plugins as `ResolvedConfig.preset`. Properties: - `kind: TKind` Which preset: `line`, `bar` or `pie`. - `options: TOptions` The current options: the given ones over the defaults, with the matching responsive options applied. - `data: TData` The current data. - `update: (data?: TData, options?: TOptions, override?: boolean) => void` Changes the data, the options or both and redraws, as 1.x `update()`: the options extend the current ones, or with `override`, the defaults. - `detach: () => void` Stops the chart's responsive updates, as 1.x `detach()`. ### PiePresetState ```ts type PiePresetState = PresetState ``` What the pie preset passes to plugins as `ResolvedConfig.preset`. Properties: - `kind: TKind` Which preset: `line`, `bar` or `pie`. - `options: TOptions` The current options: the given ones over the defaults, with the matching responsive options applied. - `data: TData` The current data. - `update: (data?: TData, options?: TOptions, override?: boolean) => void` Changes the data, the options or both and redraws, as 1.x `update()`: the options extend the current ones, or with `override`, the defaults. - `detach: () => void` Stops the chart's responsive updates, as 1.x `detach()`. ### PresetState ```ts interface PresetState ``` What a preset passes to plugins as `ResolvedConfig.preset`: its kind, the current 1.x options and 1.x-style updates. Properties: - `kind: TKind` Which preset: `line`, `bar` or `pie`. - `options: TOptions` The current options: the given ones over the defaults, with the matching responsive options applied. - `data: TData` The current data. - `update: (data?: TData, options?: TOptions, override?: boolean) => void` Changes the data, the options or both and redraws, as 1.x `update()`: the options extend the current ones, or with `override`, the defaults. - `detach: () => void` Stops the chart's responsive updates, as 1.x `detach()`. ### PresetHandle ```ts interface PresetHandle extends ChartHandle ``` The handle `lineChart`, `barChart` and `pieChart` return: a [`ChartHandle`](https://chartist.dev/api/chart/#ChartHandle) that also changes the chart's options. `update({ data })` changes the preset's data too, so responsive changes redraw with it. Properties: - `container?: Element` The element the chart renders into (undefined in `toScene`). - `update: (config: Partial>) => this` Shallow-merges the changes into the config and renders again. Plugins and listeners stay; `plugins` and `on` can't change. - `detach: () => this` Stops reacting to container size changes (and media queries in presets). The output and the listeners stay. - `destroy: () => void` Detaches, removes the output and clears all listeners. - `emit: (name: K, payload: ChartEvents[K]) => void` Emits a custom event (declared in [`ChartEvents`](https://chartist.dev/api/events-and-plugins/#ChartEvents)), e.g. from a plugin. Phase names are reserved: emitting one throws. - `on: (name: K, listener: PhaseListener) => this` Listens to a phase event, which fires on every render. Listeners of `resolve` to `scene` may return a new payload to change what's drawn; `compose` listeners may return `null` to drop a node. - `off: (name: K, listener?: PhaseListener) => this` Removes a phase listener, whoever added it. Without a listener, removes the listeners of the phase added through `on` and the config's `on`; plugins keep theirs. - `setOptions: (options: Omit, how?: { readonly replace?: boolean }) => this` Changes the options and redraws: they extend the current options, or with `{ replace: true }`, the defaults, dropping the options given before. Matching responsive options still apply on top. `plugins`, `on` and `renderer` can't change. Coming from 1.x, `setOptions(options)` is `update(undefined, options, true)` and `setOptions(options, { replace: true })` is `update(undefined, options)`. ```ts import { lineChart } from 'chartist'; const chart = lineChart('#chart', { series: [[1, 3, 2, 5]] }); // Areas from now on; the other options stay. chart.setOptions({ showArea: true }); ``` ### PresetAxisOptions ```ts type PresetAxisOptions = Omit & { … } ``` 1.x axis options whose `type` may also be a v2 axis factory. Properties: - `low?: number` The lowest value this axis shows, instead of the data's lowest. - `high?: number` The highest value this axis shows, instead of the data's highest. - `referenceValue?: number` A value this axis always includes, such as 0. - `offset?: number` Room in px for the axis labels, between the drawing area and the chart's edge. - `position?: 'end' | 'start'` Where the labels go: `start` (left or top) or `end` (right or bottom). - `labelOffset?: { … }` Moves the labels by `x` and `y` px from where they'd go. - `showLabel?: boolean` Draws the axis labels. - `labelAlign?: Partial` Aligns this axis's SVG text labels in their box, over the `align` of [`axisLabels`](https://chartist.dev/api/layers/#axisLabels). HTML labels ignore it (align them with CSS). - `showGrid?: boolean` Draws the grid lines of the axis. - `labelTransform?: (value: Label, index: number) => Label | null | undefined` Turns each label (and its index) into the text to show. Return `null` or `undefined` to leave a label out. Wins over `labelInterpolationFnc` when both are set. - `labelInterpolationFnc?: (value: Label, index: number) => Label | null | undefined` The 1.x name of [`AxisOptions.labelTransform`](https://chartist.dev/api/classes/#AxisOptions.labelTransform), which wins when both are set. - `scaleMinSpace?: number` The minimum space in px between two grid lines of a scale: the scale takes fewer, larger steps to keep it. - `onlyInteger?: boolean` Only whole numbers on the scale. - `ticks?: Label[]` The ticks: the labels of a step axis, or the values of a fixed scale axis. - `stretch?: boolean` Step axis: spreads the ticks over the whole axis, so the last one lands on its end (line charts with `fullWidth` set it). - `divisor?: number` Fixed scale axis without `ticks`: the number of equal steps between its low and high, with a tick at each end. - `highLow?: { … }` The value range of a scale as `{ high, low }`, instead of the one computed from the data. - `type?: AxisSetup['type']` A v2 axis factory or a 1.x axis class. ### PresetLayers ```ts interface PresetLayers ``` Extra layers of a preset chart, such as `band()` or the target and trend lines of `@chartist-js/essentials`: they draw on the preset's axes and get all series. Properties: - `below?: readonly Layer[]` Layers drawn above the grid and below the chart's series. - `above?: readonly Layer[]` Layers drawn above the chart's series and below the axis labels. ```ts import { barChart, band } from 'chartist'; barChart( '#chart', { labels: ['Q1', 'Q2', 'Q3'], series: [[800, 1200, 950]] }, { layers: { below: [band({ low: 800, high: 1200 })] } } ); ``` ### PresetResponsiveOptions ```ts type PresetResponsiveOptions = readonly ResponsiveEntry[] ``` Responsive options of presets: the 1.x media query pairs, plus container conditions (`container()`). Matching entries extend the options in order. ## chart() (Chartist API) > The composable chart: data, axes and layers in one config, and the handle it returns. ### chart ```ts function chart(target: string | Element, config: ChartConfig): ChartHandle ``` Creates a chart from data, axes and layers in a container, and renders it right away. The presets (`lineChart`, `barChart`, `pieChart`) are built on it; use it for what they don't cover, like bars and a line on two Y axes. The chart fills its container unless the config sets a size, and renders again when the container resizes, web fonts finish loading or the matching `responsive` entries change. Listeners in `config.on` and plugins receive the events of the first render too. While the page is printed, the SVG gets a `viewBox` of its layout size (unless the config sets one), so the drawing scales into the box it gets in the print. Without `className`, the SVG gets the chart type class of its layers, so the stylesheet's chart type rules apply: `ct-chart-bar` if a layer draws bars, else `ct-chart-pie` or `ct-chart-donut` for a pie layer, else `ct-chart-line`. A `className` replaces it. Parameters: - `target: string | Element` The container element, or a CSS selector for it. - `config: ChartConfig` The data, axes, layers and the rest of the chart. Returns: A handle to update the chart, listen to it or remove it. ```ts import { autoScaleAxis, axisGrid, axisLabels, bar, cartesianAxes, chart, line, stepAxis } from 'chartist'; const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; chart('#chart', { data: { labels: months, series: [ { name: 'rain', data: [64, 58, 70, 82, 104, 120] }, { name: 'temperature', data: [1, 3, 8, 12, 16, 20] } ] }, axes: cartesianAxes([ { id: 'month', side: 'bottom', type: stepAxis(), ticks: months }, { id: 'mm', side: 'left', type: autoScaleAxis(), low: 0 }, { id: 'celsius', side: 'right', type: autoScaleAxis(), low: 0 } ]), layers: [ axisGrid({ axes: ['month', 'mm'] }), bar({ series: ['rain'], x: 'month', y: 'mm' }), line({ series: ['temperature'], y: 'celsius', align: 'center' }), axisLabels() ] }); ``` ### ChartConfig ```ts interface ChartConfig extends PipelineOptions ``` The config of [`chart`](https://chartist.dev/api/chart/#chart): the data, the layers that draw it and the axes they draw on, plus size, padding, plugins and listeners. Without `axes`, layers draw into the whole chart area; the pie layer needs `coordinates: 'polar'`. [`toScene`](https://chartist.dev/api/rendering/#toScene) takes the same config. Properties: - `plugins?: readonly PluginEntry[]` Plugins to install before the first render, in order: plugins, or plugins with their options from [`withOptions`](https://chartist.dev/api/events-and-plugins/#withOptions). - `on?: PhaseListeners` Phase listeners by phase name (or `'*'` for all phases). They're registered before the first render, so they see it too. - `renderer?: Renderer` Turns the scene into output; the DOM renderer by default. - `responsive?: ResponsiveSet>>` Overrides that apply while their condition matches, created with `responsive([...])`: entries of a media query string or a `container()` condition, and a partial config. The chart renders again when the matching entries change. - `data: ChartData` The labels and series to draw. - `layers: readonly Layer[]` What to draw, in this order (later layers on top), e.g. `[axisGrid(), line()]`. - `coordinates?: Coordinates` The coordinate system the layers draw in: `cartesian` (the default) for axes, `polar` for `pie()`. - `padding?: number | Partial` Space in px between the chart's edge and the drawing area: one number for all sides, or some sides (the others keep the 1.x defaults). - `axes?: CartesianAxes` Axes (see `cartesianAxes`); their offsets are reserved around the area. - `width?: string | number` The chart's width: px as a number, or a CSS length such as `100%` or `20rem`. Without it, `chart()` fills the container's width. - `height?: string | number` The chart's height: px as a number, or a CSS length such as `300px`. Without it, `chart()` fills the container's height. - `viewBox?: { … }` Fixed drawing coordinates; sets the layout size and the root `viewBox`. - `className?: string` Class names for the root svg, e.g. `ct-chart-line` for the 1.x line chart CSS. - `labels?: LabelMode` Draws labels as HTML in `foreignObject` (1.x) or as SVG `text`. The default depends on the renderer: `html` for the DOM, `svg` in `toScene()`. - `preset?: LinePresetState | BarPresetState | PiePresetState` The preset that created the chart, if any (read by plugins). ### ChartOverrides ```ts type ChartOverrides = Partial> ``` The part of a [`ChartConfig`](https://chartist.dev/api/chart/#ChartConfig) that responsive entries can override: everything but `responsive`, `plugins`, `on` and `renderer`. The matching entries merge over the config in order, shallowly. Properties: - `data?: ChartData` The labels and series to draw. - `layers?: readonly Layer[]` What to draw, in this order (later layers on top), e.g. `[axisGrid(), line()]`. - `coordinates?: Coordinates` The coordinate system the layers draw in: `cartesian` (the default) for axes, `polar` for `pie()`. - `padding?: number | Partial` Space in px between the chart's edge and the drawing area: one number for all sides, or some sides (the others keep the 1.x defaults). - `axes?: CartesianAxes` Axes (see `cartesianAxes`); their offsets are reserved around the area. - `width?: string | number` The chart's width: px as a number, or a CSS length such as `100%` or `20rem`. Without it, `chart()` fills the container's width. - `height?: string | number` The chart's height: px as a number, or a CSS length such as `300px`. Without it, `chart()` fills the container's height. - `viewBox?: { … }` Fixed drawing coordinates; sets the layout size and the root `viewBox`. - `className?: string` Class names for the root svg, e.g. `ct-chart-line` for the 1.x line chart CSS. - `labels?: LabelMode` Draws labels as HTML in `foreignObject` (1.x) or as SVG `text`. The default depends on the renderer: `html` for the DOM, `svg` in `toScene()`. - `preset?: LinePresetState | BarPresetState | PiePresetState` The preset that created the chart, if any (read by plugins). ### ChartHandle ```ts interface ChartHandle ``` The handle [`chart`](https://chartist.dev/api/chart/#chart) returns: update or remove the chart, and listen to its events. Plugins get the same handle in `install`. The methods other than `destroy` and `emit` return the handle, so calls chain. Properties: - `container?: Element` The element the chart renders into (undefined in `toScene`). - `update: (config: Partial>) => this` Shallow-merges the changes into the config and renders again. Plugins and listeners stay; `plugins` and `on` can't change. - `detach: () => this` Stops reacting to container size changes (and media queries in presets). The output and the listeners stay. - `destroy: () => void` Detaches, removes the output and clears all listeners. - `emit: (name: K, payload: ChartEvents[K]) => void` Emits a custom event (declared in [`ChartEvents`](https://chartist.dev/api/events-and-plugins/#ChartEvents)), e.g. from a plugin. Phase names are reserved: emitting one throws. - `on: (name: K, listener: PhaseListener) => this` Listens to a phase event, which fires on every render. Listeners of `resolve` to `scene` may return a new payload to change what's drawn; `compose` listeners may return `null` to drop a node. - `off: (name: K, listener?: PhaseListener) => this` Removes a phase listener, whoever added it. Without a listener, removes the listeners of the phase added through `on` and the config's `on`; plugins keep theirs. ## Layers (Chartist API) > What chart() draws: lines, bars, pies, grids and labels, each a layer over the same data. ### axisGrid ```ts function axisGrid(options?: AxisLayerOptions & { readonly background?: boolean }): Layer ``` Creates a layer that draws the grid lines of the named axes (default: all) in `g.ct-grids`, one `line.ct-grid` per tick. List it first, so the series draw over it. Parameters: - `options?: AxisLayerOptions & { readonly background?: boolean }` The axes to draw and their classes; `background` adds a `rect.ct-grid-background` over the chart area, behind the lines. ```ts import { autoScaleAxis, axisGrid, axisLabels, cartesianAxes, chart, line, stepAxis } from 'chartist'; const hours = ['6am', '9am', '12pm', '3pm', '6pm']; chart('#chart', { data: { labels: hours, series: [[12, 17, 23, 21, 15]] }, axes: cartesianAxes([ { id: 'time', side: 'bottom', type: stepAxis(), ticks: hours }, { id: 'celsius', side: 'left', type: autoScaleAxis() } ]), // Horizontal lines only, on a background rect. layers: [ axisGrid({ axes: ['celsius'], background: true }), line(), axisLabels() ] }); ``` ### axisLabels ```ts function axisLabels(options?: AxisLabelsOptions): Layer ``` Creates a layer that draws the labels of the named axes (default: all) in `g.ct-labels`, each on its axis's side. Axes on the same side stack outward, each in its own `offset`. List it last, so the labels draw over the series. Labels are HTML in a `foreignObject` (as in 1.x) or SVG `text`: the chart's `labels` option decides, unless the layer sets its own. Parameters: - `options?: AxisLabelsOptions` ```ts import { autoScaleAxis, axisGrid, axisLabels, cartesianAxes, chart, line, stepAxis } from 'chartist'; const weeks = ['W1', 'W2', 'W3', 'W4']; chart('#chart', { data: { labels: weeks, series: [[3, 5, 4, 8]] }, axes: cartesianAxes([ { id: 'week', side: 'bottom', type: stepAxis(), ticks: weeks }, { id: 'km', side: 'left', type: autoScaleAxis(), low: 0 } ]), layers: [ axisGrid(), line(), // SVG text instead of HTML, e.g. to export the SVG. axisLabels({ labels: 'svg' }) ] }); ``` ### band ```ts function band(options: BandOptions): Layer ``` Creates a layer that fills the area between a low and a high edge (`g.ct-band` with a `path.ct-band-area` per piece; the band breaks at holes). Each edge is a constant value, values of its own (one per label) or a series, or `series` names a group and the band covers their minimum to their maximum at each value index. Constants and own values widen the Y axis to include them. On a preset, add it to `layers.below` to draw it behind the series; in a composed chart, list it before the series layers. It needs an X and a Y axis, from [`cartesianAxes`](https://chartist.dev/api/scales/#cartesianAxes) or a preset's. Parameters: - `options: BandOptions` ```ts import { band, lineChart } from 'chartist'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [{ name: 'visits', data: [12, 9, 7, 8, 5] }] }, { low: 0, layers: { below: [band({ low: 6, high: 10, mark: true })] } } ); ``` ### bar ```ts function bar(options?: BarLayerOptions): Layer ``` Creates a layer that draws series as bars, in the markup of the 1.x bar chart (`g.ct-series` with a `line.ct-bar` per value). The label axis is X (Y with `horizontalBars`), the value axis the other one; both come from [`cartesianAxes`](https://chartist.dev/api/scales/#cartesianAxes). Bars grow from zero on the value axis, so keep zero on it: `low: 0`, or `referenceValue: 0`. On a step axis they sit in the middle of each step. Parameters: - `options?: BarLayerOptions` ```ts import { autoScaleAxis, axisGrid, axisLabels, bar, cartesianAxes, chart, stepAxis } from 'chartist'; const quarters = ['Q1', 'Q2', 'Q3', 'Q4']; chart('#chart', { data: { labels: quarters, series: [ [800, 1200, 1400, 1300], [200, 400, 500, 300] ] }, axes: cartesianAxes([ { id: 'quarter', side: 'bottom', type: stepAxis(), ticks: quarters }, { id: 'sales', side: 'left', type: autoScaleAxis(), low: 0 } ]), layers: [axisGrid(), bar({ seriesBarDistance: 12 }), axisLabels()], className: 'ct-chart-bar' }); ``` ### line ```ts function line(options?: LineLayerOptions): Layer ``` Creates a layer that draws series as lines, with points and optional areas, in the markup of the 1.x line chart (`g.ct-series` with `path.ct-line`, `line.ct-point` and `path.ct-area`). It needs an X and a Y axis, from [`cartesianAxes`](https://chartist.dev/api/scales/#cartesianAxes). Parameters: - `options?: LineLayerOptions` ```ts import { autoScaleAxis, axisGrid, axisLabels, cartesianAxes, chart, line, stepAxis } from 'chartist'; const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']; chart('#chart', { data: { labels: days, series: [ { name: 'visits', data: [12, 9, 7, 8, 5] }, { name: 'sales', data: [2, 1, 3.5, 7, 3] } ] }, axes: cartesianAxes([ { id: 'day', side: 'bottom', type: stepAxis(), ticks: days }, { id: 'count', side: 'left', type: autoScaleAxis(), low: 0 } ]), layers: [ axisGrid(), line({ showArea: true, overrides: { sales: { showArea: false } } }), axisLabels() ] }); ``` ### determineAnchorPosition ```ts function determineAnchorPosition(center: Dot, label: Dot, direction: LabelDirection): AnchorPosition ``` The 1.x label anchor: `start`, `middle` or `end` by side and direction. Parameters: - `center: Dot` - `label: Dot` - `direction: LabelDirection` ### pie ```ts function pie(options?: PieLayerOptions): Layer ``` Creates a layer that draws a flat series as pie or donut slices with labels, in the markup of the 1.x pie chart (a `g.ct-series` per slice, then a group of `text.ct-label`). It draws in polar coordinates, so the chart needs `coordinates: 'polar'` and no axes. With several series, it draws one slice per series, from each series' first value. Parameters: - `options?: PieLayerOptions` ```ts import { chart, pie } from 'chartist'; chart('#chart', { data: { labels: ['Rent', 'Food', 'Fun'], series: [50, 30, 20] }, coordinates: 'polar', layers: [pie({ donut: true, donutWidth: 30 })], className: 'ct-chart-donut' }); ``` ### AxisLayerClassNames ```ts interface AxisLayerClassNames extends AxisClassNames ``` The classes [`axisGrid`](https://chartist.dev/api/layers/#axisGrid) and [`axisLabels`](https://chartist.dev/api/layers/#axisLabels) give what they draw. The defaults are the 1.x classes, so 1.x CSS applies. Properties: - `gridGroup: string` The group of all grid lines (`ct-grids`). - `labelGroup: string` The group of all labels (`ct-labels`). - `gridBackground: string` The rect behind the grid, with `background: true` (`ct-grid-background`). - `grid: string` Each grid line (`ct-grid`). - `label: string` Each label (`ct-label`). - `horizontal: string` Grid lines and labels of an X axis (`ct-horizontal`). - `vertical: string` Grid lines and labels of a Y axis (`ct-vertical`). - `start: string` Labels of an axis at the top or left (`ct-start`). - `end: string` Labels of an axis at the bottom or right (`ct-end`). - `gridMinor?: string` Grid lines at an axis's `minorTicks`, next to the `grid` class (`ct-grid-minor`). ### AxisLayerOptions ```ts interface AxisLayerOptions ``` The options of [`axisGrid`](https://chartist.dev/api/layers/#axisGrid) and [`axisLabels`](https://chartist.dev/api/layers/#axisLabels). Properties: - `axes?: readonly string[]` Axis ids to draw; default: all axes, in declaration order. An unknown id throws. - `classNames?: Partial` Replaces some of the classes; the others keep their 1.x names. ### AxisLabelsOptions ```ts interface AxisLabelsOptions extends AxisLayerOptions ``` The options of [`axisLabels`](https://chartist.dev/api/layers/#axisLabels): those of every axis layer, plus how the labels are drawn. Properties: - `axes?: readonly string[]` Axis ids to draw; default: all axes, in declaration order. An unknown id throws. - `classNames?: Partial` Replaces some of the classes; the others keep their 1.x names. - `labels?: LabelMode` HTML (`foreignObject`) or SVG `text`; default: the chart's mode. - `align?: Partial` Aligns SVG text labels in their box, on every axis of the layer; default: as the 1.x CSS aligns HTML labels. An axis's own `labelAlign` wins over it. HTML labels ignore it (align them with CSS). ### BandEdgeValue ```ts type BandEdgeValue = number | { readonly x: number; readonly y: number } | null | undefined ``` A value of a [`band`](https://chartist.dev/api/layers/#band) edge given as values: a number, `{ x, y }` on a scale X axis, or `null` for a hole. ### BandEdgeOption ```ts type BandEdgeOption = number | readonly BandEdgeValue[] | string | { readonly series: number | string } ``` An edge of a [`band`](https://chartist.dev/api/layers/#band): a constant value, values of its own (one per label, like a series that isn't in the data), a series by name, or a series by index or name as `{ series }`. ### BandOptions ```ts interface BandOptions ``` The options of the [`band`](https://chartist.dev/api/layers/#band) layer: `low` and `high` edges, or a `series` group. Properties: - `low?: BandEdgeOption` The low edge: a constant value, values or a series. - `high?: BandEdgeOption` The high edge: a constant value, values or a series. - `series?: readonly (string | number)[]` A group of series, by index or name: the band covers their minimum to their maximum at each value index. Can't be combined with `low` and `high`. - `x?: string` The id of the X axis to draw on; default: the first X axis. - `y?: string` The id of the Y axis to draw on; default: the first Y axis. - `lineSmooth?: boolean | LineInterpolation` How the edges run between values, as `lineSmooth` of the line layer: `true` smooths them with `Interpolation.monotoneCubic`, `false` draws straight segments, or pass an interpolation. - `align?: 'center' | 'start'` Edge position on a step axis: `start` of each step (1.x) or its `center`, where bars are centered. - `className?: string` A class added to the band's group, such as `ct-series-c` for a color. - `mark?: boolean` Gives the points and bars of every layer `ct-in-band` when their value at their value index lies between the band's edges (edges included), else `ct-out-band`. The value is `y`, or `x` when a value has no `y` (the bars of a horizontal bar chart). Nodes at an index where the band has a hole are left alone. - `classNames?: { … }` Replaces the classes of what the layer draws and marks: `band` (`ct-band`), `area` (`ct-band-area`), `inBand` (`ct-in-band`) and `outBand` (`ct-out-band`). ### BarLayerOptions ```ts interface BarLayerOptions ``` The options of the [`bar`](https://chartist.dev/api/layers/#bar) layer. Without them, it draws the bars of each label side by side, 15 px apart, on the first X and Y axis. Properties: - `series?: readonly (string | number)[]` Series to draw, by index or name; default: all series. - `x?: string` The id of the X axis to draw on; default: the first X axis. - `y?: string` The id of the Y axis to draw on; default: the first Y axis. - `horizontalBars?: boolean` Values on the X axis, labels on the Y axis. - `stackBars?: boolean` Stacks the bars of a label on each other; `stackMode` says how. The value axis grows to fit the stacks (see [`Layer.extent`](https://chartist.dev/api/layers/#Layer.extent)). - `stackMode?: boolean | 'accumulate' | 'accumulate-relative' | 'overlap'` How stacked bars stack: `accumulate` (default) adds each value to the ones before, `accumulate-relative` stacks positive values up and negative values down from zero, and `overlap` (or `true`) draws every bar from zero, on top of each other. - `seriesBarDistance?: number` Distance between the bars of different series, in px. - `offset?: number` Moves all bars of the layer along the label axis, in px: to the right for vertical bars, down for horizontal bars (negative values move them the other way). Two stacked bar layers with opposite offsets draw grouped stacks. - `distributeSeries?: boolean` One series per value of a flat series (1.x `distributeSeries`). - `reverseData?: boolean` Draws the series in reverse order (1.x `reverseData`). Can't be combined with `series`. - `classNames?: { … }` Replaces the classes of what the layer draws: `series` (`ct-series`) and `bar` (`ct-bar`). ### LineSeriesOptions ```ts interface LineSeriesOptions ``` The line options you can set per series: in the `overrides` of [`line`](https://chartist.dev/api/layers/#line), or the `series` option of `lineChart`. Properties: - `lineSmooth?: boolean | LineInterpolation` How a line runs between its points: `true` smooths it with `Interpolation.monotoneCubic`, `false` draws straight segments (`Interpolation.none`), or pass an interpolation such as `Interpolation.cardinal()`, `Interpolation.step()` or your own. - `showPoint?: boolean` Draws a point at each value. - `showLine?: boolean` Draws the lines. - `showArea?: boolean` Fills the area between each line and `areaBase`. - `areaBase?: number` The value the areas close at, usually 0. It's kept within the Y axis range. ### LineLayerOptions ```ts interface LineLayerOptions extends LineSeriesOptions ``` The options of the [`line`](https://chartist.dev/api/layers/#line) layer. Without them, it draws every series as a smooth line with points and no area, on the first X and Y axis. Properties: - `lineSmooth?: boolean | LineInterpolation` How a line runs between its points: `true` smooths it with `Interpolation.monotoneCubic`, `false` draws straight segments (`Interpolation.none`), or pass an interpolation such as `Interpolation.cardinal()`, `Interpolation.step()` or your own. - `showPoint?: boolean` Draws a point at each value. - `showLine?: boolean` Draws the lines. - `showArea?: boolean` Fills the area between each line and `areaBase`. - `areaBase?: number` The value the areas close at, usually 0. It's kept within the Y axis range. - `series?: readonly (string | number)[]` Series to draw, by index or name; default: all series. - `x?: string` The id of the X axis to draw on; default: the first X axis. - `y?: string` The id of the Y axis to draw on; default: the first Y axis. - `align?: 'center' | 'start'` Point position on a step axis: `start` of each step (1.x) or its `center`, where bars are centered. - `overrides?: Readonly>` Option overrides per series name (the 1.x `series` option). - `reverseData?: boolean` Draws the series in reverse order (1.x `reverseData`). Can't be combined with `series`. - `classNames?: { … }` Replaces the classes of what the layer draws: `series` (`ct-series`), `line` (`ct-line`), `point` (`ct-point`) and `area` (`ct-area`). ### PieLayerOptions ```ts interface PieLayerOptions ``` The options of the [`pie`](https://chartist.dev/api/layers/#pie) layer. Without them, it draws a filled pie from the top, with a label halfway out on each slice. Properties: - `startAngle?: number` Where the first slice starts, in degrees clockwise from the top. - `total?: number` Fixed total; slices are relative to it (gauges). Defaults to the sum. - `donut?: boolean` Draws a donut: rings instead of filled slices. - `donutWidth?: string | number` Donut ring width in px, or a share of the radius (`'25%'`). Without it, the stylesheet sets the width (`--ct-donut-width`, 60px by default), and in the browser the ring is laid out for the width the stylesheet gives a slice. Without a DOM (`toScene()`, `renderToString()`), it's laid out for 60 px, so set `donutWidth` there if the page's CSS draws another width. - `showLabel?: boolean` Draws a label for each slice; on by default. - `labelOffset?: number` Moves the labels away from the center (positive) or toward it (negative), in px. - `labelPosition?: 'center' | 'inside' | 'outside'` Where labels go: `inside` (the default: halfway out on a pie, on the middle of a donut's ring), `outside` (at the outer edge) or `center` (in the middle; move them with `labelOffset`). - `labelTransform?: (value: Label, index: number) => Label | null | undefined` Formats a slice's label: its entry in `labels`, or else its value. Wins over `labelInterpolationFnc` when both are set. - `labelInterpolationFnc?: (value: Label, index: number) => Label | null | undefined` The 1.x name of `labelTransform`, which wins when both are set. - `labelDirection?: LabelDirection` Which way labels anchor: `neutral` (the default), `explode` (away from the center, for labels outside the pie) or `implode` (toward it). - `ignoreEmptyValues?: boolean` Leaves out the slices and labels of zero values. - `preventOverlappingLabelOffset?: number` Shifts labels that would overlap another one by this many px, up and left, judging the overlap by the length of the drawn label text. - `classNames?: { … }` Replaces the classes of what the layer draws: `series` (`ct-series`), `slicePie` (`ct-slice-pie`), `sliceDonut` (`ct-slice-donut`) and `label` (`ct-label`). ### Coordinates ```ts type Coordinates = 'cartesian' | 'polar' ``` The coordinate system a chart draws in: `cartesian` places values on X and Y axes (lines, bars), `polar` around a center (pies and donuts). ### LayerContext ```ts interface LayerContext ``` What a layer's `compose` gets: the data it draws, the area to draw in, the axes and ways to measure text and styles. Properties: - `data: PreparedData` The prepared data; only the layer's series when it selects some. - `area: Area` The drawing area, in px in the SVG. - `padding: ChartPadding` The chart's padding, in px. - `layerIndex: number` The layer's index in the chart's `layers`; part of its nodes' keys. - `coordinates: Coordinates` The chart's coordinate system. - `measureText: MeasureText` Measures text with the renderer's metrics (in the browser, with the page's fonts and the chart's classes), for layers that lay out labels. - `measureStyle?: MeasureStyle` Reads computed styles with the renderer (in the browser, with the page's CSS and the chart's classes), for layout that depends on the stylesheet, such as a donut's ring width. Absent without a DOM. - `axes: ChartAxes` Axes created after `layout`, when the chart has axes. - `normalized?: NormalizedChartData` The data as the axes see it, when the chart has axes. - `seriesIndexes?: readonly number[]` Chart-wide index of each series in `data` (a layer may select a subset of the series). Absent when the layer draws all series. - `labels: LabelMode` How label layers draw labels: chart option, else the renderer's. - `insetArea?: Partial>` The boxes the layer reserved with [`Layer.inset`](https://chartist.dev/api/layers/#Layer.inset), one per side, in px in the SVG: just outside the axes on that side (and the boxes of layers listed before it), as long as the drawing area. Absent when the layer reserved no room. ### ExtentContext ```ts interface ExtentContext ``` What a layer's `extent` gets: its series as the axes see them, and the ids of the axes it draws on. Properties: - `normalized: NormalizedChartData` The layer's series, normalized as for the axes (`{ x, y }` values or holes); only the layer's series when it selects some. - `axes: { … }` The ids of the X and Y axis the layer draws on. - `transforms?: { … }` The transforms of the X and Y axis the layer draws on, for axes that declare one (see [`AxisTransform`](https://chartist.dev/api/scales/#AxisTransform)), such as a log axis. ### Layer ```ts interface Layer ``` A layer renders series into the chart area. Built-in and custom layers use the same contract: the "Custom layers" guide explains each field and how to write and share a layer. Properties: - `type: string` Name used in error messages, e.g. `line`. - `chartType?: string` The kind of chart the layer draws, when `type` doesn't say it: a [`chart`](https://chartist.dev/api/chart/#chart) without `className` gets `ct-chart-bar` if a layer is a `bar`, else `ct-chart-pie` or `ct-chart-donut` for a `pie` or `donut`, else `ct-chart-line`. Without it, `type` counts. - `coordinates: readonly Coordinates[]` Coordinate systems the layer can draw in. - `axes?: { … }` Axis ids to draw on; default: the first X and the first Y axis. - `series?: readonly (string | number)[]` Series to draw, by index or name; default: all series. - `extent?: (context: ExtentContext) => Readonly>` Reports the range of values the layer needs on its axes beyond the values themselves, by axis id, such as the sums of stacked bars. Scale axes include it in the range they fit to the data (an axis's own `low` and `high` still win); on an axis several layers report a range for, the axis covers them all. Runs before the axes are created, on every layout. - `inset?: (context: InsetContext) => Partial | undefined` Reserves room outside the drawing area, in px per side, for something the layer draws next to the chart, such as an axis title or a legend. The room is added to the chart's padding, so the area shrinks and the axes stay next to it; the layer draws in the boxes its context gets as `insetArea`. Layers that reserve room on the same side stack outward in the order of `layers`. Runs on every layout, before the `layout` listeners, so the room can depend on the chart's size. The "Custom layers" guide shows a layer that uses it. - `compose: (context: LayerContext) => readonly SceneNode[]` Draws the layer: returns its top-level nodes, usually one `series` group per series. The pipeline keys their data nodes and passes every node with a role through the `compose` listeners. - `refine?: (node: SceneNode, context: LayerContext) => SceneNode | undefined` Refines what the layers drew: once every layer has composed, called for each node with a role, from all layers (children before their parents), before the `compose` events, so listeners see the result. Return a changed node, such as one with a class added, or `undefined` to leave it. The node keeps its key and its children whatever you return, and refining can't drop a node. With several refining layers, a node passes through them in the order of `layers`. Leave it out when the layer only draws its own nodes: charts without a refining layer skip this step. - `annotate?: (drawn: readonly SceneNode[], context: LayerContext) => SceneNode[]` Draws from where the other layers drew, such as a label at each bar's end. Runs once the nodes of every layer without `annotate` are composed, keyed, refined and through the `compose` listeners, so a bar a listener dropped isn't there and a moved one is where it ends up. It gets those finished top-level nodes in the order of `layers`, not the nodes of other annotating layers. The nodes it returns are keyed, refined and emitted like `compose`'s, after them, at the layer's place in `layers`; their `compose` events come after the other layers'. Find the nodes you need with `findAll()`. - `keyframes?: (previous: SceneNode, next: SceneNode, context: LayerContext, progress: readonly number[]) => Keyframes | undefined` Gives the keyframes of an update animation for one of the layer's nodes, for attributes that don't move well number by number, such as an arc that should turn around its center. A renderer that animates updates (`smilAnimations()` from `chartist/live`) asks once per update, never per frame, with the progress points of the chart's easing: 0 is the previous node, 1 the new one, and points beyond them come from an easing that overshoots. Plugins with `keyframes` are asked first. Leave it out to animate the layer's nodes number by number. See also: https://chartist.dev/docs/custom-layers/ ## Scales and axes (Chartist API) > Axes that turn values into positions: step, auto-scaled and fixed, placed on any side. ### cartesianAxes ```ts function cartesianAxes(input: readonly AxisDefinition[] | AxesConfig, extra?: Pick): CartesianAxes ``` Creates the axes for the `axes` option of [`chart`](https://chartist.dev/api/chart/#chart): a list of axes, each with an `id`, a `side` and a `type`, several per side if needed. Axis types are v2 factories ([`stepAxis`](https://chartist.dev/api/scales/#stepAxis), [`autoScaleAxis`](https://chartist.dev/api/scales/#autoScaleAxis), [`fixedScaleAxis`](https://chartist.dev/api/scales/#fixedScaleAxis)) or 1.x axis classes; the other fields are the 1.x axis options (`offset`, `low`, `high`, `ticks`, …). Layers pick their axes by id, and each axis scales to the series of the layers drawn on it. Offsets default to 30 px for X axes and 40 px for Y axes, and axes on the same side stack outward in list order. Unknown or duplicate ids throw. The 1.x-style `{ x, y }` form also works, with the same defaults: X at the bottom unless its `position` is `'start'`, Y on the left unless it's `'end'`. Parameters: - `input: readonly AxisDefinition[] | AxesConfig` The axes: a list of [`AxisDefinition`](https://chartist.dev/api/scales/#AxisDefinition)s, or the 1.x `{ x, y }` form. - `extra?: Pick` `reverseData` and `normalize`, for the list form (the `{ x, y }` form takes them inline). ```ts import { autoScaleAxis, axisGrid, axisLabels, bar, cartesianAxes, chart, line, stepAxis } from 'chartist'; const months = ['Jan', 'Feb', 'Mar', 'Apr']; chart('#chart', { data: { labels: months, series: [ { name: 'eur', data: [120, 180, 150, 210] }, { name: 'usd', data: [130, 195, 160, 230] } ] }, axes: cartesianAxes([ { id: 'month', side: 'bottom', type: stepAxis(), ticks: months }, // Both on the left: 'eur' next to the chart, 'usd' outside it. { id: 'eur', side: 'left', type: autoScaleAxis(), low: 0 }, { id: 'usd', side: 'left', type: autoScaleAxis(), low: 0, offset: 50 } ]), layers: [ axisGrid({ axes: ['month', 'eur'] }), bar({ series: ['eur'], y: 'eur' }), line({ series: ['usd'], y: 'usd', align: 'center' }), axisLabels() ], className: 'ct-chart-bar' }); ``` ### stepAxis ```ts function stepAxis(): AxisFactory ``` Creates a step axis: evenly spaced ticks, one per step, that place values by their index rather than by their value. It's the label axis of line and bar charts. Same rules as 1.x `StepAxis`. Its ticks are the data's `labels`, or the axis's `ticks` when you give them. With `stretch`, the last tick sits at the axis end. ```ts import { autoScaleAxis, axisGrid, axisLabels, cartesianAxes, chart, line, stepAxis } from 'chartist'; const months = ['Jan', 'Feb', 'Mar', 'Apr']; chart('#chart', { data: { labels: months, series: [[4, 6, 5, 9]] }, axes: cartesianAxes([ // The months as ticks; stretch: the first and last at the edges. { id: 'month', side: 'bottom', type: stepAxis(), stretch: true }, { id: 'value', side: 'left', type: autoScaleAxis() } ]), layers: [axisGrid(), line(), axisLabels()] }); ``` ### autoScaleAxis ```ts function autoScaleAxis(): AxisFactory ``` Creates an auto-scaled axis: a scale fitted to the data, with round ticks at least `scaleMinSpace` px apart. Set `low`, `high` or `referenceValue` to widen it, and `onlyInteger` for whole-number steps. Same rules as 1.x `AutoScaleAxis`. It's the value axis of line and bar charts. As an X axis, it places `{ x, y }` values at their real distances. ```ts import { autoScaleAxis, axisGrid, axisLabels, cartesianAxes, chart, line } from 'chartist'; chart('#chart', { data: { series: [ [ { x: 1, y: 2 }, { x: 2.5, y: 5 }, { x: 7, y: 3 }, { x: 10, y: 8 } ] ] }, axes: cartesianAxes([ { id: 'x', side: 'bottom', type: autoScaleAxis(), onlyInteger: true }, { id: 'y', side: 'left', type: autoScaleAxis(), low: 0 } ]), layers: [axisGrid(), line(), axisLabels()] }); ``` ### fixedScaleAxis ```ts function fixedScaleAxis(): AxisFactory ``` Creates a fixed-scale axis: a scale from `low` to `high` (by default the smallest and largest value) with the `ticks` you give. Without `ticks`, `divisor` splits the range into equal parts with a tick at each end, so `divisor: 4` makes 5 ticks from `low` to `high` (1.x left out the one at `high`). ```ts import { axisGrid, axisLabels, cartesianAxes, chart, fixedScaleAxis, line, stepAxis } from 'chartist'; const teams = ['Red', 'Blue', 'Green']; chart('#chart', { data: { labels: teams, series: [[64, 81, 47]] }, axes: cartesianAxes([ { id: 'team', side: 'bottom', type: stepAxis(), ticks: teams }, // A percentage scale with three grid lines. { id: 'percent', side: 'left', type: fixedScaleAxis(), ticks: [0, 50, 100], low: 0, high: 100 } ]), layers: [axisGrid(), line({ showLine: false }), axisLabels()] }); ``` ### AxisDimension ```ts type AxisDimension = 'x' | 'y' ``` Which way an axis runs: `x` (horizontal) or `y` (vertical). ### AxisSide ```ts type AxisSide = 'top' | 'bottom' | 'left' | 'right' ``` Where an axis sits: `top`/`bottom` are X axes, `left`/`right` Y axes. ### AxisContext ```ts interface AxisContext ``` What an [`AxisFactory`](https://chartist.dev/api/scales/#AxisFactory) gets to create an axis: its direction, the data, the chart area and the axis options. Properties: - `dimension: AxisDimension` Which way the axis runs: `x` or `y`, from its side. - `series: NormalizedSeries[]` Normalized multi-value series (`{ x, y }` values): the series bound to the axis, or all of them. - `labels: readonly Label[]` Normalized labels, padded to the longest series. - `area: Area` The chart area in px, with the offsets of all axes reserved. - `options: AxisOptions` The options of the axis definition, without `id`, `side` and `type`. - `extent?: AxisExtent` The range the layers drawn on the axis need beyond their values, such as the sums of stacked bars (see [`Layer.extent`](https://chartist.dev/api/layers/#Layer.extent)). Scale axes include it in the range they fit to the data; their own `low` and `high` still win. ### AxisExtent ```ts interface AxisExtent ``` A range of values on an axis, from `low` to `high`: what a layer reports in [`Layer.extent`](https://chartist.dev/api/layers/#Layer.extent). Properties: - `low: number` The lowest value. - `high: number` The highest value. ### AxisTransform ```ts interface AxisTransform ``` How an axis that isn't linear in its values maps them: the axis is linear in `forward(value)`, as a log axis is in the logarithm of its values. Layers that fit lines, such as `trendLine()` from `@chartist-js/essentials`, fit them in that space, so the lines are straight on the axis. Properties: - `forward: (value: number) => number` The value in the space the axis is linear in; `NaN` or an infinite number for a value the axis has no place for (zero on a log axis). - `inverse: (value: number) => number` The value of a number in that space: the inverse of `forward`. ```ts import type { AxisTransform } from 'chartist'; // A base 10 log scale. const log10: AxisTransform = { forward: value => Math.log10(value), inverse: value => 10 ** value }; ``` ### ScaleAxis ```ts interface ScaleAxis ``` A positioned axis: ticks, range and projection into px along the axis. An [`AxisFactory`](https://chartist.dev/api/scales/#AxisFactory) creates it once the chart area is known; layers read it to place what they draw. Properties: - `dimension: AxisDimension` Which way the axis runs: `x` or `y`. - `kind: string` The axis type, e.g. `step`, `autoScale`, `fixedScale`. - `stretch?: boolean` Step axes: whether the last tick sits at the axis end. - `step?: number` Step axes: the distance between two ticks in px. - `ticks: readonly Label[]` The values at which grid lines and labels are drawn: labels on a step axis, numbers on a scale. - `minorTicks?: readonly number[]` Values between the ticks that get a grid line without a label, such as 20 to 90 between 10 and 100 on a log axis. The grid draws them beneath the other grid lines, with the class `ct-grid-minor`. - `transform?: AxisTransform` How the axis maps its values, when it isn't linear in them; see [`AxisTransform`](https://chartist.dev/api/scales/#AxisTransform). - `range?: { … }` The values at the axis start (`min`) and end (`max`). Step axes have none. - `length: number` Axis length in px. - `project: (value: string | number | Date | { x: number; y: number } | { x: number } | { y: number } | undefined, index?: number, series?: NormalizedSeries) => number` Places a value on the axis: the distance in px from the axis start (left, or bottom for y). Step axes place by `index` instead. ### AxisFactory ```ts interface AxisFactory ``` An axis type: creates the axis once the chart area and data are known. [`stepAxis`](https://chartist.dev/api/scales/#stepAxis), [`autoScaleAxis`](https://chartist.dev/api/scales/#autoScaleAxis) and [`fixedScaleAxis`](https://chartist.dev/api/scales/#fixedScaleAxis) return one; write your own for a scale they don't cover. It goes in the `type` of an axis. Properties: - `kind: string` The axis type, e.g. `step`. The axis it creates has the same `kind`; the line and bar layers center on `step` axes. - `transform?: AxisTransform` How the axes of this type map their values, when they aren't linear in them. Declared on the factory too, so layers know it before the axes exist (see [`ExtentContext`](https://chartist.dev/api/layers/#ExtentContext)); the axis it creates carries the same one. - `create: (context: AxisContext) => ScaleAxis` Creates the axis for a chart area and data. Runs on every layout, so after resizes too. ### LegacyAxisClass ```ts type LegacyAxisClass = (...args: any[]) => unknown ``` A 1.x axis class (or a custom subclass of the 1.x `Axis`). ### AxisSetup ```ts interface AxisSetup extends Omit ``` An axis's setup: an axis type plus the 1.x axis options (`offset`, `low`, `high`, `ticks`, `showGrid`, …). Properties: - `low?: number` The lowest value this axis shows, instead of the data's lowest. - `high?: number` The highest value this axis shows, instead of the data's highest. - `referenceValue?: number` A value this axis always includes, such as 0. - `offset?: number` Room in px for the axis labels, between the drawing area and the chart's edge. - `position?: 'end' | 'start'` Where the labels go: `start` (left or top) or `end` (right or bottom). - `labelOffset?: { … }` Moves the labels by `x` and `y` px from where they'd go. - `showLabel?: boolean` Draws the axis labels. - `labelAlign?: Partial` Aligns this axis's SVG text labels in their box, over the `align` of [`axisLabels`](https://chartist.dev/api/layers/#axisLabels). HTML labels ignore it (align them with CSS). - `showGrid?: boolean` Draws the grid lines of the axis. - `labelTransform?: (value: Label, index: number) => Label | null | undefined` Turns each label (and its index) into the text to show. Return `null` or `undefined` to leave a label out. Wins over `labelInterpolationFnc` when both are set. - `labelInterpolationFnc?: (value: Label, index: number) => Label | null | undefined` The 1.x name of [`AxisOptions.labelTransform`](https://chartist.dev/api/classes/#AxisOptions.labelTransform), which wins when both are set. - `scaleMinSpace?: number` The minimum space in px between two grid lines of a scale: the scale takes fewer, larger steps to keep it. - `onlyInteger?: boolean` Only whole numbers on the scale. - `ticks?: Label[]` The ticks: the labels of a step axis, or the values of a fixed scale axis. - `stretch?: boolean` Step axis: spreads the ticks over the whole axis, so the last one lands on its end (line charts with `fullWidth` set it). - `divisor?: number` Fixed scale axis without `ticks`: the number of equal steps between its low and high, with a tick at each end. - `highLow?: { … }` The value range of a scale as `{ high, low }`, instead of the one computed from the data. - `type: LegacyAxisClass | AxisFactory` A v2 axis factory, or a 1.x axis class. ### AxisDefinition ```ts interface AxisDefinition extends AxisSetup ``` An axis of a composed chart, in the list form of [`cartesianAxes`](https://chartist.dev/api/scales/#cartesianAxes): an id that layers bind to, a side, a type and the 1.x axis options. Properties: - `low?: number` The lowest value this axis shows, instead of the data's lowest. - `high?: number` The highest value this axis shows, instead of the data's highest. - `referenceValue?: number` A value this axis always includes, such as 0. - `offset?: number` Room in px for the axis labels, between the drawing area and the chart's edge. - `position?: 'end' | 'start'` Where the labels go: `start` (left or top) or `end` (right or bottom). - `labelOffset?: { … }` Moves the labels by `x` and `y` px from where they'd go. - `showLabel?: boolean` Draws the axis labels. - `labelAlign?: Partial` Aligns this axis's SVG text labels in their box, over the `align` of [`axisLabels`](https://chartist.dev/api/layers/#axisLabels). HTML labels ignore it (align them with CSS). - `showGrid?: boolean` Draws the grid lines of the axis. - `labelTransform?: (value: Label, index: number) => Label | null | undefined` Turns each label (and its index) into the text to show. Return `null` or `undefined` to leave a label out. Wins over `labelInterpolationFnc` when both are set. - `labelInterpolationFnc?: (value: Label, index: number) => Label | null | undefined` The 1.x name of [`AxisOptions.labelTransform`](https://chartist.dev/api/classes/#AxisOptions.labelTransform), which wins when both are set. - `scaleMinSpace?: number` The minimum space in px between two grid lines of a scale: the scale takes fewer, larger steps to keep it. - `onlyInteger?: boolean` Only whole numbers on the scale. - `ticks?: Label[]` The ticks: the labels of a step axis, or the values of a fixed scale axis. - `stretch?: boolean` Step axis: spreads the ticks over the whole axis, so the last one lands on its end (line charts with `fullWidth` set it). - `divisor?: number` Fixed scale axis without `ticks`: the number of equal steps between its low and high, with a tick at each end. - `highLow?: { … }` The value range of a scale as `{ high, low }`, instead of the one computed from the data. - `type: LegacyAxisClass | AxisFactory` A v2 axis factory, or a 1.x axis class. - `id: string` Names the axis, for the `x` and `y` of layers and the `axes` of [`axisGrid`](https://chartist.dev/api/layers/#axisGrid) and [`axisLabels`](https://chartist.dev/api/layers/#axisLabels). Ids must be unique. - `side: AxisSide` Where the axis sits: `bottom` or `top` makes an X axis, `left` or `right` a Y axis. Axes on the same side stack outward in list order. ### AxesConfig ```ts interface AxesConfig ``` The config of [`cartesianAxes`](https://chartist.dev/api/scales/#cartesianAxes): one X and one Y axis in the 1.x form, or a list of axes, plus how the data is read. Properties: - `x?: AxisSetup` The X axis in the 1.x form: at the bottom, or the top with `position: 'start'`. Its `offset` defaults to 30 px. - `y?: AxisSetup` The Y axis in the 1.x form: on the left, or the right with `position: 'end'`. Its `offset` defaults to 40 px. - `axes?: readonly AxisDefinition[]` The axis list form; used instead of `x`/`y`. - `reverseData?: boolean` Reverse labels and series before projecting (1.x `reverseData`). - `normalize?: { … }` How values are normalized (1.x `normalizeData`): `multi` is `true` for `{ x, y }` values, or the dimension single values go to; `distributed` turns a flat series into one series per value. ### PlacedScaleAxis ```ts interface PlacedScaleAxis extends ScaleAxis ``` A created axis with its place in the chart: what the axis layers draw, and what a layer finds in `axes.all`. Properties: - `dimension: AxisDimension` Which way the axis runs: `x` or `y`. - `kind: string` The axis type, e.g. `step`, `autoScale`, `fixedScale`. - `stretch?: boolean` Step axes: whether the last tick sits at the axis end. - `step?: number` Step axes: the distance between two ticks in px. - `ticks: readonly Label[]` The values at which grid lines and labels are drawn: labels on a step axis, numbers on a scale. - `minorTicks?: readonly number[]` Values between the ticks that get a grid line without a label, such as 20 to 90 between 10 and 100 on a log axis. The grid draws them beneath the other grid lines, with the class `ct-grid-minor`. - `transform?: AxisTransform` How the axis maps its values, when it isn't linear in them; see [`AxisTransform`](https://chartist.dev/api/scales/#AxisTransform). - `range?: { … }` The values at the axis start (`min`) and end (`max`). Step axes have none. - `length: number` Axis length in px. - `project: (value: string | number | Date | { x: number; y: number } | { x: number } | { y: number } | undefined, index?: number, series?: NormalizedSeries) => number` Places a value on the axis: the distance in px from the axis start (left, or bottom for y). Step axes place by `index` instead. - `id: string` The axis id: from its definition, or `x` and `y` in the 1.x form. - `side: AxisSide` The side the axis sits on. - `inset: number` Distance in px from the chart area to this axis's band (stacking). - `options: Omit` The axis options (offset, labels, grid), without the type. ### ChartAxes ```ts interface ChartAxes ``` The axes of a laid-out chart. Properties: - `x?: ScaleAxis` The X axis (for layers: the bound one; for the chart: the first). - `y?: ScaleAxis` The Y axis (for layers: the bound one; for the chart: the first). - `all?: readonly PlacedScaleAxis[]` All axes of the chart, in declaration order. ### CartesianAxes ```ts interface CartesianAxes ``` Axes for a chart's `axes` option, created by [`cartesianAxes`](https://chartist.dev/api/scales/#cartesianAxes). The pipeline asks them for the chart area and, after `layout`, to create the axes. Charts without axes don't bundle any of this. Properties: - `config: AxesConfig` The config the axes were created from, with the default offsets filled in. - `area: (width: number, height: number, padding: ChartPadding) => { width: number; height: number; area: Area }` The chart area with the axis offsets reserved. - `definitions: readonly AxisDefinition[]` The axis definitions, in declaration order. - `bind?: (layers: readonly Layer[], data: PreparedData) => LayerBinding[]` Each layer's axis ids and series selection, checked against the data. Without it, every layer draws all series on the first X and Y axis. - `create: (data: PreparedData, area: Area, bindings?: readonly LayerBinding[], layers?: readonly Layer[]) => { axes: ChartAxes; normalized: NormalizedChartData }` The axes for the final chart area, and the data as they see it. With `bindings`, each axis's range comes only from the series bound to it. With `layers`, it also covers the extents they report (see [`Layer.extent`](https://chartist.dev/api/layers/#Layer.extent)). ### LabelAlign ```ts interface LabelAlign ``` How an SVG text label sits in its label box, as set in the `align` option of [`axisLabels`](https://chartist.dev/api/layers/#axisLabels). Properties: - `horizontal: 'end' | 'start' | 'middle'` Aligns the text to the box's left (`start`), center or right (`end`). - `vertical: 'top' | 'bottom' | 'middle'` Aligns the lines of text to the box's top, middle or bottom. ## Scene (Chartist API) > The scene a chart composes before rendering: nodes, their roles and data, and how to find and change them. ### node ```ts function node(name: string, attributes: Record, className: string, meta: RoleMeta): RoleNode function node(name: string, attributes?: Record, className?: string, meta?: { readonly key?: string; readonly ctx?: NodeContext }): PlainNode ``` Creates a scene node: an SVG element with attributes and classes, and, for a node with a role, its data context. Use it in `compose` listeners and custom layers to add or replace elements. With a `role` in `meta`, the node is typed as that role's node and the context is checked. Parameters: - `name: string` SVG element name, e.g. `circle`, `g` or `text` - `attributes: Record` Attributes; `undefined` and `null` values are skipped - `className: string` Space-separated class names - `meta: RoleMeta` Role, data context and key of the node Returns: A new node without children ```ts import { lineChart, node } from 'chartist'; // Circles instead of the line chart's points. lineChart( '#chart', { series: [[1, 3, 2, 4]] }, { plugins: [ { name: 'circles', install(chart) { chart.on('compose', current => { if (current.role !== 'point') { return; } const { x, y } = current.ctx; return node('circle', { cx: x, cy: y, r: 5 }, 'ct-circle', { role: 'point', ctx: current.ctx, key: current.key }); }); } } ] } ); ``` ### findAll ```ts function findAll(scene: SceneNode, query: NodeQuery & { readonly role: R }): RoleNode[] function findAll(scene: SceneNode, query?: NodeQuery): SceneNode[] ``` Finds all nodes of a scene that match the query, in document order, the root included. With a `role`, the nodes are typed as that role's nodes. Parameters: - `scene: SceneNode` The root of the tree to search, e.g. the `scene` payload - `query: NodeQuery & { readonly role: R }` The fields to match; without one, every node matches ```ts import { barChart, findAll } from 'chartist'; barChart( '#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [[5, 4, 3, 7]] }, { plugins: [ { name: 'log-bars', install(chart) { chart.on('scene', ({ scene }) => { for (const bar of findAll(scene, { role: 'bar' })) { console.log(bar.key, bar.ctx.value, bar.ctx.y2); } }); } } ] } ); ``` ### mapNodes ```ts function mapNodes(scene: SceneNode, mapper: (current: SceneNode) => void | SceneNode | null | undefined): SceneNode | null ``` Maps a tree bottom-up: children first, then their parent with the mapped children. Return a node to replace, `null` to drop, or `undefined` to keep. Subtrees without changes are reused, not copied. Parameters: - `scene: SceneNode` The root of the tree to map - `mapper: (current: SceneNode) => void | SceneNode | null | undefined` Called once per node, with the node's mapped children Returns: The mapped tree, or `null` if the mapper dropped the root ```ts import { lineChartConfig, mapNodes } from 'chartist'; import { renderToString, toScene } from 'chartist/server'; const scene = toScene( lineChartConfig({ labels: ['Mon', 'Tue', 'Wed'], series: [[1, 3, 2]] }), { width: 600, height: 300 } ); // No grid lines, and the points marked for the stylesheet. const mapped = mapNodes(scene, current => { if (current.role === 'grid') { return null; } if (current.role === 'point') { return current.addClass('is-marked'); } }); const markup = mapped ? renderToString(mapped) : ''; ``` ### AttrValue ```ts type AttrValue = string | number ``` The value of a node attribute. Numbers are written as they are, e.g. `x1="12.5"`. ### NodeContext ```ts interface NodeContext ``` Data attached to a node without a role, e.g. by a plugin. Never rendered. Any fields are allowed; the ones below have a meaning to Chartist. Properties: - `seriesIndex?: number` The index of the series the node belongs to. With it, the node gets a `key` and [`findAll`](https://chartist.dev/api/scene/#findAll) can find it by series. - `valueIndex?: number` The index of the value in its series the node stands for. - `value?: unknown` The value the node stands for. - `meta?: unknown` The `meta` of the value or series, as given in the data. ### NodeMeta ```ts interface NodeMeta ``` What a node means, next to how it looks: its key, role and data context. None of it is rendered. Properties: - `key?: string` A stable id, e.g. `0/1/3` (layer, series, value). - `role?: keyof NodeRoles` What the node draws, e.g. `point` or `bar`: a key of [`NodeRoles`](https://chartist.dev/api/scene/#NodeRoles). - `ctx?: object` The node's data: what its role declares, or anything for plain nodes. ### SceneNodeFields ```ts interface SceneNodeFields ``` The fields every node has, whatever its role. Properties: - `name: string` SVG element name, e.g. `g`, `path`, `line`. - `attrs: Readonly>` The element's attributes, without `class`. Names with a `ct:` prefix (`ct:value`, `ct:meta`) are written in the Chartist namespace. - `classes: readonly string[]` The element's class names, each once, in the order they were added. - `children: readonly SceneNode[]` The child nodes, in document order. - `content?: string` Text content, set with `text()`. - `key?: string` A stable id that survives updates. The pipeline keys data nodes that have none: `layer/series/value` for a value's node (e.g. `0/1/3`), `layer/series` for a series' node. ### SceneNodeMethods ```ts interface SceneNodeMethods ``` The builder methods of every node. Each returns a changed copy of the same type and leaves the node it's called on as it was. Properties: - `attr: (attributes: Record) => this` Sets attributes; `undefined` or `null` removes one. - `addClass: (names: string) => this` Adds space-separated class names that aren't present yet. - `removeClass: (names: string) => this` Removes space-separated class names. - `append: (child: SceneNode, insertFirst?: boolean) => this` Adds a child at the end, or at the start with `insertFirst`. - `remove: (child: SceneNode) => this` Removes a direct child (by identity). - `replace: (child: SceneNode, next: SceneNode) => this` Replaces a direct child (by identity). - `text: (content: string) => this` Sets the text content. - `empty: () => this` Removes all children and text content. ### RoleNode ```ts type RoleNode = SceneNodeFields & SceneNodeMethods & { … } ``` A node of a role, such as `RoleNode<'point'>`: its context and attributes are the ones [`NodeRoles`](https://chartist.dev/api/scene/#NodeRoles) declares for that role. [`findAll`](https://chartist.dev/api/scene/#findAll) with a `role` returns these. Properties: - `name: string` SVG element name, e.g. `g`, `path`, `line`. - `attrs: Readonly> & NodeRoles[R]['attrs']` The element's attributes, typed as the built-in layers set them; `attr()` can change them. - `classes: readonly string[]` The element's class names, each once, in the order they were added. - `children: readonly SceneNode[]` The child nodes, in document order. - `content?: string` Text content, set with `text()`. - `key?: string` A stable id that survives updates. The pipeline keys data nodes that have none: `layer/series/value` for a value's node (e.g. `0/1/3`), `layer/series` for a series' node. - `attr: (attributes: Record) => this` Sets attributes; `undefined` or `null` removes one. - `addClass: (names: string) => this` Adds space-separated class names that aren't present yet. - `removeClass: (names: string) => this` Removes space-separated class names. - `append: (child: SceneNode, insertFirst?: boolean) => this` Adds a child at the end, or at the start with `insertFirst`. - `remove: (child: SceneNode) => this` Removes a direct child (by identity). - `replace: (child: SceneNode, next: SceneNode) => this` Replaces a direct child (by identity). - `text: (content: string) => this` Sets the text content. - `empty: () => this` Removes all children and text content. - `role: R` What the node draws, e.g. `point`. - `ctx: NodeRoles[R]['ctx'] & { … }` The node's data, as its role declares it. `seriesIndex` and `valueIndex` read as `number | undefined` on any node, declared or not. ### PlainNode ```ts type PlainNode = SceneNodeFields & SceneNodeMethods & { … } ``` A node without a role: groups, decorations, anything a plugin adds. Properties: - `name: string` SVG element name, e.g. `g`, `path`, `line`. - `attrs: Readonly>` The element's attributes, without `class`. Names with a `ct:` prefix (`ct:value`, `ct:meta`) are written in the Chartist namespace. - `classes: readonly string[]` The element's class names, each once, in the order they were added. - `children: readonly SceneNode[]` The child nodes, in document order. - `content?: string` Text content, set with `text()`. - `key?: string` A stable id that survives updates. The pipeline keys data nodes that have none: `layer/series/value` for a value's node (e.g. `0/1/3`), `layer/series` for a series' node. - `attr: (attributes: Record) => this` Sets attributes; `undefined` or `null` removes one. - `addClass: (names: string) => this` Adds space-separated class names that aren't present yet. - `removeClass: (names: string) => this` Removes space-separated class names. - `append: (child: SceneNode, insertFirst?: boolean) => this` Adds a child at the end, or at the start with `insertFirst`. - `remove: (child: SceneNode) => this` Removes a direct child (by identity). - `replace: (child: SceneNode, next: SceneNode) => this` Replaces a direct child (by identity). - `text: (content: string) => this` Sets the text content. - `empty: () => this` Removes all children and text content. - `role?: undefined` Always absent: plain nodes have no role. - `ctx?: NodeContext` Data you attach to the node, if any. ### SceneNode ```ts type SceneNode = { [R in NodeRole]: RoleNode }[NodeRole] | PlainNode ``` A scene node: a node of one of the roles in [`NodeRoles`](https://chartist.dev/api/scene/#NodeRoles), or a plain one. Check `role` to narrow it, e.g. `if (node.role === 'point') node.ctx.x`. Nodes are immutable: the builder methods ([`SceneNodeMethods`](https://chartist.dev/api/scene/#SceneNodeMethods)) return a new node. The `compose` and `scene` phase events pass nodes to your listeners, and the renderers turn them into SVG. ### RoleMeta ```ts type RoleMeta = { … } & object extends NodeRoles[R]['ctx'] ? { … } : { … } ``` What [`node`](https://chartist.dev/api/scene/#node) takes as `meta` for a node of role `R`: the role, a key, and the role's context (required when the role declares fields). Properties: - `role: R` The node's role, e.g. `point`. - `key?: string` A stable id; the pipeline sets one for data nodes that have none. ### Point ```ts interface Point ``` A position in the chart's SVG, in px from its top left corner. Properties: - `x: number` Px from the left edge of the SVG. - `y: number` Px from the top edge of the SVG. ### NoSeries ```ts interface NoSeries ``` The context of nodes that belong to no series, such as grid lines. It says so, so `ctx.seriesIndex` reads as `number | undefined` on any node. Properties: - `seriesIndex?: undefined` Always absent: the node belongs to no series. - `valueIndex?: undefined` Always absent: the node stands for no value. ### SeriesContext ```ts interface SeriesContext ``` What every node of a series knows about its series: series groups, points, lines, areas, bars and slices. Properties: - `series: unknown` The series as given in the data (array, or object with `data`). - `seriesIndex: number` The series' index in the chart's data, across layers. - `meta: unknown` The series' `meta`, if it's a series object with one. ### SeriesGroupContext ```ts interface SeriesGroupContext extends SeriesContext ``` The context of a `series` node: the `g` element that holds one series' lines, areas, points, bars or slice. It knows its series, but no value. Properties: - `series: unknown` The series as given in the data (array, or object with `data`). - `seriesIndex: number` The series' index in the chart's data, across layers. - `meta: unknown` The series' `meta`, if it's a series object with one. - `valueIndex?: undefined` Always absent: the group stands for the whole series. ### ValueContext ```ts interface ValueContext ``` What a node of one value knows about it: its index and its value. Properties: - `valueIndex: number` The value's index in its series, from 0. - `value: NormalizedSeriesPrimitiveValue` The normalized value: a number, `{ x, y }` numbers, or a hole. ### PointContext ```ts interface PointContext extends SeriesContext, ValueContext, Point ``` The context of a `point` node: one value of a line chart's series, and where it's drawn. `x` and `y` are the point's position in px in the SVG. Line values are normalized to `{ x, y }` (or a hole), so the value is `ctx.value.y`. Properties: - `x: number` Px from the left edge of the SVG. - `y: number` Px from the top edge of the SVG. - `series: unknown` The series as given in the data (array, or object with `data`). - `seriesIndex: number` The series' index in the chart's data, across layers. - `meta: unknown` The series' `meta`, if it's a series object with one. - `valueIndex: number` The value's index in its series, from 0. - `value: NormalizedSeriesPrimitiveValue` The normalized value: a number, `{ x, y }` numbers, or a hole. ### BarContext ```ts interface BarContext extends SeriesContext, ValueContext ``` The context of a `bar` node: one value of a bar chart's series, and the line it's drawn as, from the base (`x1`, `y1`) to the value (`x2`, `y2`), in px in the SVG. A stacked bar starts where the one below it ends. Bars are clipped to the chart area. Properties: - `series: unknown` The series as given in the data (array, or object with `data`). - `seriesIndex: number` The series' index in the chart's data, across layers. - `meta: unknown` The series' `meta`, if it's a series object with one. - `valueIndex: number` The value's index in its series, from 0. - `value: NormalizedSeriesPrimitiveValue` The normalized value: a number, `{ x, y }` numbers, or a hole. - `horizontal: boolean` Whether the bar is horizontal (`horizontalBars`). - `stacked: boolean` Whether the bar is a segment of a stack: stacked with `accumulate` or `accumulate-relative`, so its base is where the previous segment ends. False for `overlap`, whose bars all start at zero. - `x1: number` The X of the bar's base, in px. - `y1: number` The Y of the bar's base, in px. - `x2: number` The X of the bar's end (the value), in px. - `y2: number` The Y of the bar's end (the value), in px. ### PathContext ```ts interface PathContext extends SeriesContext ``` The context of a `line` or `area` node: the path of a whole series of a line chart, with all its values. Properties: - `series: unknown` The series as given in the data (array, or object with `data`). - `seriesIndex: number` The series' index in the chart's data, across layers. - `meta: unknown` The series' `meta`, if it's a series object with one. - `valueIndex?: undefined` Always absent: the path stands for the whole series. - `values: NormalizedSeries` All values of the series. - `path: SvgPath` The path as drawn (a copy, safe to change). - `smoothing: LineInterpolation` The interpolation the path was drawn with (`lineSmooth`), so the path can be drawn again through other points, e.g. for update animations. ### SliceContext ```ts interface SliceContext extends SeriesContext ``` The context of a `slice` node: a pie or donut slice, which is one series with one value, and its geometry in px and degrees. Properties: - `series: unknown` The series as given in the data (array, or object with `data`). - `seriesIndex: number` The series' index in the chart's data, across layers. - `meta: unknown` The series' `meta`, if it's a series object with one. - `value: number` The slice's value. - `valueIndex: 0` Always 0: a slice's series has one value. - `index: number` Same as `seriesIndex`, as in 1.x draw events. - `totalDataSum: number` The sum of all slices (or the `total` option). - `path: SvgPath` The slice's outline as drawn (a copy, safe to change). - `center: Point` The center of the pie, in px in the SVG. - `radius: number` The pie's radius in px (for a donut, to the middle of its ring). - `startAngle: number` Where the slice starts, in degrees clockwise from the top. - `endAngle: number` Where the slice ends, in degrees clockwise from the top. ### GridContext ```ts interface GridContext extends NoSeries ``` The context of a `grid` node: the grid line of an axis at one tick, across the chart area. Its ends are in px in the SVG. Properties: - `seriesIndex?: undefined` Always absent: the node belongs to no series. - `valueIndex?: undefined` Always absent: the node stands for no value. - `axis: ScaleAxis` The axis the line belongs to; `axis.dimension` is `x` or `y`. - `index: number` The tick's index, in `axis.minorTicks` for a minor grid line. - `minor?: true` `true` for a grid line at one of the axis's `minorTicks`. - `x1: number` The X of the line's start, in px. - `y1: number` The Y of the line's start, in px. - `x2: number` The X of the line's end, in px. - `y2: number` The Y of the line's end, in px. ### AxisLabelContext ```ts interface AxisLabelContext extends NoSeries ``` The context of an axis `label` node: the label of an axis at one tick, and the box it's laid out in, in px in the SVG. Properties: - `seriesIndex?: undefined` Always absent: the node belongs to no series. - `valueIndex?: undefined` Always absent: the node stands for no value. - `kind: 'axis'` `axis` for axis labels; check it to tell them from pie labels. - `axis: ScaleAxis` The axis the label belongs to; `axis.dimension` is `x` or `y`. - `index: number` The tick's index. - `text: Label` The label after `labelInterpolationFnc`. - `x: number` The left edge of the label's box, in px. - `y: number` The top edge of the label's box, in px. - `width: number` The width of the label's box in px. On the X axis, the space to the next tick. - `height: number` The height of the label's box in px. On the Y axis, the space to the next tick. ### PieLabelContext ```ts interface PieLabelContext extends SeriesContext, Point ``` The context of a pie `label` node: a slice's label on a pie or donut. `x` and `y` are where the label is anchored, in px in the SVG. Properties: - `x: number` Px from the left edge of the SVG. - `y: number` Px from the top edge of the SVG. - `series: unknown` The series as given in the data (array, or object with `data`). - `seriesIndex: number` The series' index in the chart's data, across layers. - `meta: unknown` The series' `meta`, if it's a series object with one. - `kind: 'pie'` `pie` for slice labels; check it to tell them from axis labels. - `valueIndex: 0` Always 0, as for the slice. - `index: number` Same as `seriesIndex`, as in 1.x draw events. - `text: string` The label as drawn. ### BandEdge ```ts type BandEdge = { readonly value: number } | { readonly values: readonly (number | undefined)[] } | { readonly seriesIndex: number } ``` An edge of a band: a constant value, the edge's own values (`undefined` for a hole), or the chart-wide index of the series it follows. ### BandContext ```ts interface BandContext extends NoSeries ``` The context of a `band` node: one filled piece of a `band()` layer (the band breaks at holes), and the edges it was drawn from. Properties: - `seriesIndex?: undefined` Always absent: the node belongs to no series. - `valueIndex?: undefined` Always absent: the node stands for no value. - `low?: BandEdge` The low edge, when the band has edges. - `high?: BandEdge` The high edge, when the band has edges. - `series?: readonly number[]` The chart-wide indexes of the series whose minimum and maximum the band covers, when it's drawn from a group. - `path: SvgPath` The piece's outline as drawn (a copy, safe to change). - `smoothing: LineInterpolation` The interpolation the edges were drawn with (`lineSmooth`). ### TextAttrs ```ts interface TextAttrs ``` The attributes of a target label's SVG `text` element. Properties: - `x: number` The X of the anchor, in px. - `y: number` The Y of the text's baseline, in px. - `text-anchor: 'end' | 'start'` `start` or `end`: which end of the text sits at the anchor. ### LineAttrs ```ts interface LineAttrs ``` The attributes of a `line` element: points, bars and grid lines. A point is a line of 0.01 px, which the CSS draws as a dot with a round cap. Properties: - `x1: number` The X of the line's start, in px. - `y1: number` The Y of the line's start, in px. - `x2: number` The X of the line's end, in px. - `y2: number` The Y of the line's end, in px. - `ct:value?: string | number` The value as written to the output, for points and bars. - `ct:meta?: string` The value's `meta`, serialized, for points and bars that have one. ### PathAttrs ```ts interface PathAttrs ``` The attributes of a `path` element: lines, areas and slices. Properties: - `d: string` The path data, as in SVG's `d` attribute. - `ct:value?: string | number` The value as written to the output, for slices. - `ct:meta?: string` The series' `meta`, serialized, for slices that have one. ### SeriesAttrs ```ts interface SeriesAttrs ``` The attributes of a series' `g` element. Properties: - `ct:series-name?: string` The series' `name`, if it has one. - `ct:meta?: string` The series' `meta`, serialized, if it has one (line and bar charts). ### RectAttrs ```ts interface RectAttrs ``` The attributes of a `rect` element: the grid background. Properties: - `x: number` The left edge, in px. - `y: number` The top edge, in px. - `width: number` The width in px. - `height: number` The height in px. ### NodeRoles ```ts interface NodeRoles ``` The roles of scene nodes, and for each its data context (`ctx`) and attributes (`attrs`). Checking `node.role` narrows a [`SceneNode`](https://chartist.dev/api/scene/#SceneNode) to its role, so `ctx` has that role's fields. The built-in layers and axes use the roles below; the class names are their defaults. A plugin that adds nodes with a role of its own declares it here with TypeScript's module augmentation; [`node`](https://chartist.dev/api/scene/#node) and [`findAll`](https://chartist.dev/api/scene/#findAll) then accept it, and its `compose` listeners see it. Properties: - `point: { … }` A value of a line chart: a `line` of 0.01 px (class `ct-point`). - `bar: { … }` A value of a bar chart: a `line` (class `ct-bar`). - `line: { … }` A series' line in a line chart: a `path` (class `ct-line`). - `area: { … }` A series' filled area in a line chart: a `path` (class `ct-area`). - `slice: { … }` A pie or donut slice: a `path` (class `ct-slice-pie` or `ct-slice-donut`). - `series: { … }` The `g` element that holds a series' nodes (class `ct-series`). - `grid: { … }` A grid line of an axis at a tick: a `line` (class `ct-grid`). - `gridBackground: { … }` The `rect` behind the chart area (class `ct-grid-background`). - `label: { … }` An axis label (a `foreignObject` with a `span`, or SVG `text`) or a pie label (SVG `text`). Check `ctx.kind` to tell them apart. - `band: { … }` A filled piece of a `band()` layer: a `path` (class `ct-band-area`). ```ts import { node, type ChartPlugin } from 'chartist'; declare module 'chartist' { interface NodeRoles { marker: { ctx: { readonly seriesIndex: number; readonly label: string }; attrs: { readonly cx: number; readonly cy: number }; }; } } // A marker on the last point of each series. export const markers: ChartPlugin = { name: 'markers', install(chart) { chart.on('compose', current => { if (current.role !== 'series') { return; } const last = current.children.at(-1); if (last?.role !== 'point') { return; } const { x, y, seriesIndex } = last.ctx; return current.append( node('circle', { cx: x, cy: y, r: 6 }, 'ct-marker', { role: 'marker', ctx: { seriesIndex, label: 'last' } }) ); }); } }; ``` ### NodeRole ```ts type NodeRole = keyof NodeRoles ``` A role name, such as `point` or `bar`: a key of [`NodeRoles`](https://chartist.dev/api/scene/#NodeRoles). ### NodeQuery ```ts interface NodeQuery ``` What [`findAll`](https://chartist.dev/api/scene/#findAll) looks for: nodes by role, key and data context fields. A node matches when it matches every field given; an empty query matches every node. Properties: - `role?: keyof NodeRoles` Only nodes of this role, e.g. `bar`. - `key?: string` Only the node with this key, e.g. `0/1/3` (layer, series, value). - `seriesIndex?: number` Only nodes of the series with this index (`ctx.seriesIndex`). - `valueIndex?: number` Only nodes of the value with this index (`ctx.valueIndex`). ## Events and plugins (Chartist API) > The pipeline phases, their events and payloads, custom events, and the plugin shape. ### withOptions ```ts function withOptions(plugin: ChartPlugin, options: TOptions): PluginWithOptions ``` Pairs a plugin with its options for a chart's `plugins`, and checks the options against the plugin's options type. `install` receives them as its second argument. Parameters: - `plugin: ChartPlugin` - `options: TOptions` ```ts import { lineChart, withOptions, type ChartPlugin } from 'chartist'; const lastPoint: ChartPlugin<{ className: string }> = { name: 'last-point', install(chart, options) { chart.on('compose', current => { if (current.role !== 'series') { return; } const last = current.children.at(-1); return last && options ? current.replace(last, last.addClass(options.className)) : undefined; }); } }; lineChart( '#chart', { series: [[1, 3, 2, 5]] }, { plugins: [withOptions(lastPoint, { className: 'ct-last' })] } ); ``` ### ChartEvents ```ts interface ChartEvents ``` The custom events a chart can emit, by name, with their payloads. The core emits only `responsive`. A plugin that emits events of its own declares them here (TypeScript merges your declaration into Chartist's), so `on()`, `off()` and `emit()` check the names and payloads: ```ts declare module 'chartist' { interface ChartEvents { 'legend:click': { readonly seriesIndex: number }; } } ``` `chartist/compat` declares the 1.x events (`draw`, `created`, …). Properties: - `responsive: { … } | { … }` Responsive entries changed which overrides apply: the config before and after (`chart()`), or the options before and after (presets). - `data: DataEvent` 1.x: the chart's data, before the first render (`type: 'initial'`) and after an update with new data (`type: 'update'`). Listeners can change it in place. Needs the [`compat`](https://chartist.dev/api/compat/#compat) plugin. - `optionsChanged: OptionsChangedEvent` 1.x: the responsive options changed, with the options before and after. Needs the [`compat`](https://chartist.dev/api/compat/#compat) plugin. - `draw: PointDrawEvent | LineDrawEvent | AreaDrawEvent | AxesDrawEvent | BarDrawEvent | SliceDrawEvent | SliceLabelDrawEvent` 1.x: an element was drawn: a grid line, label, point, line, area, bar or slice, with its live `element`. Check `type` to narrow it. Needs the [`compat`](https://chartist.dev/api/compat/#compat) plugin. - `created: LineChartCreatedEvent | BarChartCreatedEvent | PieChartCreatedEvent` 1.x: the chart is drawn and in the DOM, after its `draw` events, with the `svg`, the chart area and the axes. Needs the [`compat`](https://chartist.dev/api/compat/#compat) plugin. - `animationBegin: AnimationEvent` 1.x: an SMIL animation began, started with `animate()` on an element of the chart, such as the `element` of a `draw` event. Needs the [`compat`](https://chartist.dev/api/compat/#compat) plugin. - `animationEnd: AnimationEvent` 1.x: an SMIL animation ended, started with `animate()` on an element of the chart. Needs the [`compat`](https://chartist.dev/api/compat/#compat) plugin. ### CustomEventName ```ts type CustomEventName = keyof ChartEvents & string ``` The name of a custom event: a key of [`ChartEvents`](https://chartist.dev/api/events-and-plugins/#ChartEvents). Plugins list the names they emit in `provides`. ### ChartPlugin ```ts interface ChartPlugin ``` A plugin: a name and an `install` function that registers listeners on the chart. Plugins go into `plugins`, in the config of [`chart`](https://chartist.dev/api/chart/#chart) or the options of the presets. They install in order, so with two listeners on the same event, the earlier plugin's runs first. For options, write a function that takes them and returns the plugin, or read them from the second argument of `install` and pass the plugin with [`withOptions`](https://chartist.dev/api/events-and-plugins/#withOptions). Properties: - `name: string` Names the plugin. Prefix your custom events with it to keep them apart. - `provides?: readonly CustomEventName[]` The custom events this plugin emits with `chart.emit()`, each declared in [`ChartEvents`](https://chartist.dev/api/events-and-plugins/#ChartEvents). They're known before any plugin installs, so another plugin may listen to them. - `install: (chart: ChartHandle, options?: TOptions) => unknown` Sets the plugin up: runs once, before the first render, with the chart's handle. Listeners registered here stay for every later render, after updates and resizes too, and `off(name)` without a listener leaves them alone: remove one with its reference. Return a function to undo what `install` set up outside the chart, such as a listener on the document: `destroy()` calls it once, after the chart's output is removed. `detach()` doesn't. - `keyframes?: (previous: SceneNode, next: SceneNode, progress: readonly number[]) => Keyframes | undefined` Gives the keyframes of a node's update animation, for nodes that don't move well number by number, such as a path the plugin draws. A renderer that animates updates (`smilAnimations()` from `chartist/live`) asks it once per update, never per frame, with the progress points of the chart's easing: 0 is the previous node, 1 the new one, and points beyond them come from an easing that overshoots. Plugins are asked in their order, before the layer that drew the node; the first one that returns keyframes wins. ```ts import { barChart, type ChartPlugin } from 'chartist'; // Bars at or above the limit get the class ct-over. const threshold = (limit: number): ChartPlugin => ({ name: 'threshold', install(chart) { chart.on('compose', current => { if (current.role !== 'bar') { return; } const { value } = current.ctx; const y = typeof value === 'number' ? value : value && 'y' in value ? value.y : undefined; return y !== undefined && y >= limit ? current.addClass('ct-over') : undefined; }); } }); barChart('#chart', { series: [[5, 4, 3, 7]] }, { plugins: [threshold(5)] }); ``` ### PluginWithOptions ```ts type PluginWithOptions = readonly [ChartPlugin, TOptions] ``` A plugin paired with its options, as [`withOptions`](https://chartist.dev/api/events-and-plugins/#withOptions) creates it. `install` receives the options as its second argument. ### PluginEntry ```ts type PluginEntry = ChartPlugin | PluginWithOptions ``` An entry of a chart's `plugins`: a plugin, or a plugin with its options. Prefer [`withOptions`](https://chartist.dev/api/events-and-plugins/#withOptions) (it checks the options) or a function that returns a plugin over a bare `[plugin, options]` pair. A 1.x plugin (a plain function) throws; convert it with `fromLegacyPlugin()` from `chartist/compat`. ### ChartData ```ts interface ChartData ``` Chart data as passed to `chart()` or `toScene()`. Properties: - `labels?: readonly (string | number | Date)[]` The labels of the values, e.g. for the X axis or the slices. - `series: readonly unknown[]` The series: arrays of values, series objects with `data`, or, for a pie, one value per slice. ### PreparedSeries ```ts interface PreparedSeries ``` One series after the `prepare` phase. Properties: - `name?: string` The series' `name`, from a series object. - `className?: string` The series' `className`, from a series object. - `meta?: unknown` The series' `meta`, from a series object. - `values: readonly unknown[]` The series' values, as given. ### PreparedData ```ts interface PreparedData ``` The data after the `prepare` phase: every series as a [`PreparedSeries`](https://chartist.dev/api/events-and-plugins/#PreparedSeries), whether it was given as an array or an object. Properties: - `labels: readonly (string | number | Date)[]` The labels as given, or an empty array. - `series: readonly PreparedSeries[]` The series, each with its `values`. - `flat?: boolean` The input was a flat series (e.g. pie or distributed bar data). ### Area ```ts interface Area ``` The drawing area inside the padding (and the axes), in px in the SVG. `y1` is the bottom edge, as in 1.x. Properties: - `x1: number` The left edge. - `y1: number` The bottom edge. - `x2: number` The right edge. - `y2: number` The top edge. - `width: number` The area's width in px. - `height: number` The area's height in px. ### Layout ```ts interface Layout ``` The payload of the `layout` phase: the chart's size and the drawing area inside its padding and axes. A `layout` listener can return a changed layout, e.g. to make room for a legend. Properties: - `width: number` The chart's width in px (at least the left and right padding). - `height: number` The chart's height in px (at least the top and bottom padding). - `padding: ChartPadding` The padding around the drawing area, in px. - `area: Area` Where the layers draw, inside the padding and the axes. - `inset?: ChartPadding` The room layers reserved outside the drawing area with [`Layer.inset`](https://chartist.dev/api/layers/#Layer.inset), in px per side; `padding` includes it. Absent when no layer reserved room. ### NormalizedChartData ```ts interface NormalizedChartData ``` Data normalized for axes: multi values (`{ x, y }`), padded labels. Properties: - `labels: readonly Label[]` The labels, padded with empty strings to the longest series. - `series: readonly NormalizedSeries[]` The series' values as numbers, `{ x, y }` values or holes, each series padded with holes to the longest one. ### InsetContext ```ts interface InsetContext ``` What a layer's `inset` gets: the chart's size and padding, its axes, the layer's series and ways to measure text and styles, so it can size the room it needs. Properties: - `width: number` The chart's width in px. - `height: number` The chart's height in px. - `padding: ChartPadding` The chart's padding option, in px, without any reserved room. - `data: PreparedData` The prepared data; only the layer's series when it selects some. - `axes: readonly AxisDefinition[]` The chart's axis definitions, with their ids, sides and offsets; empty without axes. - `coordinates: Coordinates` The chart's coordinate system. - `layerIndex: number` The layer's index in the chart's `layers`. - `labels: LabelMode` How label layers draw labels: chart option, else the renderer's. - `measureText: MeasureText` Measures text with the renderer's metrics (in the browser, with the page's fonts and the chart's classes). - `measureStyle?: MeasureStyle` Reads computed styles with the renderer. Absent without a DOM. ### ResolvedConfig ```ts interface ResolvedConfig ``` Options after the `resolve` phase: defaults applied, listeners removed. Properties: - `data: ChartData` The chart's data, as given. - `layers: readonly Layer[]` The layers, drawn in this order. - `coordinates: Coordinates` The coordinate system; `cartesian` unless the config says otherwise. - `padding: ChartPadding` The padding in px on every side, the 1.x defaults filled in. - `axes?: CartesianAxes` The chart's axes, from `cartesianAxes()`, if it has any. - `width?: string | number` The root svg's `width` from the config, e.g. `100%` or `600`. - `height?: string | number` The root svg's `height` from the config, e.g. `300px`. - `viewBox?: { … }` Fixed drawing coordinates for the root svg's `viewBox`, in px. - `className?: string` Class names of the root svg, e.g. `ct-chart-line`. - `labels?: LabelMode` Label mode for all label layers; else the renderer decides. - `preset?: LinePresetState | BarPresetState | PiePresetState` The preset that created the chart, if any: its kind, options and data. Check `kind` to narrow it, e.g. `config.preset?.kind === 'line'`. ### PhaseEvents ```ts interface PhaseEvents ``` The payloads of the phase events, by phase, in the order the phases run. Listen with `on` in the config of `chart()`, `chart.on()` or a plugin. A listener can return a new payload to change the phase (see [`PhaseResult`](https://chartist.dev/api/events-and-plugins/#PhaseResult)); later phases build on the change. When the container resizes, the chart runs again from `layout`, so `resolve` and `prepare` listeners aren't called. Properties: - `resolve: { … }` The config with defaults applied: padding, coordinates, layers, axes. - `prepare: { … }` The data, each series as `{ values, name, className, meta }`. - `layout: Layout` The chart's size and the drawing area inside the padding and axes. - `compose: SceneNode` Each node with a role that a layer draws, children before their parents: series, lines, areas, points, bars, slices, grid lines and labels. Return a changed node, a new one, or `null` to drop it. - `scene: { … }` The whole scene, its root the `svg`. - `render: RenderEvent` The renderer has applied the scene: created, replaced or changed the output in place. `output` is what it returned. - `mount: RenderEvent & { … }` The output is in the page; touch elements only here. Return a function to undo what the listener did: it runs before the next render changes the output, and on `destroy()`. ```ts import { barChart, type ChartPlugin } from 'chartist'; // Bars at or above the limit get the class ct-over. const threshold = (limit: number): ChartPlugin => ({ name: 'threshold', install(chart) { chart.on('compose', current => { if (current.role !== 'bar') { return; } // A number, { x, y } (with an X scale axis) or a hole. const { value } = current.ctx; const y = typeof value === 'number' ? value : value && 'y' in value ? value.y : undefined; return y !== undefined && y >= limit ? current.addClass('ct-over') : undefined; }); } }); barChart( '#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [[5, 4, 3, 7]] }, { plugins: [threshold(5)] } ); ``` ### RenderEvent ```ts interface RenderEvent ``` What the `render` and `mount` events carry about a render: the scene, the renderer's output, why the chart rendered and what changed. Properties: - `scene: SceneNode` The scene the renderer applied. - `output: RenderOutput` What the renderer returned. - `cause: RenderCause` Why the chart rendered, e.g. `update` or `resize`. - `previous?: SceneNode` The previous render's scene; absent on the first render and on `hydrate`. - `changes: readonly SceneChange[]` What the render did to the output, in document order. When the renderer changed its output in place (`liveDomRenderer()` from `chartist/live`), these are its changes; otherwise every node of the previous scene is removed and every node of the new one added. A `hydrate` render has an `add` entry for every node, as a first render. - `hydration?: { … }` On a `hydrate` render: how many attributes, classes and texts of the server's markup differed from the scene the browser built, and were set. 0 when the server and the browser drew the same data and options; more means they didn't. Styles, which the markup leaves out, don't count. ### PhaseName ```ts type PhaseName = keyof PhaseEvents ``` The name of a pipeline phase, such as `compose`: a key of [`PhaseEvents`](https://chartist.dev/api/events-and-plugins/#PhaseEvents). ### PhaseResult ```ts type PhaseResult = K extends 'render' ? void : K extends 'mount' ? unknown : PhaseEvents[K] | K extends 'compose' ? null : never | undefined | void ``` What a phase listener returns: a new payload to transform the phase, or nothing to keep it. `compose` listeners may return `null` to drop the node. `render` listeners return nothing (they observe). A function a `mount` listener returns is its cleanup; other values are ignored, so `() => chart.detach()` stays a valid listener. ### PhaseListener ```ts type PhaseListener = (payload: PhaseEvents[K]) => PhaseResult ``` A listener of one phase: gets the phase's payload and may return a new one (see [`PhaseResult`](https://chartist.dev/api/events-and-plugins/#PhaseResult)). Listeners of a phase run in the order they were added, each with the previous one's result. ### AllPhasesListener ```ts type AllPhasesListener = (name: PhaseName, payload: unknown) => void ``` A listener of every phase (`'*'`): gets the phase name and its final payload, after the phase's own listeners. It only observes. ### PhaseListeners ```ts type PhaseListeners = { [K in PhaseName]?: PhaseListener } & { … } ``` Phase listeners by phase name, as the `on` option of `chart()` takes them. They're registered before the first render. Properties: - `*?: AllPhasesListener` Listens to every phase, with the phase name as first argument. ## Rendering (Chartist API) > From scene to output: renderers, text measuring, and rendering on the server. ### toScene ```ts function toScene(config: ChartConfig, size: SceneSize): SceneNode ``` Builds a chart's scene without rendering it: no DOM, timers or globals. Runs the phases `resolve` to `scene` with the config's plugins and listeners. It takes the same config as `chart()`, and the SVG gets the same chart type class without `className`. Listeners of `render` and `mount` never run, responsive overrides and the `renderer` are ignored, and calling `update()`, `detach()` or `destroy()` on the handle a plugin gets throws. Turn the scene into markup with `renderToString()` from `chartist/server`, which re-exports this function. Parameters: - `config: ChartConfig` The chart: data, axes, layers, plugins and listeners - `size: SceneSize` The size to lay out at, in px, and how to measure text Returns: The scene, its root the `svg` ```ts import { lineChartConfig, node, toScene } from 'chartist'; import { renderToString } from 'chartist/server'; const config = lineChartConfig( { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[12, 9, 7, 8, 5]] }, { viewBox: { width: 600, height: 300 }, on: { // Styles inside the SVG, for tools that read it on its own. scene: ({ scene }) => ({ scene: scene.append( node('style').text('.ct-line { fill: none; stroke: #d70206; }'), true ) }) } } ); const scene = toScene(config, { width: 600, height: 300 }); export const markup = renderToString(scene); ``` ### seriesColors ```ts const seriesColors: readonly string[] ``` The default series colors, `a` to `o`, as hex: the colors of `chartist/styles.css` (`$ct-series-colors` in the Sass settings). Renderers without CSS use them to color series the way the styles do. The styles color the first fifteen series only. ```ts import { seriesColors } from 'chartist'; // The color of series `c`, the third. export const third = seriesColors[2]; ``` ### applyAttribute ```ts function applyAttribute(element: Element, name: string, value: string | number): void ``` Sets one scene attribute on an element as the DOM renderer does: names with a known prefix (`ct:value`) in their namespace, and `style` through the CSSOM, which a Content Security Policy allows. For renderers that change elements they keep. Parameters: - `element: Element` The element to change - `name: string` The attribute's name, as in a scene node's `attrs` - `value: string | number` The attribute's value ```ts import { applyAttribute } from 'chartist'; const point = document.querySelector('.ct-point'); if (point) { applyAttribute(point, 'ct:value', 12); applyAttribute(point, 'style', 'stroke-width: 12px;'); } ``` ### renderElement ```ts function renderElement(current: SceneNode, document: Document, options?: RenderElementOptions): Element ``` Creates the element tree of a scene node as the DOM renderer does: SVG elements, HTML elements inside a `foreignObject`, attributes set with [`applyAttribute`](https://chartist.dev/api/rendering/#applyAttribute). For renderers that create elements. Parameters: - `current: SceneNode` The node to create - `document: Document` The document to create the elements in - `options?: RenderElementOptions` The namespace, and a callback for each created element Returns: The node's element, with its children ```ts import { node, renderElement } from 'chartist'; const dot = renderElement(node('circle', { cx: 5, cy: 5, r: 4 }, 'ct-dot'), document); document.querySelector('svg')?.append(dot); ``` ### domRenderer ```ts function domRenderer(): Renderer ``` Creates the renderer `chart()` uses by default: it renders scenes as inline SVG into the container, replacing an earlier Chartist SVG there (as 1.x does) on every render, and measures text and reads styles for layout in the container, so its CSS applies. It ignores the previous render it gets from a chart's second render on; `liveDomRenderer()` from `chartist/live` keeps its SVG and changes it instead. Wrap it to change what every render puts on the page. ```ts import { chart, domRenderer, pie, type Renderer } from 'chartist'; const dom = domRenderer(); // The DOM renderer, with a name for screen readers on every render. const renderer: Renderer = { ...dom, render(scene, container) { const output = dom.render(scene, container); output.svg.setAttribute('role', 'img'); output.svg.setAttribute('aria-label', 'Monthly budget'); return output; } }; chart('#chart', { data: { series: [50, 30, 20] }, coordinates: 'polar', layers: [pie()], renderer }); ``` ### textAscent ```ts const textAscent: 0.85 ``` Where SVG text labels put a line's baseline: this share of the font size below the top of the line (a typical font's ascent). Lines are one font size apart, so a renderer finds a line's vertical center at `y - (textAscent - 0.5) * fontSize` from its `y`. ### estimateText ```ts const estimateText: MeasureText ``` Estimates text size without a DOM: an average glyph is 0.6 em wide, and a line is 1.2 em high, at `fontSize` or else 12 px. `toScene()` measures with it unless you pass your own, which can build on it. ```ts import { estimateText, type MeasureText } from 'chartist'; // Bold labels: a tenth wider than the estimate. export const measureText: MeasureText = (text, options) => { const size = estimateText(text, options); return { ...size, width: size.width * 1.1 }; }; ``` ### SceneSize ```ts interface SceneSize ``` The size [`toScene`](https://chartist.dev/api/rendering/#toScene) and `renderForHydration()` lay a chart out at, since there's no container to measure, and how to measure text there. Properties: - `width: number` The width to lay out at, in px. In `toScene()`, the root svg gets it as its `width`, unless the config sets one. - `height: number` The height to lay out at, in px. In `toScene()`, the root svg gets it as its `height`, unless the config sets one. - `measureText?: MeasureText` Text measurement for layout; an estimate by default. - `labels?: LabelMode` Label mode when the config sets none. `svg` by default in `toScene()`, since string output often goes to tools without HTML support; `html` in `renderForHydration()`, as `chart()` draws them. ### DomRenderOutput ```ts interface DomRenderOutput extends RenderOutput ``` What [`domRenderer`](https://chartist.dev/api/rendering/#domRenderer) returns from `render()`: the inline SVG it added to the container. Properties: - `svg: SVGSVGElement` The chart's `svg` element, in the container. - `elementOf: (node: SceneNode) => Element | undefined` The rendered element of a scene node of this render. - `remove: () => void` Takes the SVG out of the container again. - `patched?: boolean` `true` when the renderer changed its previous output in place with the `update` it got, instead of replacing it. Then `changes` says what it did. - `changes?: readonly SceneChange[]` What the renderer did to each changed node, with `patched`. Without them, the chart describes the render as a replacement. - `animation?: UpdateAnimation` The timing of the update animation the renderer started, if any. The `mount` event passes it on as `animation`. ### RenderElementOptions ```ts interface RenderElementOptions ``` What [`renderElement`](https://chartist.dev/api/rendering/#renderElement) takes besides the node and the document. Properties: - `html?: boolean` Creates the elements in the XHTML namespace, as inside a `foreignObject`. - `created?: (node: SceneNode, element: Element) => void` Called for every node and the element created for it. ### TextMetrics ```ts interface TextMetrics ``` The size of a measured text, as a [`MeasureText`](https://chartist.dev/api/rendering/#MeasureText) function returns it. Properties: - `width: number` The text's width in px. - `height: number` The text's height in px: one line. - `fontSize?: number` The font size in px the text was measured with, when known. ### LabelMode ```ts type LabelMode = 'html' | 'svg' ``` How labels are drawn: HTML in a `foreignObject` (1.x) or SVG `text`. ### MeasureTextOptions ```ts interface MeasureTextOptions ``` What a [`MeasureText`](https://chartist.dev/api/rendering/#MeasureText) function gets with the text to measure. Properties: - `classes?: readonly string[]` Class names of the text, for renderers that apply CSS. - `fontSize?: number` Font size in px. Without it, the browser measures with the CSS font size and the estimate uses 12, the size of 1.x `.ct-label`. - `rootClass?: string` Classes of the chart's root svg, so chart-type CSS applies. - `container?: Element` Where to measure (the chart container), so its CSS applies. ### MeasureText ```ts type MeasureText = (text: string, options?: MeasureTextOptions) => TextMetrics ``` Measures how much space a text takes, in px, so labels can be wrapped and aligned. In the browser, Chartist measures with the page's fonts; `toScene()` estimates (0.6 em per character, 1.2 em per line) unless you pass your own in its size. ```ts import type { MeasureText } from 'chartist'; // Wider glyphs than the estimate, for a wide font. export const measureText: MeasureText = (text, options = {}) => { const fontSize = options.fontSize ?? 12; return { width: text.length * fontSize * 0.7, height: fontSize * 1.25 }; }; ``` ### MeasureStyleOptions ```ts interface MeasureStyleOptions ``` What a [`MeasureStyle`](https://chartist.dev/api/rendering/#MeasureStyle) function gets with the property to read. Properties: - `classes?: readonly string[]` Class names of the element whose style is read. - `rootClass?: string` Classes of the chart's root svg, so chart-type CSS applies. - `container?: Element` Where to measure (the chart container), so its CSS applies. ### MeasureStyle ```ts type MeasureStyle = (property: string, options?: MeasureStyleOptions) => string | undefined ``` Reads a computed CSS property of an SVG `path` with the given classes, for values that the stylesheet decides and layout needs: the pie layer reads the `stroke-width` of a donut slice to fit the ring. Returns `undefined` when it can't tell. In the browser, Chartist reads it in the chart's container; `toScene()` has no stylesheet, so layout uses its defaults there. ```ts import { domRenderer, type Renderer } from 'chartist'; // The DOM renderer, but donuts are laid out for 30px rings, whatever // the stylesheet says. Pass it as `renderer` to `chart()`. export const renderer: Renderer = { ...domRenderer(), measureStyle: property => (property === 'stroke-width' ? '30px' : undefined) }; ``` ### RenderCause ```ts type RenderCause = 'create' | 'hydrate' | 'update' | 'resize' | 'responsive' | 'fonts' ``` Why a chart rendered: `create` for its first render, `hydrate` for the first render of a chart that took server markup over (see `renderForHydration()`), `update` for `update()` (and the presets' `setOptions()`), `resize` when its container's size changed, `responsive` when its matching `responsive` entries changed, `fonts` when web fonts finished loading. ### SceneChange ```ts interface SceneChange ``` What a render did to one node of the output: `add` for a node the previous scene didn't have, `update` for a node whose attributes, classes or text changed (with its `previous` node), `remove` for a node of the previous scene that's gone. Properties: - `op: 'remove' | 'update' | 'add'` What happened to the node. - `node: SceneNode` The node: of the new scene, or of the previous one for `remove`. - `previous?: SceneNode` The node in the previous scene, for `update`. ### Keyframes ```ts type Keyframes = readonly Readonly>[] ``` The keyframes of a node's update animation: one set of attributes per progress point, in the order of the points. ### UpdateAnimation ```ts interface UpdateAnimation ``` The timing of a render's update animation: its duration in ms and its easing as a CSS easing, such as `ease-out`. Properties: - `duration: number` The duration in ms. - `easing: string` The CSS easing, such as `ease-out` or `cubic-bezier(0.3, 0, 0.2, 1)`. ### RenderUpdate ```ts interface RenderUpdate ``` What a renderer gets about the previous render, from a chart's second render on. A renderer can change its previous output in place with it, instead of replacing it; renderers keep no state themselves. Properties: - `previous: TOutput` What this renderer returned for the previous render. - `previousScene: SceneNode` The previous render's scene. - `cause: RenderCause` Why the chart renders. - `labels: readonly unknown[]` The data's labels, for keys from labels. - `previousLabels: readonly unknown[]` The previous render's labels, for keys from labels in the previous scene. - `keyframes: (previous: SceneNode, next: SceneNode, progress: readonly number[]) => Keyframes | undefined` Asks the chart's plugins, then the layer that drew the node, for the keyframes of a node's update at the given progress points (0 is the previous node, 1 the new one; points may lie beyond them when the easing overshoots). Returns nothing when none of them has any. ### RenderOutput ```ts interface RenderOutput ``` What a renderer returns from `render()`. The `render` and `mount` phase events pass it on as `output`. Properties: - `elementOf?: (node: SceneNode) => Element | undefined` The rendered element of a scene node, if the output is DOM. - `remove?: () => void` Removes the output again. - `patched?: boolean` `true` when the renderer changed its previous output in place with the `update` it got, instead of replacing it. Then `changes` says what it did. - `changes?: readonly SceneChange[]` What the renderer did to each changed node, with `patched`. Without them, the chart describes the render as a replacement. - `animation?: UpdateAnimation` The timing of the update animation the renderer started, if any. The `mount` event passes it on as `animation`. ### Renderer ```ts interface Renderer ``` Turns a scene into output. `chart()` renders inline SVG into its container with the DOM renderer, unless its config sets a `renderer`. To get markup instead, without a DOM, use `toScene()` and `renderToString()` from `chartist/server`. A renderer that wraps another one keeps its `adopt` only if it passes it on, as spreading the wrapped renderer does. Properties: - `render: (scene: SceneNode, target: Element, update?: RenderUpdate) => TOutput` Renders a scene into the chart's container, after the `scene` phase and on every update and resize. From the second render on, it gets the previous render as `update`: a renderer that changes its previous output in place returns `patched: true` and its `changes`; one that ignores `update` replaces its output, as the DOM renderer does. - `measureText?: MeasureText` Measures text for layout, in the container so its CSS applies; without it, Chartist estimates text sizes. - `measureStyle?: MeasureStyle` Reads computed styles for layout, in the container so its CSS applies; without it, layout uses its defaults (a donut's ring is laid out for 60 px). - `labels?: LabelMode` The default label mode of this renderer; `html` if omitted. - `adopt?: (container: Element, replay: (layout: { readonly width: number; readonly height: number; readonly labels: LabelMode }) => SceneNode) => TOutput & { readonly repaired: number } | undefined` Takes over markup from `renderForHydration()` in the chart's container, instead of rendering: finds it and reads the layout its marker records, gets the scene for that layout from `replay`, binds the scene's nodes to the markup's elements and makes them what `render()` would draw. A chart calls it once, before its first render, unless it always draws a new svg; later renders go through `render()`, with the adopted output as the previous one. Returns nothing when there's no markup to take over or it doesn't match the scene, and the chart then renders as usual. `liveDomRenderer()` from `chartist/live` has it; the DOM renderer doesn't, since it replaces its svg on every render. ## Responsive (Chartist API) > Options that change with media queries or with the size of the chart’s container. ### container *experimental* ```ts function container(query: string): ContainerCondition ``` Marks a responsive entry as a container query, evaluated against the chart container or an ancestor with a CSS `container-type`. Needs `Element.matchContainer()`; until browsers ship it, load the `match-container` polyfill. Without it, Chartist warns once and ignores container entries; in `toScene()`, they never match. Works in the responsive entries of `chart()` and of the presets. Parameters: - `query: string` The condition you'd write after `@container` in CSS ```ts import 'match-container'; import { container, lineChart } from 'chartist'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[12, 9, 7, 8, 5]] }, { low: 0 }, [ [ container('(width < 400px)'), { showPoint: false, axisX: { labelTransform: value => String(value)[0] } } ] ] ); ``` ### responsive ```ts function responsive(entries: readonly ResponsiveEntry[]): ResponsiveSet ``` Responsive overrides for `chart()`: `[condition, overrides]` entries with a media query string or a `container()` condition. Matching overrides are shallow-merged over the config in order, so later entries win and an override replaces a whole option (e.g. all `layers`). When the matching entries change, the chart emits a `responsive` event with the config before and after, and renders again. Parameters: - `entries: readonly ResponsiveEntry[]` `[condition, overrides]` pairs; the overrides are a partial config: padding, axes, layers, size and the like ```ts import { autoScaleAxis, axisGrid, axisLabels, cartesianAxes, chart, line, responsive, stepAxis } from 'chartist'; const labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']; chart('#chart', { data: { labels, series: [[12, 9, 7, 8, 5]] }, axes: cartesianAxes([ { id: 'x', side: 'bottom', type: stepAxis(), ticks: labels }, { id: 'y', side: 'left', type: autoScaleAxis(), low: 0 } ]), layers: [axisGrid(), line({ showArea: true }), axisLabels()], responsive: responsive([ [ '(max-width: 640px)', { padding: 5, layers: [axisGrid(), line({ showPoint: false }), axisLabels()] } ] ]) }); ``` ### ContainerCondition *experimental* ```ts interface ContainerCondition ``` A container query condition for a responsive entry, created with [`container`](https://chartist.dev/api/responsive/#container). It rests on `Element.matchContainer()`, a draft standard (w3c/csswg-drafts#6205), through a polyfill: it may still change in a 2.x minor. Properties: - `type: 'container'` Always `container`: tells the condition from a media query string. - `query: string` A `@container` condition, e.g. `(width < 400px)`. ### ResponsiveCondition ```ts type ResponsiveCondition = string | ContainerCondition ``` When a responsive entry applies: a window media query string, such as `(max-width: 640px)` (as in 1.x), or a [`container`](https://chartist.dev/api/responsive/#container) condition. Media queries are matched in the window of the chart's container, so a chart in an iframe follows the iframe's viewport. ### ResponsiveEntry ```ts type ResponsiveEntry = readonly [ResponsiveCondition, T] ``` A responsive entry: a condition and the overrides that apply while it matches, e.g. `['(max-width: 640px)', { showPoint: false }]`. Matching entries apply in order, so later ones win. ### ResponsiveWatcher ```ts interface ResponsiveWatcher ``` Watches the conditions of a [`ResponsiveSet`](https://chartist.dev/api/responsive/#ResponsiveSet)'s entries, as its `watch()` returns it: which entries match, and when that changes. Properties: - `matching: readonly number[]` Indexes of the matching entries, as of the last `check()`. - `check: () => boolean` Re-reads all conditions; true if the matching set changed. - `stop: () => void` Stops watching: no more `onChange` calls, and no listeners left. ### ResponsiveSet ```ts interface ResponsiveSet ``` Responsive entries for `chart()`, with the watcher that evaluates them. Created by `responsive()`, so charts without responsive entries don't bundle the watcher. Properties: - `entries: readonly ResponsiveEntry[]` The entries as given to `responsive()`. - `watch: (host: Element | undefined, onChange: () => void) => ResponsiveWatcher` Starts watching the entries' conditions; `chart()` calls it. Calls `onChange` when the set of matching entries changes. ## Data (Chartist API) > The shapes of chart data: series, values, labels and meta, as given and as normalized. ### Meta ```ts type Meta = any ``` Anything you attach to a series or value as `meta`, such as a tooltip text. Chartist doesn't read it: it hands it back in events and writes it to the `ct:meta` attribute. ### Multi ```ts type Multi = { x: number | string | Date | null; y: number | string | Date | null } | { x: number | string | Date | null } | { y: number | string | Date | null } ``` A value with coordinates: `{ x, y }`, or only `x` or `y`. Line charts draw `{ x, y }` values on two scale axes (scatter plots). ### NormalizedMulti ```ts type NormalizedMulti = { x: number; y: number } | { x: number } | { y: number } ``` A [`Multi`](https://chartist.dev/api/data/#Multi) value after normalizing: its coordinates as numbers. ### Label ```ts type Label = string | number | Date ``` A label of an axis step or a pie slice. ### AllSeriesTypes ```ts type AllSeriesTypes = FlatSeries | (Series | SeriesObject)[] ``` Every shape `series` can take: a [`FlatSeries`](https://chartist.dev/api/data/#FlatSeries) (pies, distributed bars), or a list of [`Series`](https://chartist.dev/api/data/#Series) arrays and [`SeriesObject`](https://chartist.dev/api/data/#SeriesObject)s. ### Data ```ts interface Data ``` Chart data: the series, and labels for the positions of their values (or for the slices of a pie). Properties: - `labels?: Label[]` One label per value position: the steps of the X axis in line and bar charts, the slices in pie charts. - `series: T` The values: an array per series (or a series object with a name, class name and meta), or one flat array for pies and distributed bars. ### Series ```ts type Series = SeriesValue[] ``` A series as an array of values: `[5, 2, 4]`. Use a [`SeriesObject`](https://chartist.dev/api/data/#SeriesObject) to give it a name, class name or meta. ### SeriesObject ```ts interface SeriesObject ``` A series with a name, class name or meta: `{ name: 'Sales', data: [5, 2, 4] }`. Properties: - `name?: string` The series' name. Line charts look up per-series options in `series` by it. - `className?: string` A class name for the series' group, instead of `ct-series-a`, `-b`, … - `meta?: any` Anything to attach to the series, handed back in events. - `data: SeriesValue[]` The series' values. ### SeriesValue ```ts type SeriesValue = SeriesObjectValue | T ``` A value of a series: a plain value, or `{ value, meta }`. ### SeriesPrimitiveValue ```ts type SeriesPrimitiveValue = number | string | boolean | Date | Multi | null | undefined ``` A plain value of a series: a number (numeric strings are read as numbers), a [`Multi`](https://chartist.dev/api/data/#Multi) `{ x, y }`, or `null` or `undefined` for a hole. ### SeriesObjectValue ```ts interface SeriesObjectValue ``` A value with meta: `{ value: 5, meta: 'Tuesday' }`. Properties: - `meta?: any` Anything to attach to the value, handed back in events. - `value: T` The value. ### FlatSeries ```ts type FlatSeries = FlatSeriesValue[] ``` One flat array of values, one per slice or bar: the series of a pie chart, or of a bar chart with `distributeSeries`. ### FlatSeriesValue ```ts type FlatSeriesValue = SeriesValue | FlatSeriesObjectValue ``` A value of a [`FlatSeries`](https://chartist.dev/api/data/#FlatSeries): a plain value, `{ value, meta }`, or a [`FlatSeriesObjectValue`](https://chartist.dev/api/data/#FlatSeriesObjectValue) with a name and class name for its slice. ### FlatSeriesPrimitiveValue ```ts type FlatSeriesPrimitiveValue = number | string | null | undefined ``` A plain value of a [`FlatSeries`](https://chartist.dev/api/data/#FlatSeries): a number (numeric strings are read as numbers), or `null` or `undefined` for a hole. ### FlatSeriesObjectValue ```ts interface FlatSeriesObjectValue ``` A value of a flat series that is its own series, such as a pie slice: `{ value: 20, name: 'Rent', className: 'rent' }`. Properties: - `name?: string` The name of the value's series. - `className?: string` A class name for the value's series group, such as a slice's. - `meta?: any` Anything to attach to the value, handed back in events. - `value: T` The value. ### AllNormalizedSeriesTypes ```ts type AllNormalizedSeriesTypes = NormalizedFlatSeries | NormalizedSeries[] ``` Every shape a normalized `series` can take: a [`NormalizedFlatSeries`](https://chartist.dev/api/data/#NormalizedFlatSeries) or a list of [`NormalizedSeries`](https://chartist.dev/api/data/#NormalizedSeries). ### NormalizedData ```ts interface NormalizedData extends Data ``` Chart data after [`normalizeData`](https://chartist.dev/api/helpers/#normalizeData): labels filled in, and every series an array of numbers, `{ x, y }` numbers or holes. Properties: - `labels: Label[]` One label per value position, empty strings where none was given. - `series: T` The normalized series. ### NormalizedSeries ```ts type NormalizedSeries = NormalizedSeriesValue[] ``` A series after normalizing: its values without meta or series object. ### NormalizedSeriesValue ```ts type NormalizedSeriesValue = NormalizedSeriesPrimitiveValue ``` A normalized value: a number, `{ x, y }` numbers, or `undefined` for a hole. ### NormalizedSeriesPrimitiveValue ```ts type NormalizedSeriesPrimitiveValue = number | NormalizedMulti | undefined ``` A normalized value: a number, `{ x, y }` numbers, or `undefined` for a hole. ### NormalizedFlatSeries ```ts type NormalizedFlatSeries = number[] ``` A [`FlatSeries`](https://chartist.dev/api/data/#FlatSeries) after normalizing: one number per slice or bar. ## SVG and animation (Chartist API) > The small SVG toolkit behind the charts: elements, paths, SMIL animations, easings and line interpolation. ### Interpolation ```ts namespace Interpolation ``` The line interpolations: functions that turn a series' points into the [`SvgPath`](https://chartist.dev/api/svg/#SvgPath) of its line, for the `lineSmooth` option of line charts. Each one is a factory: call it with its options, such as `Interpolation.cardinal({ tension: 0.2 })`, and pass the result. Write your own as a [`LineInterpolation`](https://chartist.dev/api/classes/#LineInterpolation). - `CardinalInterpolationOptions` The options of [`Interpolation.cardinal`](https://chartist.dev/api/svg/#Interpolation.cardinal). - `cardinal(options?: CardinalInterpolationOptions): (pathCoordinates: number[], valueData: SegmentData[]) => SvgPath` Smooths a line with a cardinal (Catmull-Rom) spline: round curves that go through every point. When values change quickly, the curve can swing past them; [`Interpolation.monotoneCubic`](https://chartist.dev/api/svg/#Interpolation.monotoneCubic) (the default for `lineSmooth: true`) doesn't. With two points or fewer, the line is straight, as with [`Interpolation.none`](https://chartist.dev/api/svg/#Interpolation.none). - `MonotoneCubicInterpolationOptions` The options of [`Interpolation.monotoneCubic`](https://chartist.dev/api/svg/#Interpolation.monotoneCubic). - `monotoneCubic(options?: MonotoneCubicInterpolationOptions): (pathCoordinates: number[], valueData: SegmentData[]) => SvgPath` Smooths a line with a monotone cubic spline: a smooth curve through every point that never swings above or below the values around it. It's what `lineSmooth: true` uses. The x values have to keep one direction, increasing or decreasing: where an x value repeats or turns back, the line breaks. With two points or fewer, the line is straight, as with [`Interpolation.none`](https://chartist.dev/api/svg/#Interpolation.none). - `NoneInterpolationOptions` The options of [`Interpolation.none`](https://chartist.dev/api/svg/#Interpolation.none). - `none(options?: NoneInterpolationOptions): (pathCoordinates: number[], valueData: SegmentData[]) => SvgPath` Draws a line as straight segments from point to point, without smoothing. It's what `lineSmooth: false` uses. - `SimpleInteractionOptions` The options of [`Interpolation.simple`](https://chartist.dev/api/svg/#Interpolation.simple). - `simple(options?: SimpleInteractionOptions): (pathCoordinates: number[], valueData: SegmentData[]) => SvgPath` Smooths a line with curves whose handles are horizontal: it goes through every point and never swings above or below them, but flows less than [`Interpolation.cardinal`](https://chartist.dev/api/svg/#Interpolation.cardinal). `divisor` sets how round it is. - `StepInterpolationOptions` The options of [`Interpolation.step`](https://chartist.dev/api/svg/#Interpolation.step). - `step(options?: StepInterpolationOptions): (pathCoordinates: number[], valueData: SegmentData[]) => SvgPath` Draws a line in steps: horizontal and vertical segments instead of diagonals or curves. Each step adds a corner to the path, and with `showPoint` a point is drawn there too. ### easings ```ts const easings: { easeInSine: number[]; easeOutSine: number[]; easeInOutSine: number[]; easeInQuad: number[]; easeOutQuad: number[]; easeInOutQuad: number[]; easeInCubic: number[]; easeOutCubic: number[]; easeInOutCubic: number[]; easeInQuart: number[]; easeOutQuart: number[]; easeInOutQuart: number[]; easeInQuint: number[]; easeOutQuint: number[]; easeInOutQuint: number[]; easeInExpo: number[]; easeOutExpo: number[]; easeInOutExpo: number[]; easeInCirc: number[]; easeOutCirc: number[]; easeInOutCirc: number[]; easeInBack: number[]; easeOutBack: number[]; easeInOutBack: number[] } ``` The named easing curves for [`Svg.animate`](https://chartist.dev/api/svg/#Svg.animate), from `easeInSine` to `easeInOutBack`: each the four numbers of a cubic Bézier curve. Name one as an animation's `easing`, or pass four numbers of your own, such as `easing: [0.25, 0.1, 0.25, 1]`. ```ts import { lineChart, findAll, Svg, type ChartPlugin } from 'chartist'; // Points drop in from the top and bounce a little at the end. const dropIn: ChartPlugin = { name: 'drop-in', install(chart) { chart.on('mount', ({ scene, elementOf }) => { for (const point of findAll(scene, { role: 'point' })) { const element = elementOf(point); const { y } = point.ctx; if (element) { new Svg(element).animate({ y1: { dur: 600, from: 0, to: y, easing: 'easeOutBack' }, y2: { dur: 600, from: 0, to: y, easing: 'easeOutBack' } }); } } }); } }; lineChart('#chart', { series: [[5, 2, 4, 3]] }, { plugins: [dropIn] }); ``` ### keySplinesOf ```ts function keySplinesOf(easing: string | readonly number[]): string | undefined ``` The SMIL `keySplines` of an easing SMIL can express: a cubic Bézier curve whose four numbers lie within 0 and 1 (the CSS keywords, many `cubic-bezier()` curves, most names of [`easings`](https://chartist.dev/api/svg/#easings)). Browsers ignore an animation whose `keySplines` leave that range, so for other easings it returns nothing: sample them with [`sampleEasing`](https://chartist.dev/api/svg/#sampleEasing) instead. Parameters: - `easing: string | readonly number[]` A CSS easing, a name of [`easings`](https://chartist.dev/api/svg/#easings), or four numbers Returns: The `keySplines` value, or nothing ```ts import { keySplinesOf } from 'chartist'; keySplinesOf('ease-out'); // '0 0 0.58 1' keySplinesOf('easeOutBack'); // undefined: it overshoots ``` ### sampleEasing ```ts function sampleEasing(easing: string | readonly number[], duration: number): number[] ``` The eased progress of an animation at evenly spaced times, for SMIL `values` with even `keyTimes`: 12 points, and one per 50 ms for animations longer than 600 ms, at most 40. It takes the CSS easings (the keywords, `cubic-bezier()` curves, also ones that overshoot, `linear()` and `steps()`), the names of [`easings`](https://chartist.dev/api/svg/#easings) and four numbers of a cubic Bézier curve; anything else is sampled as `ease`. Points lie below 0 or above 1 where the easing overshoots. Parameters: - `easing: string | readonly number[]` The easing - `duration: number` The animation's duration in ms Returns: The progress at each point, from 0 to 1 ```ts import { sampleEasing } from 'chartist'; // A value that grows from 10 to 20 and overshoots a little on the way. const progress = sampleEasing('cubic-bezier(0.34, 1.56, 0.64, 1)', 400); const values = progress.map(p => 10 + (20 - 10) * p).join(';'); ``` ### Svg ```ts class Svg ``` Wraps an SVG element for chaining: set attributes and classes, add child elements, and animate attributes with SMIL. Wrap an element of a rendered chart (from `elementOf` in a `mount` listener), or create a new one by name. Members: - `Easing: { … }` The easing curves for [`Svg.animate`](https://chartist.dev/api/svg/#Svg.animate), as 1.x had them on the class. - `attr: (attributes: string, ns?: string) => string | null` Set attributes on the current SVG element of the wrapper you're currently working on. - `elem: (name: string, attributes?: Attributes, className?: string, insertFirst?: boolean) => Svg` Create a new SVG element whose wrapper object will be selected for further operations. This way you can also create nested groups easily. - `parent: () => Svg | null` Wraps the element's parent. - `root: () => Svg` This method returns a Svg wrapper around the root SVG element of the current tree. - `querySelector: (selector: string) => Svg | null` Find the first child SVG element of the current element that matches a CSS selector. The returned object is a Svg wrapper. - `querySelectorAll: (selector: string) => SvgList` Finds all descendants of the element that match a CSS selector. - `getNode: () => T` Returns the underlying SVG node for the current element. - `foreignObject: (content: string | Node, attributes?: Attributes, className?: string, insertFirst?: boolean) => Svg` This method creates a foreignObject (see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/foreignObject) that allows to embed HTML content into a SVG graphic. With the help of foreignObjects you can enable the usage of regular HTML elements inside of SVG where they are subject for SVG positioning and transformation but the Browser will use the HTML rendering capabilities for the containing DOM. - `text: (t: string) => Svg` This method adds a new text element to the current Svg wrapper. - `empty: () => Svg` This method will clear all child nodes of the current wrapper object. - `remove: () => Svg | null` Removes the element from its parent. - `replace: (newElement: Svg) => Svg` This method will replace the element with a new element that can be created outside of the current DOM. - `append: (element: Svg, insertFirst?: boolean) => Svg` This method will append an element to the current element as a child. - `classes: () => string[]` Returns an array of class names that are attached to the current wrapper element. This method can not be chained further. - `addClass: (names: string) => Svg` Adds one or a space separated list of classes to the current element and ensures the classes are only existing once. - `removeClass: (names: string) => Svg` Removes one or a space separated list of classes from the current element. - `removeAllClasses: () => Svg` Removes all classes from the current element. - `height: () => number` Get element height using `clientHeight` - `width: () => number` Get element width using `clientWidth` - `animate: (animations: Record, guided?: boolean, eventEmitter?: EventEmitter) => Svg` Animates attributes of the element with SMIL: adds an `` element per attribute. Each [`AnimationDefinition`](https://chartist.dev/api/svg/#AnimationDefinition) holds SMIL attributes (usually `begin`, `dur`, `from` and `to`, see https://www.w3.org/TR/SVG11/animate.html), plus an optional `easing`: a name from [`easings`](https://chartist.dev/api/svg/#easings) or four numbers of a cubic Bézier curve. An animations object could look like this: ```ts element.animate({ opacity: { dur: 1000, from: 0, to: 1 }, x1: { dur: '1000ms', from: 100, to: 200, easing: 'easeOutQuart' }, y1: { dur: '2s', from: 0, to: 100 } }); ``` `dur` and `begin` given as numbers are milliseconds. **Guided mode** (the default) makes one-off animations behave: the attribute is set to `from` right away, even before a delayed start; the animation starts after `begin` ms (a timeout, not the document's timeline); and when it ends, the attribute keeps `to` and the `` element is removed. Pass `guided: false` to write the `` elements as given. An attribute with an array of definitions never runs guided. ```ts import { barChart, findAll, Svg, type ChartPlugin } from 'chartist'; // Bars grow from the axis, animated from a plugin's mount listener. const growBars: ChartPlugin = { name: 'grow-bars', install(chart) { chart.on('mount', ({ scene, elementOf }) => { for (const bar of findAll(scene, { role: 'bar' })) { const element = elementOf(bar); if (element) { new Svg(element).animate({ y2: { begin: bar.ctx.valueIndex * 120, dur: 700, from: bar.attrs.y1, to: bar.attrs.y2, easing: 'easeOutQuint' } }); } } }); } }; barChart('#chart', { series: [[5, 4, 3, 7]] }, { plugins: [growBars] }); ``` ### SvgList ```ts class SvgList ``` A list of [`Svg`](https://chartist.dev/api/svg/#Svg) wrappers that calls an `Svg` method on all of them at once, such as `addClass` or `animate`. Each method returns the list, for chaining. [`Svg.querySelectorAll`](https://chartist.dev/api/svg/#Svg.querySelectorAll) returns one. Members: - `attr: (...args: [attributes: Attributes]) => SvgList` Sets attributes on every element, as [`Svg.attr`](https://chartist.dev/api/svg/#Svg.attr) does. - `elem: (...args: [name: string, attributes?: Attributes, className?: string, insertFirst: boolean]) => SvgList` Adds a child element to every element, as [`Svg.elem`](https://chartist.dev/api/svg/#Svg.elem) does. - `root: (...args: []) => SvgList` Calls [`Svg.root`](https://chartist.dev/api/svg/#Svg.root) on every element; returns the list, not the roots. - `getNode: (...args: []) => SvgList` Calls [`Svg.getNode`](https://chartist.dev/api/svg/#Svg.getNode) on every element; returns the list, not the nodes. - `foreignObject: (...args: [content: string | Node, attributes?: Attributes, className?: string, insertFirst: boolean]) => SvgList` Adds a `foreignObject` with the content to every element, as [`Svg.foreignObject`](https://chartist.dev/api/svg/#Svg.foreignObject) does. - `text: (...args: [t: string]) => SvgList` Adds a text node to every element, as [`Svg.text`](https://chartist.dev/api/svg/#Svg.text) does. - `empty: (...args: []) => SvgList` Removes the children of every element, as [`Svg.empty`](https://chartist.dev/api/svg/#Svg.empty) does. - `remove: (...args: []) => SvgList` Removes every element from its parent, as [`Svg.remove`](https://chartist.dev/api/svg/#Svg.remove) does. - `addClass: (...args: [names: string]) => SvgList` Adds classes to every element, as [`Svg.addClass`](https://chartist.dev/api/svg/#Svg.addClass) does. - `removeClass: (...args: [names: string]) => SvgList` Removes classes from every element, as [`Svg.removeClass`](https://chartist.dev/api/svg/#Svg.removeClass) does. - `removeAllClasses: (...args: []) => SvgList` Removes all classes from every element, as [`Svg.removeAllClasses`](https://chartist.dev/api/svg/#Svg.removeAllClasses) does. - `animate: (...args: [animations: Record, guided: boolean, eventEmitter?: EventEmitter]) => SvgList` Animates attributes of every element with SMIL, as [`Svg.animate`](https://chartist.dev/api/svg/#Svg.animate) does. ### SvgPath ```ts class SvgPath ``` Builds and changes the `d` of an SVG `path`: move, line, curve and arc commands, with a cursor where new commands go. Lines, areas and slices are `SvgPath`s, and interpolations such as [`Interpolation.cardinal`](https://chartist.dev/api/svg/#Interpolation.cardinal) return one. Each command can carry the data of its point, which the chart reads back for its points. Members: - `join: (paths: SvgPath[], close?: boolean, options?: SvgPathOptions) => SvgPath` Joins paths into a new path, in the order given. The new path gets copies of the commands, and its cursor is at the end. - `pathElements: PathCommand[]` The path's commands, in order: each with its letter, its coordinates and the data of its point. - `position: (pos: number) => this` Gets or sets the current position (cursor) inside of the path. You can move around the cursor freely but limited to 0 or the count of existing elements. All modifications with element functions will insert new elements at the position of this cursor. - `remove: (count: number) => SvgPath` Removes elements from the path starting at the current position. - `move: (x: number, y: number, relative?: boolean, data?: SegmentData) => SvgPath` Use this function to add a new move SVG path element. - `line: (x: number, y: number, relative?: boolean, data?: SegmentData) => SvgPath` Use this function to add a new line SVG path element. - `curve: (x1: number, y1: number, x2: number, y2: number, x: number, y: number, relative?: boolean, data?: SegmentData) => SvgPath` Use this function to add a new curve SVG path element. - `arc: (rx: number, ry: number, xAr: number, lAf: number, sf: number, x: number, y: number, relative?: boolean, data?: SegmentData) => SvgPath` Use this function to add a new non-bezier curve SVG path element. - `parse: (path: string) => SvgPath` Parses the `d` of an SVG path and inserts its commands at the cursor. A closing `Z` at the end is dropped: the `close` of the path object decides that. - `stringify: () => string` Writes the path as a string for the `d` attribute of a `path` element. Numbers are rounded to the `accuracy` option's decimals (to integers with `0`, not at all with `false`), and a closed path ends with `Z`. - `scale: (x: number, y: number) => SvgPath` Scales all elements in the current SVG path object. There is an individual parameter for each coordinate. Scaling will also be done for control points of curves and the radii of arcs, affecting the given coordinate; an arc's rotation and flags stay as they are. - `translate: (x: number, y: number) => SvgPath` Translates all elements in the current SVG path object. The translation is relative and there is an individual parameter for each coordinate. Translation will also be done for control points of curves, affecting the given coordinate; an arc's radii, rotation and flags stay as they are. - `transform: (transformFnc: (cmd: PathCommand, param: keyof T, cmdIndex: number, paramIndex: number, cmds: PathCommand[]) => number | void) => SvgPath` This function will run over all existing path elements and then loop over their attributes. The callback function will be called for every path element attribute that exists in the current path. The method signature of the callback function looks like this: ```javascript function(pathElement, paramName, pathElementIndex, paramIndex, pathElements) ``` If something else than undefined is returned by the callback function, this value will be used to replace the old value. This allows you to build custom transformations of path objects that can't be achieved using the basic transformation functions scale and translate. - `clone: (close?: boolean) => SvgPath` This function clones a whole path object with all its properties. This is a deep clone and path element objects will also be cloned. - `splitByCommand: (command: string) => SvgPath[]` Splits the path into new paths, each starting at the given command, such as `'M'` to get the separate pieces of a line with holes. The letter matches absolute and relative commands. The pieces get copies of the commands and the path's options, with their cursor at the end; the last piece is closed if the path is. ```ts import { SvgPath } from 'chartist'; const path = new SvgPath() .move(0, 100) .line(50, 20) .curve(70, 0, 90, 0, 100, 40); path.stringify(); // 'M0,100L50,20C70,0,90,0,100,40' // 10px to the right, at half the height path.translate(10, 0).scale(1, 0.5); ``` ### BasePathParams ```ts interface BasePathParams ``` The end point every path command has. Properties: - `x: number` The x of the command's end point. - `y: number` The y of the command's end point. ### MoveParams ```ts type MoveParams = BasePathParams ``` The parameters of a move command (`M`): the point to move to. Properties: - `x: number` The x of the command's end point. - `y: number` The y of the command's end point. ### LineParams ```ts type LineParams = BasePathParams ``` The parameters of a line command (`L`): the point to draw to. Properties: - `x: number` The x of the command's end point. - `y: number` The y of the command's end point. ### CurveParams ```ts interface CurveParams extends BasePathParams ``` The parameters of a cubic Bézier curve command (`C`). Properties: - `x: number` The x of the command's end point. - `y: number` The y of the command's end point. - `x1: number` The x of the first control point. - `y1: number` The y of the first control point. - `x2: number` The x of the second control point. - `y2: number` The y of the second control point. ### ArcParams ```ts interface ArcParams extends BasePathParams ``` The parameters of an elliptical arc command (`A`), as in SVG. Properties: - `x: number` The x of the command's end point. - `y: number` The y of the command's end point. - `rx: number` The x radius of the ellipse. - `ry: number` The y radius of the ellipse. - `xAr: number` The rotation of the ellipse's x axis, in degrees. - `lAf: number` The large arc flag: `1` takes the longer way around, `0` the shorter. - `sf: number` The sweep flag: `1` draws the arc clockwise, `0` counterclockwise. ### PathParams ```ts type PathParams = MoveParams | LineParams | CurveParams | ArcParams ``` The parameters of any path command. ### PathCommand ```ts type PathCommand = { … } & T ``` A command of an [`SvgPath`](https://chartist.dev/api/svg/#SvgPath): its letter, its parameters and the data of its point. Properties: - `command: string` The command letter: `M`, `L`, `C` or `A`, or from a parsed path also `H`, `V`, `S`, `Q`, `T` or `Z` (lowercase when relative). - `data?: SegmentData` The value of the chart point the command draws to, if any. ### SvgPathOptions ```ts interface SvgPathOptions ``` The options of an [`SvgPath`](https://chartist.dev/api/svg/#SvgPath). Properties: - `accuracy?: number | false` The decimals numbers are rounded to when the path is stringified: `0` rounds to integers, `false` doesn't round. ### Attributes ```ts type Attributes = Record ``` SVG attributes by name, for [`Svg.attr`](https://chartist.dev/api/svg/#Svg.attr). `undefined` values are skipped; names with a prefix such as `ct:value` use its namespace. ### AnimationDefinition ```ts interface AnimationDefinition ``` One SMIL animation of an attribute, for [`Svg.animate`](https://chartist.dev/api/svg/#Svg.animate): the attributes of its `` element, plus an `easing`. Properties: - `id?: string` The `id` of the `` element, to start other animations relative to it. - `easing?: number[] | string & {} | 'easeInSine' | 'easeOutSine' | 'easeInOutSine' | 'easeInQuad' | 'easeOutQuad' | 'easeInOutQuad' | 'easeInCubic' | 'easeOutCubic' | 'easeInOutCubic' | 'easeInQuart' | 'easeOutQuart' | 'easeInOutQuart' | 'easeInQuint' | 'easeOutQuint' | 'easeInOutQuint' | 'easeInExpo' | 'easeOutExpo' | 'easeInOutExpo' | 'easeInCirc' | 'easeOutCirc' | 'easeInOutCirc' | 'easeInBack' | 'easeOutBack' | 'easeInOutBack'` The easing: a name from [`easings`](https://chartist.dev/api/svg/#easings), four numbers of a cubic Bézier curve, or a CSS easing such as `'ease-out'`, `'cubic-bezier(0.34, 1.56, 0.64, 1)'`, `linear()` or `steps()`. Sets `calcMode`, `keySplines` and `keyTimes` for you, or, for easings SMIL can't express (ones that overshoot, `linear()`, `steps()`), `values` sampled between `from` and `to`. - `calcMode?: 'discrete' | 'linear' | 'paced' | 'spline'` How values are interpolated (SMIL `calcMode`); an `easing` sets `'spline'`. - `restart?: 'always' | 'whenNotActive' | 'never'` Whether the animation can restart while it's running (SMIL `restart`). - `repeatCount?: number | 'indefinite'` How many times the animation repeats, or `'indefinite'`. - `repeatDur?: string` How long the animation repeats in total, or `'indefinite'`. - `keySplines?: string` The Bézier control points of a `'spline'` animation (SMIL `keySplines`); an `easing` sets them. - `keyTimes?: string` The times of the key values, from `0` to `1` (SMIL `keyTimes`); an `easing` sets them. - `values?: string` The values in between, `;`-separated (SMIL `values`); an easing that overshoots sets them. - `fill?: string` `'freeze'` keeps the last value after the animation, `'remove'` resets it. Guided mode sets `'freeze'`. - `min?: string | number` The minimum active duration (SMIL `min`). - `max?: string | number` The maximum active duration (SMIL `max`). - `begin?: string | number` When the animation starts: ms as a number, or a SMIL time such as `'1s'`. - `end?: string | number` When the animation ends (SMIL `end`). - `dur: string | number` How long the animation runs: ms as a number, or a SMIL time such as `'2s'`. - `from: string | number` The value the attribute starts at. - `to: string | number` The value the attribute ends at. ### AnimationEvent ```ts interface AnimationEvent ``` The payload of the `animationBegin` and `animationEnd` events that [`Svg.animate`](https://chartist.dev/api/svg/#Svg.animate) emits when it gets an event emitter. Properties: - `element: Svg` The animated element. - `animate: Element` The `` element. - `params: AnimationDefinition` The animation's definition, as given. ## chartist/compat (Chartist API) > The 1.x events (data, draw, created, …) and 1.x plugins on the functional API. ### compat *from `chartist/compat`* ```ts const compat: ChartPlugin & { legacyPlugin: typeof legacyPlugin } ``` The compat plugin. Restores the 1.x events (`data`, `optionsChanged`, `draw`, `created`, `animationBegin`, `animationEnd`) with 1.x payloads. With it, the 1.x chart classes also run 1.x plugin functions. Add it to `plugins`, of a 1.x class, a preset or `chart()`. The `draw` events come in the 1.x order, right after the chart is in the DOM, so `element` is a live element; then `created`. Animations started with `animate()` on the chart's elements emit `animationBegin` and `animationEnd`. Importing `chartist/compat` also types the 1.x events on every chart handle. It needs the DOM renderer, and the `draw` and `created` events need a chart made by a preset or a 1.x class. ```ts import { lineChart } from 'chartist'; import { compat } from 'chartist/compat'; const chart = lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[12, 9, 7, 8, 5]] }, { plugins: [compat] } ); // Registered after the first render, so it runs from the next one on. chart.on('draw', data => { if (data.type === 'point' && data.index === 4) { data.element.addClass('is-friday'); } }); ``` ### fromLegacyPlugin *from `chartist/compat`* ```ts function fromLegacyPlugin(plugin: LegacyPlugin, options?: unknown): ChartPlugin ``` Converts a 1.x plugin function into a v2 plugin for `chart()` and the preset functions. It turns the 1.x events on for the chart and calls the plugin with a 1.x-style chart object (`container`, `data`, `options`, `svg`, `eventEmitter`, `on`, `off`, `update`, `detach`). You don't need [`compat`](https://chartist.dev/api/compat/#compat) in the list as well. Parameters: - `plugin: LegacyPlugin` The 1.x plugin: a function that gets the chart (and its options) - `options?: unknown` The plugin's options; without them, the plugin is called with the chart only, as in 1.x ```ts import { lineChart, type Plugin } from 'chartist'; import { fromLegacyPlugin } from 'chartist/compat'; // A 1.x plugin: a function that gets the chart and its options. const caption: Plugin = (chart, options) => { chart.on('created', () => { chart.container.setAttribute('aria-label', options.text); }); }; lineChart( '#chart', { series: [[1, 3, 2, 4]] }, { plugins: [fromLegacyPlugin(caption, { text: 'Visitors per week' })] } ); ``` ### AreaDrawEvent *from `chartist/compat`* ```ts type AreaDrawEvent = Core.AreaDrawEvent ``` The 1.x `draw` event of an area (with `showArea`), one per series and segment. `index` is the series' index and `meta` the series' meta. Properties: - `type: 'area'` Always `'area'`. - `values: NormalizedSeries` All normalized values of the series. - `path: SvgPath` The area's path as drawn (a copy, safe to change). - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `chartRect: ChartRect` The drawing area, in px. - `axisX: Axis` The X axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `axisY: Axis` The Y axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `meta: any` The `meta` of the value (or of the series, for lines and areas). - `index: number` The index of the value in its series (of the series, for lines and areas). - `series: SeriesObject | FlatSeriesValue | Series` The series as given in the data: an array or a series object. - `seriesIndex: number` The index of the series in the data. ### AxesDrawEvent *from `chartist/compat`* ```ts type AxesDrawEvent = Core.AxesDrawEvent ``` The 1.x `draw` events of the axes: grid lines, the grid background and labels. ### BarChartCreatedEvent *from `chartist/compat`* ```ts type BarChartCreatedEvent = Core.BarChartCreatedEvent ``` The payload of a bar chart's 1.x `created` event. Properties: - `chartRect: ChartRect` The drawing area, in px. - `axisX: Axis` The X axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `axisY: Axis` The Y axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `svg: Svg` The chart's SVG element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `options: TOptions` The options the chart was drawn with, defaults and responsive options applied. ### BarDrawEvent *from `chartist/compat`* ```ts type BarDrawEvent = Core.BarDrawEvent ``` The 1.x `draw` event of a bar, one per value. The bar is a `line` element from its base (`x1`, `y1`) to its value (`x2`, `y2`). Properties: - `type: 'bar'` Always `'bar'`. - `value: number | NormalizedMulti` The bar's normalized value: a number, or `{ x, y }` numbers. - `x1: number` The x of the bar's base, in px. - `y1: number` The y of the bar's base, in px. - `x2: number` The x of the bar's end (its value), in px. - `y2: number` The y of the bar's end (its value), in px. - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `chartRect: ChartRect` The drawing area, in px. - `axisX: Axis` The X axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `axisY: Axis` The Y axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `meta: any` The `meta` of the value (or of the series, for lines and areas). - `index: number` The index of the value in its series (of the series, for lines and areas). - `series: SeriesObject | FlatSeriesValue | Series` The series as given in the data: an array or a series object. - `seriesIndex: number` The index of the series in the data. ### CreatedEvent *from `chartist/compat`* ```ts type CreatedEvent = Core.CreatedEvent ``` The payload of the 1.x `created` event, emitted once a chart is drawn: its SVG, drawing area, axes and options. Properties: - `chartRect: ChartRect` The drawing area, in px. - `axisX: Axis` The X axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `axisY: Axis` The Y axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `svg: Svg` The chart's SVG element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `options: TOptions` The options the chart was drawn with, defaults and responsive options applied. ### DataEvent *from `chartist/compat`* ```ts type DataEvent = Core.DataEvent ``` The payload of the 1.x `data` event, emitted before a chart draws new data. Listeners may change `data` in place to transform it. Properties: - `type: 'update' | 'initial'` `'initial'` for the first data, `'update'` for data set later. - `data: Data` The data the chart is about to draw (a copy, safe to change). ### DrawEvent *from `chartist/compat`* ```ts type DrawEvent = Core.DrawEvent ``` The fields every 1.x `draw` event shares. A `draw` listener gets one per element drawn; check `type` to tell them apart. Properties: - `type: string` What was drawn: `'point'`, `'line'`, `'bar'`, `'grid'`, … - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `chartRect: ChartRect` The drawing area, in px. - `axisX: Axis` The X axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `axisY: Axis` The Y axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `meta: any` The `meta` of the value (or of the series, for lines and areas). - `index: number` The index of the value in its series (of the series, for lines and areas). - `series: SeriesObject | FlatSeriesValue | Series` The series as given in the data: an array or a series object. - `seriesIndex: number` The index of the series in the data. ### GridBackgroundDrawEvent *from `chartist/compat`* ```ts type GridBackgroundDrawEvent = Core.GridBackgroundDrawEvent ``` The 1.x `draw` event of the grid background, the `rect` behind the drawing area (with `showGridBackground`). Properties: - `type: 'gridBackground'` Always `'gridBackground'`. - `group: Svg` The grid group the background is in. - `element: Svg` The background `rect`. ### GridDrawEvent *from `chartist/compat`* ```ts type GridDrawEvent = Core.GridDrawEvent ``` The 1.x `draw` event of a grid line, one per tick of each axis. `x1`, `y1`, `x2`, `y2` are the line's ends in px. Properties: - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `index: number` The index of the value in its series (of the series, for lines and areas). - `type: 'grid'` Always `'grid'`. - `axis: Axis` The axis the grid line belongs to. - `x1: number` The x of the line's start, in px. - `y1: number` The y of the line's start, in px. - `x2: number` The x of the line's end, in px. - `y2: number` The y of the line's end, in px. ### LabelDrawEvent *from `chartist/compat`* ```ts type LabelDrawEvent = Core.LabelDrawEvent ``` The 1.x `draw` event of an axis label, one per tick. The label is a `foreignObject` with a `span`, placed at `x`, `y` with the given size. Properties: - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `index: number` The index of the value in its series (of the series, for lines and areas). - `type: 'label'` Always `'label'`. - `axis: Axis` The axis the label belongs to. - `text: Label` The label as shown, after `labelTransform`. - `x: number` The x of the label's box, in px. - `y: number` The y of the label's box, in px. - `width: number` The width of the label's box, in px. - `height: number` The height of the label's box, in px. ### LineChartCreatedEvent *from `chartist/compat`* ```ts type LineChartCreatedEvent = Core.LineChartCreatedEvent ``` The payload of a line chart's 1.x `created` event. Properties: - `chartRect: ChartRect` The drawing area, in px. - `axisX: Axis` The X axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `axisY: Axis` The Y axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `svg: Svg` The chart's SVG element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `options: TOptions` The options the chart was drawn with, defaults and responsive options applied. ### LineDrawEvent *from `chartist/compat`* ```ts type LineDrawEvent = Core.LineDrawEvent ``` The 1.x `draw` event of a line, one per series. `index` is the series' index and `meta` the series' meta. Properties: - `type: 'line'` Always `'line'`. - `values: NormalizedSeries` All normalized values of the series. - `path: SvgPath` The line's path as drawn (a copy, safe to change). - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `chartRect: ChartRect` The drawing area, in px. - `axisX: Axis` The X axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `axisY: Axis` The Y axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `meta: any` The `meta` of the value (or of the series, for lines and areas). - `index: number` The index of the value in its series (of the series, for lines and areas). - `series: SeriesObject | FlatSeriesValue | Series` The series as given in the data: an array or a series object. - `seriesIndex: number` The index of the series in the data. ### OptionsChangedEvent *from `chartist/compat`* ```ts type OptionsChangedEvent = Core.OptionsChangedEvent ``` The payload of the 1.x `optionsChanged` event, emitted when a responsive option starts or stops applying. Properties: - `previousOptions: T` The options before the change. - `currentOptions: T` The options now in effect. ### PieChartCreatedEvent *from `chartist/compat`* ```ts type PieChartCreatedEvent = Core.PieChartCreatedEvent ``` The payload of a pie chart's 1.x `created` event: as for other charts, without axes. Properties: - `chartRect: ChartRect` The drawing area, in px. - `svg: Svg` The chart's SVG element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `options: TOptions` The options the chart was drawn with, defaults and responsive options applied. ### Plugin *from `chartist/compat`* ```ts type Plugin = Core.Plugin ``` A 1.x plugin: a function that gets the chart object (and its options) and registers listeners with `chart.on('draw', …)`. It runs with the `compat` plugin from `chartist/compat`, whose `fromLegacyPlugin()` also turns it into a v2 plugin. ### PointDrawEvent *from `chartist/compat`* ```ts type PointDrawEvent = Core.PointDrawEvent ``` The 1.x `draw` event of a line chart's point, one per value. The point is a very short `line` element at `x`, `y` (CSS gives it a round cap). Properties: - `type: 'point'` Always `'point'`. - `value: NormalizedSeriesPrimitiveValue` The point's normalized value: `{ x, y }` numbers, or a number. - `x: number` The point's x, in px. - `y: number` The point's y, in px. - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `chartRect: ChartRect` The drawing area, in px. - `axisX: Axis` The X axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `axisY: Axis` The Y axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `meta: any` The `meta` of the value (or of the series, for lines and areas). - `index: number` The index of the value in its series (of the series, for lines and areas). - `series: SeriesObject | FlatSeriesValue | Series` The series as given in the data: an array or a series object. - `seriesIndex: number` The index of the series in the data. ### SliceDrawEvent *from `chartist/compat`* ```ts type SliceDrawEvent = Core.SliceDrawEvent ``` The 1.x `draw` event of a pie or donut slice, one per value. `index` is the slice's index and `series` its value as given in the data. Properties: - `type: 'slice'` Always `'slice'`. - `value: NormalizedSeriesPrimitiveValue` The slice's value. - `totalDataSum: number` The value of the full circle: the sum of all values, or the `total` option. - `path: SvgPath` The slice's path as drawn (a copy, safe to change). - `center: Dot` The center of the pie, in px. - `radius: number` The radius of the pie, in px. - `startAngle: number` Where the slice starts, in degrees clockwise from the top. - `endAngle: number` Where the slice ends, in degrees clockwise from the top. - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `chartRect: ChartRect` The drawing area, in px. - `meta: any` The `meta` of the value (or of the series, for lines and areas). - `index: number` The index of the value in its series (of the series, for lines and areas). - `series: SeriesObject | FlatSeriesValue | Series` The series as given in the data: an array or a series object. ### SliceLabelDrawEvent *from `chartist/compat`* ```ts type SliceLabelDrawEvent = Core.SliceLabelDrawEvent ``` The 1.x `draw` event of a pie slice's label. `index` is the slice's index; the label is a `text` element anchored at `x`, `y`. Properties: - `type: 'label'` Always `'label'`. - `text: string` The label as shown, after `labelTransform`. - `x: number` The x the label is anchored at, in px. - `y: number` The y the label is anchored at, in px. - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `chartRect: ChartRect` The drawing area, in px. - `meta: any` The `meta` of the value (or of the series, for lines and areas). - `index: number` The index of the value in its series (of the series, for lines and areas). - `series: SeriesObject | FlatSeriesValue | Series` The series as given in the data: an array or a series object. ## chartist/live (Chartist API) > Live updates: the renderer that changes a chart’s SVG in place, the keyed diff, and SMIL update animations. ### smilAnimations *experimental · from `chartist/live`* ```ts function smilAnimations(): LiveAnimations ``` Animates the changes of an update with SMIL, timed by the chart's CSS: each updated node's changed numbers and paths move from their previous value to their new one over `--ct-update-duration` (a CSS time, 300 ms by default; `0s` turns the animations off) with the easing in `--ct-update-easing` (a CSS easing, `ease-out` by default). The attributes hold their new values from the start, and an update during an animation continues from where each element is drawn. It animates updates (`update()`, `setOptions()`) and changes of the matching `responsive` options, not resizes, and nothing while the visitor's system asks for reduced motion. Lines and areas follow their values' keys, and pie slices turn around the pie; layers and plugins can give keyframes of their own. Returns: The animations, for `liveDomRenderer({ animations })` ```ts import { lineChart } from 'chartist'; import { liveDomRenderer, smilAnimations } from 'chartist/live'; // 300 ms with ease-out, unless the CSS sets --ct-update-duration // and --ct-update-easing. const chart = lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed'], series: [[5, 2, 4]] }, { renderer: liveDomRenderer({ animations: smilAnimations() }) } ); chart.update({ data: { labels: ['Mon', 'Tue', 'Wed'], series: [[3, 6, 1]] } }); ``` ### diffScenes *experimental · from `chartist/live`* ```ts function diffScenes(previous: SceneNode, next: SceneNode, options?: DiffOptions): SceneChange[] ``` The changes between two scenes, matched by what each node stands for: `add`, `update` (with the previous node) and `remove`, in the new scene's document order, with the `remove` entries after their former parent's kept children. Every node of an added or removed subtree has its own entry. The live renderer patches its SVG with them; other renderers that change their output in place can use them too. Nodes are matched among the children of matched parents: values by their layer, series and key (see `key`), series by layer and index, grid lines and axis labels by axis and tick, nodes with their own `key` by it, and the rest by element name and first class, in order. Parameters: - `previous: SceneNode` The previous scene - `next: SceneNode` The new scene - `options?: DiffOptions` How values are identified, and the data's labels Returns: The changes, in the new scene's document order ```ts import { lineChartConfig, toScene } from 'chartist'; import { diffScenes } from 'chartist/live'; const size = { width: 600, height: 300 }; const labels = ['Mo', 'Tu', 'We']; const before = toScene(lineChartConfig({ labels, series: [[1, 2, 3]] }), size); const after = toScene(lineChartConfig({ labels, series: [[1, 5, 3]] }), size); for (const { op, node } of diffScenes(before, after, { labels })) { console.log(op, node.role, node.key); } ``` ### liveDomRenderer *experimental · from `chartist/live`* ```ts function liveDomRenderer(options?: LiveDomRendererOptions): Renderer ``` Creates a DOM renderer that keeps a chart's SVG and changes it in place on every later render, instead of drawing a new one: elements that stand for the same thing as before stay, with only their changed attributes, classes and text set, elements for new nodes are created and elements of nodes that are gone are removed. What listeners added to kept elements stays, and `mount` gets the `changes` of the render. Its first render, and every render without the previous one (1.x charts), draws a new SVG as [`domRenderer`](https://chartist.dev/api/rendering/#domRenderer) does. A chart whose container holds markup from `renderForHydration()` takes that markup over instead, keeping the server's elements, unless `adopt` is `false`. The renderer keeps no state of its own, so one instance can serve many charts. Parameters: - `options?: LiveDomRendererOptions` How values are identified (`key`), update animations, such as `smilAnimations()`, and whether to take server markup over Returns: The renderer, for `renderer` in `chart()` and the presets ```ts import { lineChart } from 'chartist'; import { liveDomRenderer } from 'chartist/live'; const labels = ['Mo', 'Tu', 'We', 'Th', 'Fr']; const values = [3, 5, 4, 6, 5]; const chart = lineChart( '#chart', { labels, series: [values] }, // Points and labels follow their day as the window slides. { renderer: liveDomRenderer({ key: 'label' }) } ); let day = 0; setInterval(() => { labels.push(`Day ${++day}`); labels.shift(); values.push(Math.round(Math.random() * 10)); values.shift(); chart.update({ labels: [...labels], series: [[...values]] }); }, 1000); ``` ### ValueKeyContext *experimental · from `chartist/live`* ```ts interface ValueKeyContext ``` What [`ValueKey`](https://chartist.dev/api/live/#ValueKey) functions get about a value: where it is, the value itself, the label at its position and its meta. Properties: - `seriesIndex: number` The series' index in the chart's data. - `valueIndex: number` The value's index in its series (for pie data, 0). - `value: unknown` The value, as the layer drew it. - `label: unknown` The label at the value's position: `labels[valueIndex]`, or the slice's label in pie data. - `meta: unknown` The value's meta, if any. ### ValueKey *experimental · from `chartist/live`* ```ts type ValueKey = 'index' | 'label' | ((value: ValueKeyContext) => string | number) ``` What identifies a value across updates: `'index'` (the default) its position in its series, `'label'` the label at its position, or a function of the value, such as `({ meta }) => meta.id`. ### DiffOptions *experimental · from `chartist/live`* ```ts interface DiffOptions ``` The options of [`diffScenes`](https://chartist.dev/api/live/#diffScenes): how values are identified, and the labels for `key: 'label'`. Properties: - `key?: ValueKey` What identifies a value across updates; `'index'` by default. - `labels?: readonly unknown[]` The new scene's labels, for `key: 'label'`. - `previousLabels?: readonly unknown[]` The previous scene's labels, for `key: 'label'`; `labels` if omitted. ### LiveAnimationContext *experimental · from `chartist/live`* ```ts interface LiveAnimationContext ``` What the live renderer hands its [`LiveAnimations`](https://chartist.dev/api/live/#LiveAnimations) after patching the SVG of an update. Properties: - `changes: readonly SceneChange[]` The changes the render made, in document order. - `elementOf: (node: SceneNode) => Element | undefined` The element of a node of the new scene. - `update: RenderUpdate` The previous render, with the cause and the keyframes lookup. - `container: Element` The chart's container. - `scene: SceneNode` The new scene. - `measureStyle?: MeasureStyle` Reads computed styles in the container, with the chart's root classes. - `valueKey: (context: Omit, scene: 'previous' | 'next') => string` The key that identifies a value across updates, as the renderer's `key` option says: for the value at `valueIndex` of series `seriesIndex`, with its value and meta, in the previous scene or the new one (labels differ between them). ### LiveAnimations *experimental · from `chartist/live`* ```ts interface LiveAnimations ``` Update animations for the live renderer, such as [`smilAnimations`](https://chartist.dev/api/live/#smilAnimations): called after the SVG of an update was patched, it starts the animations and returns their timing, or nothing when the render doesn't animate. Properties: - `animate: (context: LiveAnimationContext) => UpdateAnimation | undefined` Starts the update animations of a render. ### LiveDomRendererOptions *experimental · from `chartist/live`* ```ts interface LiveDomRendererOptions ``` The options of [`liveDomRenderer`](https://chartist.dev/api/live/#liveDomRenderer). Properties: - `key?: ValueKey` What identifies a value across updates; `'index'` by default. - `animations?: LiveAnimations` Update animations, such as `smilAnimations()`; none by default. - `adopt?: boolean` Takes over markup from `renderForHydration()` in the chart's container and keeps its elements; `true` by default. With `false`, the chart draws a new svg over the server markup. ## chartist/server (Chartist API) > Charts as markup without a DOM: SVG strings, markup the browser takes over, and stylesheets written into the SVG for images and PDFs. ### computeStyles *experimental · from `chartist/server`* ```ts function computeStyles(scene: SceneNode, css: string | readonly string[], options?: ComputeStylesOptions): ComputedStyles ``` Computes the styles a scene's SVG elements get from CSS, with Chartist's own CSS engine: a small one, made for chart styles. Pass the result to [`applyStyles`](https://chartist.dev/api/server/#applyStyles). It matches rules as a browser does, per element, with the scene below a page's `html` and `body` (so `:root`, `html` and `body` rules apply, and their custom properties reach the chart): type, class and attribute selectors, `:root`, `:not()`, `:is()` and `:where()`, and the descendant and child combinators, ranked by `!important`, the element's own `style`, specificity and order. Custom properties are resolved per element, and `light-dark()` by `colorScheme`. Rules in `@media (prefers-color-scheme)` apply for their scheme, and `@supports` and `@layer` blocks are read in place. What it doesn't support it leaves out, and reports to `onSkip`: other at-rules, sibling and structural selectors (`+`, `~`, `:nth-child()`), ids and other pseudo-classes, nested rules and the `font` shorthand. `calc()` and relative units such as `rem` are passed on as written. For full CSS, compute the styles with a browser and pass those to `applyStyles()`. Only the values of properties SVG has presentation attributes for are computed; elements inside a `foreignObject` (HTML labels) get none. Pass only stylesheets your application controls: CSS values, `url()` included, end up in the markup. New with 2.1: the server API may still change in a 2.x minor before it becomes stable. Parameters: - `scene: SceneNode` A scene, usually from `toScene()` - `css: string | readonly string[]` A stylesheet's text, or several, applied in order as if the later ones came later in the page - `options?: ComputeStylesOptions` The color scheme (`light` by default), and `onSkip` Returns: The values each node gets ```ts import { lineChartConfig } from 'chartist'; import { applyStyles, computeStyles, toScene } from 'chartist/server'; const css = '.ct-line { stroke: teal; } .ct-series:nth-child(2) { stroke: red; }'; const scene = toScene(lineChartConfig({ series: [[5, 2, 4]] }), { width: 600, height: 300 }); const styles = computeStyles(scene, css, { // `.ct-series:nth-child(2)`: selector onSkip: ({ text, reason }) => console.warn(`${text}: ${reason}`) }); export const styled = applyStyles(scene, styles); ``` ### applyStyles *experimental · from `chartist/server`* ```ts function applyStyles(scene: SceneNode, styles: string | readonly string[] | ComputedStyles, options?: ComputeStylesOptions): SceneNode ``` Writes styles into a scene as SVG presentation attributes, so its markup draws the same in tools that read SVG on its own: rasterizers that make PNGs, PDF libraries, email. Every SVG element gets its values as attributes such as `stroke="#d70206"`; values of properties SVG has no attribute for are left out. Its classes stay, so a page can still style the markup, and elements inside a `foreignObject` (HTML labels) are left as they are: use SVG labels for images. Give it CSS, which [`computeStyles`](https://chartist.dev/api/server/#computeStyles) computes with Chartist's own, small CSS engine, or the styles another engine computed, such as a headless browser's `getComputedStyle()` for each element. New with 2.1: the server API may still change in a 2.x minor before it becomes stable. Parameters: - `scene: SceneNode` A scene, usually from `toScene()`; it isn't changed - `styles: string | readonly string[] | ComputedStyles` The values per node from [`computeStyles`](https://chartist.dev/api/server/#computeStyles) or another engine, or the CSS for `computeStyles()`: a stylesheet's text or several, applied in order - `options?: ComputeStylesOptions` With CSS: the options of `computeStyles()` Returns: A copy of the scene with the styles as attributes ```ts import { readFileSync } from 'node:fs'; import { createRequire } from 'node:module'; import { lineChartConfig } from 'chartist'; import { applyStyles, renderToString, toScene } from 'chartist/server'; const css = readFileSync( createRequire(import.meta.url).resolve('chartist/styles.css'), 'utf8' ); const scene = toScene( lineChartConfig({ labels: ['Mon', 'Tue', 'Wed'], series: [[5, 2, 4]] }), { width: 600, height: 300 } ); // SVG for a rasterizer: the colors and widths as attributes. export const svg = renderToString(applyStyles(scene, css)); ``` ### toScene *from `chartist/server`* Builds a chart's scene without rendering it: no DOM, timers or globals. The same as [`toScene`](https://chartist.dev/api/rendering/#toScene) from `chartist`, re-exported so server code needs one import. ### renderForHydration *experimental · from `chartist/server`* ```ts function renderForHydration(config: ChartConfig, size: SceneSize): string ``` Renders a chart to markup that `chart()` and the presets take over in the browser (hydration), instead of drawing a new chart. Needs no DOM, so it works in Node.js and in server bundles; put the markup into the chart's container once, and create the chart there with `liveDomRenderer()` from `chartist/live` as its renderer, which keeps the server's elements. It lays the chart out as [`toScene`](https://chartist.dev/api/rendering/#toScene) does, at the given size. The root gets the size `chart()` gives it (`100%` unless the config sets one) and, unless the config sets a `viewBox`, a `viewBox` of the layout size, so the drawing scales into its container until the browser lays it out at the real size. Its `data-ct-hydrate` attribute records what the browser needs to build the same scene again: the size, the label mode, whether text was estimated and whether the `viewBox` only scales. Labels are HTML unless `size.labels` or the config says otherwise, as `chart()` draws them. The markup has no `style` attributes, so it passes a Content Security Policy of `style-src 'self'`, and it needs Chartist's stylesheet in the page. Styles that SVG has attributes for (a label's `overflow`, a donut's `stroke-width`) are written as those; the others, such as styles a `scene` listener adds, are applied when the browser takes the markup over. For a self-contained SVG image, with its styles inline (files, email, a static chart), use `toScene()` and [`renderToString`](https://chartist.dev/api/server/#renderToString) instead. New with 2.1: the server API may still change in a 2.x minor before it becomes stable. Parameters: - `config: ChartConfig` The chart, as the browser creates it: data, axes, layers, plugins and listeners - `size: SceneSize` The size to lay out at, in px, and how to measure text; use the estimate (no `measureText`), which the browser replays Returns: The SVG markup, its root marked with `data-ct-hydrate` ```ts import { lineChartConfig } from 'chartist'; import { renderForHydration } from 'chartist/server'; const data = { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[12, 9, 7, 8, 5]] }; // On the server, in the chart's container: export const markup = renderForHydration(lineChartConfig(data), { width: 600, height: 300 }); // In the browser, the chart keeps the server's elements: // lineChart('#chart', data, { renderer: liveDomRenderer() }); ``` ### renderToString *experimental · from `chartist/server`* ```ts function renderToString(scene: SceneNode): string ``` Serializes a scene to standalone SVG markup. Needs no DOM, so it works in Node.js and in server bundles. The root gets the SVG and `ct:` namespaces, and attribute values and text are escaped. The markup has the chart's classes but no styles: load `chartist/styles.css` where it's shown, or add a `style` element to the scene. New in `chartist/server` with 2.1: the server API may still change in a 2.x minor before it becomes stable. Parameters: - `scene: SceneNode` A scene, usually from `toScene()` Returns: The SVG markup, e.g. `…` ```ts import { lineChartConfig } from 'chartist'; import { renderToString, toScene } from 'chartist/server'; export function weekChart(values: number[]): string { const scene = toScene( lineChartConfig({ labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [values] }), { width: 600, height: 300 } ); return renderToString(scene); } ``` ### ComputedStyles *experimental · from `chartist/server`* ```ts type ComputedStyles = (node: SceneNode) => Readonly> | undefined ``` The styles of a scene's elements: for a scene node, the values of the CSS properties it gets (property to value, e.g. `{ stroke: '#d70206' }`), or nothing. [`computeStyles`](https://chartist.dev/api/server/#computeStyles) makes one with Chartist's own CSS engine; another engine can make one too, for [`applyStyles`](https://chartist.dev/api/server/#applyStyles). New with 2.1: the server API may still change in a 2.x minor before it becomes stable. ### SkippedStyle *experimental · from `chartist/server`* ```ts interface SkippedStyle ``` A part of a stylesheet that [`computeStyles`](https://chartist.dev/api/server/#computeStyles) left out, as `onSkip` gets it: its text and why. `at-rule` for an at-rule other than `@media (prefers-color-scheme)`, `@supports` and `@layer`; `selector` for a selector it can't match; `nested` for a nested rule; `shorthand` for `font`; `value` for a declaration whose value doesn't resolve on an element (a `var()` without a value or fallback there). New with 2.1: the server API may still change in a 2.x minor before it becomes stable. Properties: - `text: string` The part as written: an at-rule's prelude, a selector, a declaration. - `reason: SkipReason` Why it was left out. ### ComputeStylesOptions *experimental · from `chartist/server`* ```ts interface ComputeStylesOptions ``` Options of [`computeStyles`](https://chartist.dev/api/server/#computeStyles). New with 2.1: the server API may still change in a 2.x minor before it becomes stable. Properties: - `colorScheme?: 'light' | 'dark'` The color scheme to style for: which value of `light-dark()` and which `@media (prefers-color-scheme)` rules apply. - `onSkip?: (skipped: SkippedStyle) => void` Called once for each part of the stylesheets that was left out, so a rule that doesn't apply says why. Rules for the other color scheme and pseudo-elements (`::before`), which style nothing in SVG, aren't reported. ### ApplyStylesOptions *experimental · from `chartist/server`* ```ts type ApplyStylesOptions = ComputeStylesOptions ``` Options of [`applyStyles`](https://chartist.dev/api/server/#applyStyles) when it gets CSS: those of [`computeStyles`](https://chartist.dev/api/server/#computeStyles). New with 2.1: the server API may still change in a 2.x minor before it becomes stable. Properties: - `colorScheme?: 'light' | 'dark'` The color scheme to style for: which value of `light-dark()` and which `@media (prefers-color-scheme)` rules apply. - `onSkip?: (skipped: SkippedStyle) => void` Called once for each part of the stylesheets that was left out, so a rule that doesn't apply says why. Rules for the other color scheme and pseudo-elements (`::before`), which style nothing in SVG, aren't reported. ### SkipReason *experimental · from `chartist/server`* ```ts type SkipReason = 'at-rule' | 'selector' | 'nested' | 'shorthand' | 'value' ``` Why `computeStyles()` left a part of a stylesheet out: `at-rule`, `selector`, `nested`, `shorthand` or `value` (see `SkippedStyle`). New with 2.1: the server API may still change in a 2.x minor before it becomes stable. ### SceneSize *from `chartist/server`* The size toScene and `renderForHydration()` lay a chart out at, since there's no container to measure, and how to measure text there. The same as [`SceneSize`](https://chartist.dev/api/rendering/#SceneSize) from `chartist`, re-exported so server code needs one import. ## 1.x classes (Chartist API) > LineChart, BarChart and PieChart as in 1.x, with their options, axis classes, events and drawing helpers. Deprecated, and removed from chartist in 3.0: new code uses the chart functions. ### LineChart *1.x* ```ts class LineChart extends PresetChart ``` The 1.x line chart class: a wrapper over the `lineChart` preset with the 1.x API. 1.x events and 1.x plugin functions need the compat plugin (`chartist/compat`). See also: [`lineChart`](https://chartist.dev/api/charts/#lineChart) for the v2 functional API. ### BarChart *1.x* ```ts class BarChart extends PresetChart ``` The 1.x bar chart class: a wrapper over the `barChart` preset with the 1.x API. 1.x events and 1.x plugin functions need the compat plugin (`chartist/compat`). See also: [`barChart`](https://chartist.dev/api/charts/#barChart) for the v2 functional API. ### PieChart *1.x* ```ts class PieChart extends PresetChart ``` The 1.x pie chart class: a wrapper over the `pieChart` preset with the 1.x API. 1.x events and 1.x plugin functions need the compat plugin (`chartist/compat`). See also: [`pieChart`](https://chartist.dev/api/charts/#pieChart) for the v2 functional API. ### BaseChart *1.x* ```ts class BaseChart ``` The base class of the 1.x chart classes ([`LineChart`](https://chartist.dev/api/classes/#LineChart), [`BarChart`](https://chartist.dev/api/classes/#BarChart), [`PieChart`](https://chartist.dev/api/classes/#PieChart)): the container, `update`, `detach`, `on` and `off`. ### getSeriesOption *1.x* ```ts function getSeriesOption(series: Series | SeriesObject, options: LineChartOptionsWithDefaults, key: T): LineChartOptionsWithDefaults[T] ``` Reads a line chart option for a series: the per-series value from `options.series[name]` if the series has a name and sets it, the chart's value otherwise. Parameters: - `series: Series | SeriesObject` - `options: LineChartOptionsWithDefaults` - `key: T` ### createSvg *1.x* ```ts function createSvg(container: Element, width?: string | number, height?: string | number, className?: string, viewBox?: ViewBox): Svg ``` Create or reinitialize the SVG element for the chart Parameters: - `container: Element` The containing DOM Node object that will be used to plant the SVG element - `width?: string | number` Set the width of the SVG element. Default is 100% - `height?: string | number` Set the height of the SVG element. Default is 100% - `className?: string` Specify a class to be added to the SVG element - `viewBox?: ViewBox` Returns: The created/reinitialized SVG element ### createGrid *1.x* ```ts function createGrid(position: number, index: number, axis: Axis, offset: number, length: number, group: Svg, classes: string[], eventEmitter: EventEmitter): void ``` Creates a grid line based on a projected value. Parameters: - `position: number` - `index: number` - `axis: Axis` - `offset: number` - `length: number` - `group: Svg` - `classes: string[]` - `eventEmitter: EventEmitter` ### createGridBackground *1.x* ```ts function createGridBackground(gridGroup: Svg, chartRect: ChartRect, className: string, eventEmitter: EventEmitter): void ``` Creates a grid background rect and emits the draw event. Parameters: - `gridGroup: Svg` - `chartRect: ChartRect` - `className: string` - `eventEmitter: EventEmitter` ### createLabel *1.x* ```ts function createLabel(position: number, length: number, index: number, label: Label, axis: Axis, axisOffset: number, labelOffset: { x: number; y: number }, group: Svg, classes: string[], eventEmitter: EventEmitter): void ``` Creates a label based on a projected value and an axis. Parameters: - `position: number` - `length: number` - `index: number` - `label: Label` - `axis: Axis` - `axisOffset: number` - `labelOffset: { x: number; y: number }` - `group: Svg` - `classes: string[]` - `eventEmitter: EventEmitter` ### optionsProvider *1.x* ```ts function optionsProvider(options: T, responsiveOptions: ResponsiveOptions | undefined, eventEmitter: EventEmitter): OptionsProvider ``` Provides options handling functionality with callback for options changes triggered by responsive options and media query matches Parameters: - `options: T` Options set by user - `responsiveOptions: ResponsiveOptions | undefined` Optional functions to add responsive behavior to chart - `eventEmitter: EventEmitter` The event emitter that will be used to emit the options changed events Returns: The consolidated options object from the defaults, base and matching responsive options ### AutoScaleAxis *1.x* ```ts class AutoScaleAxis extends Axis ``` The 1.x axis with a scale fitted to the values, in round steps at least `scaleMinSpace` px apart. ### Axis *1.x* ```ts class Axis ``` The base class of the 1.x axes: projects values to px along the drawing area and draws its grid lines and labels. ### FixedScaleAxis *1.x* ```ts class FixedScaleAxis extends Axis ``` The 1.x axis with fixed ticks: the `ticks` option, or `divisor` equal steps from low to high, with a tick at both ends. ### StepAxis *1.x* ```ts class StepAxis extends Axis ``` The 1.x axis with one step per tick, such as the labels of a line chart's X axis. ### EventEmitter *1.x* ```ts class EventEmitter ``` The 1.x event emitter: `on`, `off` and `emit` by event name, with `'*'` for all events. ### AxisType *1.x* ```ts type AxisType = typeof AutoScaleAxis | typeof FixedScaleAxis | typeof StepAxis ``` The 1.x axis classes an axis's `type` option takes: [`AutoScaleAxis`](https://chartist.dev/api/classes/#AutoScaleAxis), [`FixedScaleAxis`](https://chartist.dev/api/classes/#FixedScaleAxis) or [`StepAxis`](https://chartist.dev/api/classes/#StepAxis). ### BarChartOptions *1.x* ```ts interface BarChartOptions extends Options ``` The options of a bar chart (1.x `BarChart`, and `barChart`). Properties: - `classNames?: { … }` The class names of the chart's elements. Change them to style the chart with your own CSS instead of Chartist's. - `classNames.chart?: string` The chart's SVG element. Default: `'ct-chart-bar'`. - `classNames.horizontalBars?: string` The chart's SVG element, with `horizontalBars`. Default: `'ct-horizontal-bars'`. - `classNames.label?: string` Each axis label. Default: `'ct-label'`. - `classNames.labelGroup?: string` The group of an axis's labels. Default: `'ct-labels'`. - `classNames.series?: string` The group of each series. Default: `'ct-series'`. - `classNames.bar?: string` Each bar. Default: `'ct-bar'`. - `classNames.grid?: string` Each grid line. Default: `'ct-grid'`. - `classNames.gridGroup?: string` The group of the grid lines. Default: `'ct-grids'`. - `classNames.gridBackground?: string` The grid background (with `showGridBackground`). Default: `'ct-grid-background'`. - `classNames.vertical?: string` Labels and grid lines of the vertical axis. Default: `'ct-vertical'`. - `classNames.horizontal?: string` Labels and grid lines of the horizontal axis. Default: `'ct-horizontal'`. - `classNames.start?: string` Labels at the start of their axis (left or top). Default: `'ct-start'`. - `classNames.end?: string` Labels at the end of their axis (right or bottom). Default: `'ct-end'`. - `seriesBarDistance?: number` The distance in px between the bars of a group (one bar per series). Default: `15`. - `stackBars?: boolean` Stacks the bars of a group on each other; `stackMode` says how. Default: `false`. - `stackMode?: boolean | 'accumulate' | 'accumulate-relative'` How stacked bars stack: `accumulate` adds up each group's values (the value axis fits the totals), `accumulate-relative` stacks positive values up and negative values down, and `true` draws every bar from zero, overlapping. Default: `'accumulate'`. - `horizontalBars?: boolean` Draws horizontal bars. The axes swap: the Y axis shows the labels and the X axis the values, so swap their options too. Default: `false`. - `distributeSeries?: boolean` Treats one flat array of values as one series per value, each in its own color: a profile rather than values over time. Default: `false`. - `reverseData?: boolean` Reverses the labels, the order of the series and each series' values. Default: `false`. - `showGridBackground?: boolean` Fills the grid area with a background (class `ct-grid-background`). Default: `false`. - `width?: string | number` The chart's width: px as a number, or a CSS length such as `'100%'`. Without it, the chart takes the width of its container. - `height?: string | number` The chart's height: px as a number, or a CSS length such as `'300px'`. Without it, the chart takes the height of its container. - `low?: number` The lowest value the value axis shows, instead of the data's lowest. Values below it are drawn outside the drawing area. - `high?: number` The highest value the value axis shows, instead of the data's highest. Values above it are drawn outside the drawing area. - `referenceValue?: number` A value the value axis always includes, such as 0, so it shows even when all values are above or below it. Bar charts use 0. Default: `0`. - `chartPadding?: number | Partial` Space in px between the chart's edges and its drawing area (labels sit in it): one number for every side, or an object with some sides. Default: `{ top: 15, right: 15, bottom: 5, left: 10 }`. - `axisX?: TXAxisOptions` Options of the X axis. Default: `{ offset: 30, position: 'end', labelOffset: { x: 0, y: 0 }, showLabel: true, showGrid: true, scaleMinSpace: 30, onlyInteger: false }`. - `axisY?: TYAxisOptions` Options of the Y axis. Default: `{ offset: 40, position: 'start', labelOffset: { x: 0, y: 0 }, showLabel: true, showGrid: true, scaleMinSpace: 20, onlyInteger: false }`. - `plugins?: (Plugin | [Plugin, any] | { … })[]` 1.x plugin functions (they run with the compat plugin) and v2 plugins such as `compat`. - `viewBox?: ViewBox` A `viewBox` for the SVG, so the drawing scales with its box instead of being drawn for its size. Text scales too: adjust it with responsive options. - `viewBox.width: number` The viewBox width, in SVG units. - `viewBox.height: number` The viewBox height, in SVG units. ### BarChartOptionsWithDefaults *1.x* ```ts type BarChartOptionsWithDefaults = RequiredKeys, RequiredKeys>, 'referenceValue' | 'chartPadding' | 'seriesBarDistance' | 'stackMode' | 'axisX' | 'axisY', 'classNames'> ``` [`BarChartOptions`](https://chartist.dev/api/classes/#BarChartOptions) after the defaults are applied: the 1.x `BarChart` works with these. ### BarChartCreatedEvent *1.x* ```ts type BarChartCreatedEvent = CreatedEvent ``` The payload of a bar chart's 1.x `created` event. Properties: - `chartRect: ChartRect` The drawing area, in px. - `axisX: Axis` The X axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `axisY: Axis` The Y axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `svg: Svg` The chart's SVG element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `options: TOptions` The options the chart was drawn with, defaults and responsive options applied. ### BarDrawEvent *1.x* ```ts interface BarDrawEvent extends DrawEvent ``` The 1.x `draw` event of a bar, one per value. The bar is a `line` element from its base (`x1`, `y1`) to its value (`x2`, `y2`). Properties: - `type: 'bar'` Always `'bar'`. - `value: number | NormalizedMulti` The bar's normalized value: a number, or `{ x, y }` numbers. - `x1: number` The x of the bar's base, in px. - `y1: number` The y of the bar's base, in px. - `x2: number` The x of the bar's end (its value), in px. - `y2: number` The y of the bar's end (its value), in px. - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `chartRect: ChartRect` The drawing area, in px. - `axisX: Axis` The X axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `axisY: Axis` The Y axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `meta: any` The `meta` of the value (or of the series, for lines and areas). - `index: number` The index of the value in its series (of the series, for lines and areas). - `series: SeriesObject | FlatSeriesValue | Series` The series as given in the data: an array or a series object. - `seriesIndex: number` The index of the series in the data. ### BarChartEventsTypes *1.x* ```ts type BarChartEventsTypes = BaseChartEventsTypes ``` The 1.x events of a [`BarChart`](https://chartist.dev/api/classes/#BarChart) and their payloads, by event name. Properties: - `data: DataEvent` - `options: OptionsChangedEvent` - `animationBegin: AnimationEvent` - `animationEnd: AnimationEvent` - `created: TCreateEvent` - `draw: TDrawEvents` ### LineInterpolation *1.x* ```ts type LineInterpolation = (pathCoordinates: number[], valueData: SegmentData[]) => SvgPath ``` Turns a series' points into the path of its line (see `Interpolation`). ### LineChartOptions *1.x* ```ts interface LineChartOptions extends Options ``` The options of a line chart (1.x `LineChart`, and `lineChart`). Properties: - `classNames?: { … }` The class names of the chart's elements. Change them to style the chart with your own CSS instead of Chartist's. - `classNames.chart?: string` The chart's SVG element. Default: `'ct-chart-line'`. - `classNames.label?: string` Each axis label. Default: `'ct-label'`. - `classNames.labelGroup?: string` The group of an axis's labels. Default: `'ct-labels'`. - `classNames.series?: string` The group of each series. Default: `'ct-series'`. - `classNames.line?: string` Each line. Default: `'ct-line'`. - `classNames.point?: string` Each point. Default: `'ct-point'`. - `classNames.area?: string` Each area. Default: `'ct-area'`. - `classNames.grid?: string` Each grid line. Default: `'ct-grid'`. - `classNames.gridGroup?: string` The group of the grid lines. Default: `'ct-grids'`. - `classNames.gridBackground?: string` The grid background (with `showGridBackground`). Default: `'ct-grid-background'`. - `classNames.vertical?: string` Labels and grid lines of the vertical axis. Default: `'ct-vertical'`. - `classNames.horizontal?: string` Labels and grid lines of the horizontal axis. Default: `'ct-horizontal'`. - `classNames.start?: string` Labels at the start of their axis (left or top). Default: `'ct-start'`. - `classNames.end?: string` Labels at the end of their axis (right or bottom). Default: `'ct-end'`. - `showLine?: boolean` Draws the lines. Default: `true`. - `showPoint?: boolean` Draws a point at each value. Default: `true`. - `showArea?: boolean` Fills the area between each line and `areaBase`. Default: `false`. - `areaBase?: number` The value the areas close at, usually 0. Default: `0`. - `lineSmooth?: boolean | LineInterpolation` How a line runs between its points: `true` smooths it with `Interpolation.monotoneCubic`, `false` draws straight segments (`Interpolation.none`), or pass an interpolation such as `Interpolation.cardinal()`, `Interpolation.step()` or your own. Default: `true`. - `showGridBackground?: boolean` Fills the grid area with a background (class `ct-grid-background`). Default: `false`. - `fullWidth?: boolean` Stretches the X axis so the last value lands on the right edge, without a last grid line. The last label may need chart padding to fit. Default: `false`. - `reverseData?: boolean` Reverses the labels, the order of the series and each series' values. Default: `false`. - `series?: Record, 'series'>>` Options per series, by series name: line options for one series. - `width?: string | number` The chart's width: px as a number, or a CSS length such as `'100%'`. Without it, the chart takes the width of its container. - `height?: string | number` The chart's height: px as a number, or a CSS length such as `'300px'`. Without it, the chart takes the height of its container. - `low?: number` The lowest value the value axis shows, instead of the data's lowest. Values below it are drawn outside the drawing area. - `high?: number` The highest value the value axis shows, instead of the data's highest. Values above it are drawn outside the drawing area. - `referenceValue?: number` A value the value axis always includes, such as 0, so it shows even when all values are above or below it. Bar charts use 0. - `chartPadding?: number | Partial` Space in px between the chart's edges and its drawing area (labels sit in it): one number for every side, or an object with some sides. Default: `{ top: 15, right: 15, bottom: 5, left: 10 }`. - `axisX?: TXAxisOptions` Options of the X axis. Default: `{ offset: 30, position: 'end', labelOffset: { x: 0, y: 0 }, showLabel: true, showGrid: true }`. - `axisY?: TYAxisOptions` Options of the Y axis. Default: `{ offset: 40, position: 'start', labelOffset: { x: 0, y: 0 }, showLabel: true, showGrid: true, scaleMinSpace: 20, onlyInteger: false }`. - `plugins?: (Plugin | [Plugin, any] | { … })[]` 1.x plugin functions (they run with the compat plugin) and v2 plugins such as `compat`. - `viewBox?: ViewBox` A `viewBox` for the SVG, so the drawing scales with its box instead of being drawn for its size. Text scales too: adjust it with responsive options. - `viewBox.width: number` The viewBox width, in SVG units. - `viewBox.height: number` The viewBox height, in SVG units. ### LineChartOptionsWithDefaults *1.x* ```ts type LineChartOptionsWithDefaults = RequiredKeys, RequiredKeys>, 'showLine' | 'showPoint' | 'areaBase' | 'lineSmooth' | 'chartPadding' | 'axisX' | 'axisY', 'classNames'> ``` [`LineChartOptions`](https://chartist.dev/api/classes/#LineChartOptions) after the defaults are applied: the 1.x `LineChart` works with these. ### LineChartCreatedEvent *1.x* ```ts type LineChartCreatedEvent = CreatedEvent ``` The payload of a line chart's 1.x `created` event. Properties: - `chartRect: ChartRect` The drawing area, in px. - `axisX: Axis` The X axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `axisY: Axis` The Y axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `svg: Svg` The chart's SVG element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `options: TOptions` The options the chart was drawn with, defaults and responsive options applied. ### PointDrawEvent *1.x* ```ts interface PointDrawEvent extends DrawEvent ``` The 1.x `draw` event of a line chart's point, one per value. The point is a very short `line` element at `x`, `y` (CSS gives it a round cap). Properties: - `type: 'point'` Always `'point'`. - `value: NormalizedSeriesPrimitiveValue` The point's normalized value: `{ x, y }` numbers, or a number. - `x: number` The point's x, in px. - `y: number` The point's y, in px. - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `chartRect: ChartRect` The drawing area, in px. - `axisX: Axis` The X axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `axisY: Axis` The Y axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `meta: any` The `meta` of the value (or of the series, for lines and areas). - `index: number` The index of the value in its series (of the series, for lines and areas). - `series: SeriesObject | FlatSeriesValue | Series` The series as given in the data: an array or a series object. - `seriesIndex: number` The index of the series in the data. ### LineDrawEvent *1.x* ```ts interface LineDrawEvent extends DrawEvent ``` The 1.x `draw` event of a line, one per series. `index` is the series' index and `meta` the series' meta. Properties: - `type: 'line'` Always `'line'`. - `values: NormalizedSeries` All normalized values of the series. - `path: SvgPath` The line's path as drawn (a copy, safe to change). - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `chartRect: ChartRect` The drawing area, in px. - `axisX: Axis` The X axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `axisY: Axis` The Y axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `meta: any` The `meta` of the value (or of the series, for lines and areas). - `index: number` The index of the value in its series (of the series, for lines and areas). - `series: SeriesObject | FlatSeriesValue | Series` The series as given in the data: an array or a series object. - `seriesIndex: number` The index of the series in the data. ### AreaDrawEvent *1.x* ```ts interface AreaDrawEvent extends DrawEvent ``` The 1.x `draw` event of an area (with `showArea`), one per series and segment. `index` is the series' index and `meta` the series' meta. Properties: - `type: 'area'` Always `'area'`. - `values: NormalizedSeries` All normalized values of the series. - `path: SvgPath` The area's path as drawn (a copy, safe to change). - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `chartRect: ChartRect` The drawing area, in px. - `axisX: Axis` The X axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `axisY: Axis` The Y axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `meta: any` The `meta` of the value (or of the series, for lines and areas). - `index: number` The index of the value in its series (of the series, for lines and areas). - `series: SeriesObject | FlatSeriesValue | Series` The series as given in the data: an array or a series object. - `seriesIndex: number` The index of the series in the data. ### LineChartEventsTypes *1.x* ```ts type LineChartEventsTypes = BaseChartEventsTypes ``` The 1.x events of a [`LineChart`](https://chartist.dev/api/classes/#LineChart) and their payloads, by event name. Properties: - `data: DataEvent` - `options: OptionsChangedEvent` - `animationBegin: AnimationEvent` - `animationEnd: AnimationEvent` - `created: TCreateEvent` - `draw: TDrawEvents` ### LabelDirection *1.x* ```ts type LabelDirection = 'implode' | 'neutral' | 'explode' ``` Which way pie labels anchor: see `labelDirection` in [`PieChartOptions`](https://chartist.dev/api/classes/#PieChartOptions). ### AnchorPosition *1.x* ```ts type AnchorPosition = 'start' | 'middle' | 'end' ``` The `text-anchor` of a pie label: `'start'`, `'middle'` or `'end'`. ### RadialLabelPosition *1.x* ```ts type RadialLabelPosition = 'inside' | 'center' | 'outside' ``` Where pie labels go: see `labelPosition` in [`PieChartOptions`](https://chartist.dev/api/classes/#PieChartOptions). ### Dot *1.x* ```ts interface Dot ``` A point in px, such as the center of a pie. Properties: - `x: number` The x, in px. - `y: number` The y, in px. ### PieChartOptions *1.x* ```ts interface PieChartOptions extends Omit ``` The options of a pie or donut chart (1.x `PieChart`, and `pieChart`). Properties: - `classNames?: { … }` The class names of the chart's elements. Change them to style the chart with your own CSS instead of Chartist's. - `classNames.chartPie?: string` The chart's SVG element, for pies. Default: `'ct-chart-pie'`. - `classNames.chartDonut?: string` The chart's SVG element, for donuts. Default: `'ct-chart-donut'`. - `classNames.series?: string` The group of each series. Default: `'ct-series'`. - `classNames.slicePie?: string` Each pie slice. Default: `'ct-slice-pie'`. - `classNames.sliceDonut?: string` Each donut slice. Default: `'ct-slice-donut'`. - `classNames.label?: string` Each axis label. Default: `'ct-label'`. - `startAngle?: number` Where the first slice starts, in degrees clockwise from the top. Default: `0`. - `total?: number` The value a full circle stands for. With a total above the sum of the values, the pie stays open, as for a gauge. - `donut?: boolean` Draws a donut: rings instead of filled slices. Default: `false`. - `donutWidth?: string | number` The width of a donut's ring: px as a number, or a share of the radius as a string (`'30%'`). Without it, the stylesheet sets the width (`--ct-donut-width`, 60px by default), and in the browser the ring is laid out for the width the stylesheet gives a slice. Without a DOM (`renderToString()`), it's laid out for 60 px. - `showLabel?: boolean` Draws a label for each slice. Default: `true`. - `labelOffset?: number` Moves the labels away from the center (positive) or toward it (negative), in px. Default: `0`. - `labelPosition?: RadialLabelPosition` Where labels go: `inside` (halfway out on a pie, on the middle of a donut's ring, plus `labelOffset`), `outside` (at the outer edge) or `center` (in the middle; move them with `labelOffset`). Default: `'inside'`. - `labelTransform?: (value: Label, index: number) => Label | null | undefined` Turns each slice's label (or value) and index into the text to show. Return `null` or `undefined` to leave a label out. Wins over `labelInterpolationFnc` when both are set. - `labelInterpolationFnc?: (value: Label, index: number) => Label | null | undefined` The 1.x name of `labelTransform`, which wins when both are set. - `labelDirection?: LabelDirection` Which way labels anchor: `neutral`, `explode` (away from the center, for labels outside the pie) or `implode` (toward it). Default: `'neutral'`. - `ignoreEmptyValues?: boolean` Leaves out the slices and labels of empty values. Default: `false`. - `preventOverlappingLabelOffset?: number` Moves labels apart so they don't overlap, using this offset in px. The overlap is judged by the length of the drawn label text. 0 leaves them where they are. Default: `0`. - `width?: string | number` The chart's width: px as a number, or a CSS length such as `'100%'`. Without it, the chart takes the width of its container. - `height?: string | number` The chart's height: px as a number, or a CSS length such as `'300px'`. Without it, the chart takes the height of its container. - `low?: number` The lowest value the value axis shows, instead of the data's lowest. Values below it are drawn outside the drawing area. - `high?: number` The highest value the value axis shows, instead of the data's highest. Values above it are drawn outside the drawing area. - `referenceValue?: number` A value the value axis always includes, such as 0, so it shows even when all values are above or below it. Bar charts use 0. - `chartPadding?: number | Partial` Space in px between the chart's edges and its drawing area (labels sit in it): one number for every side, or an object with some sides. Default: `{ top: 5, right: 5, bottom: 5, left: 5 }`. - `plugins?: (Plugin | [Plugin, any] | { … })[]` 1.x plugin functions (they run with the compat plugin) and v2 plugins such as `compat`. - `viewBox?: ViewBox` A `viewBox` for the SVG, so the drawing scales with its box instead of being drawn for its size. Text scales too: adjust it with responsive options. - `viewBox.width: number` The viewBox width, in SVG units. - `viewBox.height: number` The viewBox height, in SVG units. ### PieChartOptionsWithDefaults *1.x* ```ts type PieChartOptionsWithDefaults = RequiredKeys ``` [`PieChartOptions`](https://chartist.dev/api/classes/#PieChartOptions) after the defaults are applied: the 1.x `PieChart` works with these. ### PieChartCreatedEvent *1.x* ```ts type PieChartCreatedEvent = Omit, 'axisX' | 'axisY'> ``` The payload of a pie chart's 1.x `created` event: as for other charts, without axes. Properties: - `chartRect: ChartRect` The drawing area, in px. - `svg: Svg` The chart's SVG element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `options: TOptions` The options the chart was drawn with, defaults and responsive options applied. ### SliceDrawEvent *1.x* ```ts interface SliceDrawEvent extends Omit ``` The 1.x `draw` event of a pie or donut slice, one per value. `index` is the slice's index and `series` its value as given in the data. Properties: - `type: 'slice'` Always `'slice'`. - `value: NormalizedSeriesPrimitiveValue` The slice's value. - `totalDataSum: number` The value of the full circle: the sum of all values, or the `total` option. - `path: SvgPath` The slice's path as drawn (a copy, safe to change). - `center: Dot` The center of the pie, in px. - `radius: number` The radius of the pie, in px. - `startAngle: number` Where the slice starts, in degrees clockwise from the top. - `endAngle: number` Where the slice ends, in degrees clockwise from the top. - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `chartRect: ChartRect` The drawing area, in px. - `meta: any` The `meta` of the value (or of the series, for lines and areas). - `index: number` The index of the value in its series (of the series, for lines and areas). - `series: SeriesObject | FlatSeriesValue | Series` The series as given in the data: an array or a series object. ### SliceLabelDrawEvent *1.x* ```ts interface SliceLabelDrawEvent extends Omit ``` The 1.x `draw` event of a pie slice's label. `index` is the slice's index; the label is a `text` element anchored at `x`, `y`. Properties: - `type: 'label'` Always `'label'`. - `text: string` The label as shown, after `labelTransform`. - `x: number` The x the label is anchored at, in px. - `y: number` The y the label is anchored at, in px. - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `chartRect: ChartRect` The drawing area, in px. - `meta: any` The `meta` of the value (or of the series, for lines and areas). - `index: number` The index of the value in its series (of the series, for lines and areas). - `series: SeriesObject | FlatSeriesValue | Series` The series as given in the data: an array or a series object. ### PieChartEventsTypes *1.x* ```ts type PieChartEventsTypes = BaseChartEventsTypes ``` The 1.x events of a [`PieChart`](https://chartist.dev/api/classes/#PieChart) and their payloads, by event name. Properties: - `data: DataEvent` - `options: OptionsChangedEvent` - `animationBegin: AnimationEvent` - `animationEnd: AnimationEvent` - `created: TCreateEvent` - `draw: TDrawEvents` ### BaseChartEventsTypes *1.x* ```ts interface BaseChartEventsTypes ``` The 1.x events every chart class emits and their payloads, by event name. Properties: - `data: DataEvent` - `options: OptionsChangedEvent` - `animationBegin: AnimationEvent` - `animationEnd: AnimationEvent` - `created: TCreateEvent` - `draw: TDrawEvents` ### OptionsProvider *1.x* ```ts interface OptionsProvider ``` What [`optionsProvider`](https://chartist.dev/api/classes/#optionsProvider) returns: the current options, and a way to stop listening to media queries. Properties: - `removeMediaQueryListeners: () => void` - `getCurrentOptions: () => T` ### ChartPadding *1.x* ```ts interface ChartPadding ``` Space in px between the chart's edges and its drawing area, per side. Properties: - `top: number` Space above the drawing area, in px. - `right: number` Space right of the drawing area, in px. - `bottom: number` Space below the drawing area, in px. - `left: number` Space left of the drawing area, in px. ### Plugin *1.x* ```ts type Plugin = (chart: any, options?: any) => void ``` A 1.x plugin: a function that gets the chart object (and its options) and registers listeners with `chart.on('draw', …)`. It runs with the `compat` plugin from `chartist/compat`, whose `fromLegacyPlugin()` also turns it into a v2 plugin. ### Options *1.x* ```ts interface Options ``` The options all 1.x charts share: size, value range, padding, axes, class names and plugins. [`LineChartOptions`](https://chartist.dev/api/classes/#LineChartOptions), [`BarChartOptions`](https://chartist.dev/api/classes/#BarChartOptions) and [`PieChartOptions`](https://chartist.dev/api/classes/#PieChartOptions) build on them. Properties: - `width?: string | number` The chart's width: px as a number, or a CSS length such as `'100%'`. Without it, the chart takes the width of its container. - `height?: string | number` The chart's height: px as a number, or a CSS length such as `'300px'`. Without it, the chart takes the height of its container. - `low?: number` The lowest value the value axis shows, instead of the data's lowest. Values below it are drawn outside the drawing area. - `high?: number` The highest value the value axis shows, instead of the data's highest. Values above it are drawn outside the drawing area. - `referenceValue?: number` A value the value axis always includes, such as 0, so it shows even when all values are above or below it. Bar charts use 0. - `chartPadding?: number | Partial` Space in px between the chart's edges and its drawing area (labels sit in it): one number for every side, or an object with some sides. - `axisX?: TXAxisOptions` Options of the X axis. - `axisY?: TYAxisOptions` Options of the Y axis. - `classNames?: Record` The class names of the chart's elements, to style a chart with your own classes instead of Chartist's. - `plugins?: (Plugin | [Plugin, any] | { … })[]` 1.x plugin functions (they run with the compat plugin) and v2 plugins such as `compat`. - `viewBox?: ViewBox` A `viewBox` for the SVG, so the drawing scales with its box instead of being drawn for its size. Text scales too: adjust it with responsive options. ### AxisOptions *1.x* ```ts interface AxisOptions ``` The options of one axis of a line or bar chart. Properties: - `type?: AxisType` The axis type: `StepAxis` (one step per label), `AutoScaleAxis` (a scale fitted to the values) or `FixedScaleAxis` (fixed ticks). - `low?: number` The lowest value this axis shows, instead of the data's lowest. - `high?: number` The highest value this axis shows, instead of the data's highest. - `referenceValue?: number` A value this axis always includes, such as 0. - `offset?: number` Room in px for the axis labels, between the drawing area and the chart's edge. - `position?: 'end' | 'start'` Where the labels go: `start` (left or top) or `end` (right or bottom). - `labelOffset?: { … }` Moves the labels by `x` and `y` px from where they'd go. - `showLabel?: boolean` Draws the axis labels. - `labelAlign?: Partial` Aligns this axis's SVG text labels in their box, over the `align` of [`axisLabels`](https://chartist.dev/api/layers/#axisLabels). HTML labels ignore it (align them with CSS). - `showGrid?: boolean` Draws the grid lines of the axis. - `labelTransform?: (value: Label, index: number) => Label | null | undefined` Turns each label (and its index) into the text to show. Return `null` or `undefined` to leave a label out. Wins over `labelInterpolationFnc` when both are set. - `labelInterpolationFnc?: (value: Label, index: number) => Label | null | undefined` The 1.x name of [`AxisOptions.labelTransform`](https://chartist.dev/api/classes/#AxisOptions.labelTransform), which wins when both are set. - `scaleMinSpace?: number` The minimum space in px between two grid lines of a scale: the scale takes fewer, larger steps to keep it. - `onlyInteger?: boolean` Only whole numbers on the scale. - `ticks?: Label[]` The ticks: the labels of a step axis, or the values of a fixed scale axis. - `stretch?: boolean` Step axis: spreads the ticks over the whole axis, so the last one lands on its end (line charts with `fullWidth` set it). - `divisor?: number` Fixed scale axis without `ticks`: the number of equal steps between its low and high, with a tick at each end. - `highLow?: { … }` The value range of a scale as `{ high, low }`, instead of the one computed from the data. ### OptionsWithDefaults *1.x* ```ts type OptionsWithDefaults = RequiredKeys, RequiredKeys>, 'axisX' | 'axisY' | 'classNames'> ``` [`Options`](https://chartist.dev/api/classes/#Options) after the defaults are applied: the axes and class names are set. ### ResponsiveOptions *1.x* ```ts type ResponsiveOptions = [string, T][] ``` The 1.x responsive options: `[mediaQuery, options]` pairs, applied over the chart's options while the media query matches. ### CreatedEvent *1.x* ```ts interface CreatedEvent ``` The payload of the 1.x `created` event, emitted once a chart is drawn: its SVG, drawing area, axes and options. Properties: - `chartRect: ChartRect` The drawing area, in px. - `axisX: Axis` The X axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `axisY: Axis` The Y axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `svg: Svg` The chart's SVG element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `options: TOptions` The options the chart was drawn with, defaults and responsive options applied. ### DrawEvent *1.x* ```ts interface DrawEvent ``` The fields every 1.x `draw` event shares. A `draw` listener gets one per element drawn; check `type` to tell them apart. Properties: - `type: string` What was drawn: `'point'`, `'line'`, `'bar'`, `'grid'`, … - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `chartRect: ChartRect` The drawing area, in px. - `axisX: Axis` The X axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `axisY: Axis` The Y axis, a 1.x [`Axis`](https://chartist.dev/api/classes/#Axis) object. - `meta: any` The `meta` of the value (or of the series, for lines and areas). - `index: number` The index of the value in its series (of the series, for lines and areas). - `series: SeriesObject | FlatSeriesValue | Series` The series as given in the data: an array or a series object. - `seriesIndex: number` The index of the series in the data. ### DataEvent *1.x* ```ts interface DataEvent ``` The payload of the 1.x `data` event, emitted before a chart draws new data. Listeners may change `data` in place to transform it. Properties: - `type: 'update' | 'initial'` `'initial'` for the first data, `'update'` for data set later. - `data: Data` The data the chart is about to draw (a copy, safe to change). ### OptionsChangedEvent *1.x* ```ts interface OptionsChangedEvent ``` The payload of the 1.x `optionsChanged` event, emitted when a responsive option starts or stops applying. Properties: - `previousOptions: T` The options before the change. - `currentOptions: T` The options now in effect. ### GridDrawEvent *1.x* ```ts interface GridDrawEvent extends Omit ``` The 1.x `draw` event of a grid line, one per tick of each axis. `x1`, `y1`, `x2`, `y2` are the line's ends in px. Properties: - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `index: number` The index of the value in its series (of the series, for lines and areas). - `type: 'grid'` Always `'grid'`. - `axis: Axis` The axis the grid line belongs to. - `x1: number` The x of the line's start, in px. - `y1: number` The y of the line's start, in px. - `x2: number` The x of the line's end, in px. - `y2: number` The y of the line's end, in px. ### GridBackgroundDrawEvent *1.x* ```ts interface GridBackgroundDrawEvent ``` The 1.x `draw` event of the grid background, the `rect` behind the drawing area (with `showGridBackground`). Properties: - `type: 'gridBackground'` Always `'gridBackground'`. - `group: Svg` The grid group the background is in. - `element: Svg` The background `rect`. ### LabelDrawEvent *1.x* ```ts interface LabelDrawEvent extends Omit ``` The 1.x `draw` event of an axis label, one per tick. The label is a `foreignObject` with a `span`, placed at `x`, `y` with the given size. Properties: - `element: Svg` The drawn element, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg): change or animate it here. - `group: Svg` The group the element is in, wrapped in [`Svg`](https://chartist.dev/api/svg/#Svg). - `index: number` The index of the value in its series (of the series, for lines and areas). - `type: 'label'` Always `'label'`. - `axis: Axis` The axis the label belongs to. - `text: Label` The label as shown, after `labelTransform`. - `x: number` The x of the label's box, in px. - `y: number` The y of the label's box, in px. - `width: number` The width of the label's box, in px. - `height: number` The height of the label's box, in px. ### AxesDrawEvent *1.x* ```ts type AxesDrawEvent = GridDrawEvent | GridBackgroundDrawEvent | LabelDrawEvent ``` The 1.x `draw` events of the axes: grid lines, the grid background and labels. ### EventListener *1.x* ```ts type EventListener = (data: T) => void ``` A listener of one event: gets the event's payload. ### AllEventsListener *1.x* ```ts type AllEventsListener = (event: string, data: T) => void ``` A listener of every event (`'*'`): gets the event's name and payload. ## Helpers (Chartist API) > The data, math and utility functions the charts are built from, for plugins and custom layers. ### axisUnits ```ts const axisUnits: { readonly x: { readonly pos: 'x'; readonly len: 'width'; readonly dir: 'horizontal'; readonly rectStart: 'x1'; readonly rectEnd: 'x2'; readonly rectOffset: 'y2' }; readonly y: { readonly pos: 'y'; readonly len: 'height'; readonly dir: 'vertical'; readonly rectStart: 'y2'; readonly rectEnd: 'y1'; readonly rectOffset: 'x1' } } ``` The names an axis uses per dimension: its position, length and direction, and its edges in the [`ChartRect`](https://chartist.dev/api/helpers/#ChartRect). ### namespaces ```ts const namespaces: Record ``` This object contains all namespaces used within Chartist. ### precision ```ts const precision: 8 ``` Precision level used internally in Chartist for rounding. If you require more decimal places you can increase this number. ### escapingMap ```ts const escapingMap: Record ``` A map with characters to escape for strings to be safely used as attribute values. ### normalizePadding ```ts function normalizePadding(padding: number | Partial | undefined): { top: number; right: number; bottom: number; left: number } ``` Turns a padding option into a full [`ChartPadding`](https://chartist.dev/api/classes/#ChartPadding): a number pads every side, an object keeps its numeric sides and sets the missing ones to 0, and `undefined` gives 0 on every side. ### createChartRect ```ts function createChartRect(svg: Svg, options: Options): { x1: number; x2: number; y1: number; y2: number; padding: { top: number; right: number; bottom: number; left: number }; width: () => number; height: () => number } ``` Initialize chart drawing rectangle (area where chart is drawn) x1,y1 = bottom left / x2,y2 = top right ### getBounds ```ts function getBounds(axisLength: number, highLow: { high: number; low: number }, scaleMinSpace: number, onlyInteger?: boolean): Bounds ``` Calculate and retrieve all the bounds for the chart and return them in one array ### getMetaData ```ts function getMetaData(seriesData: FlatSeriesValue | Series | SeriesObject, index: number): any ``` Get meta data of a specific value in a series. ### isDataHoleValue ```ts function isDataHoleValue(value: unknown): value is null | undefined ``` Checks if a value is considered a hole in the data series. ### isArrayOfSeries ```ts function isArrayOfSeries(value: unknown): value is (Series | SeriesObject)[] ``` Checks if value is array of series objects. ### isMultiValue ```ts function isMultiValue(value: unknown): value is Multi ``` Checks if provided value object is multi value (contains x or y properties) ### getMultiValue ```ts function getMultiValue(value: unknown, dimension?: AxisName): number | undefined ``` Gets a value from a dimension `value.x` or `value.y` while returning value directly if it's a valid numeric value. If the value is not numeric and it's falsey this function will return `defaultValue`. ### getHighLow ```ts function getHighLow(data: NormalizedSeries[], options: Options, dimension?: AxisName, extent?: { readonly low: number; readonly high: number }): { high: number; low: number } ``` Get highest and lowest value of data array. This Array contains the data that will be visualized in the chart. ### normalizeData ```ts function normalizeData(data: Data, reverse?: boolean, multi?: false): NormalizedData function normalizeData(data: Data<(Series | SeriesObject)[]>, reverse: boolean | undefined, multi: true | AxisName): NormalizedData function normalizeData(data: Data<(Series | SeriesObject)[] | FlatSeries>, reverse: boolean | undefined, multi: boolean | AxisName, distributed: true): NormalizedData function normalizeData(data: Data<(Series | SeriesObject)[] | FlatSeries>, reverse?: boolean, multi?: boolean | AxisName): NormalizedData ``` Ensures that the data object passed as second argument to the charts is present and correctly initialized. ### splitIntoSegments ```ts function splitIntoSegments(pathCoordinates: number[], valueData: SegmentData[], options?: { increasingX?: boolean; fillHoles?: boolean }): Segment[] ``` Splits a list of coordinates and associated values into segments. Each returned segment contains a pathCoordinates valueData property describing the segment. With the default options, segments consist of contiguous sets of points that do not have an undefined value. Any points with undefined values are discarded. **Options** The following options are used to determine how segments are formed ```javascript var options = { // If fillHoles is true, undefined values are simply discarded without creating a new segment. Assuming other options are default, this returns single segment. fillHoles: false, // If increasingX is true, the coordinates in all segments have strictly increasing x-values. increasingX: false }; ``` ## serialize ```ts function serialize(data: string | number | object): string function serialize(data: unknown): string | null | undefined ``` This function serializes arbitrary data to a string. In case of data that can't be easily converted to a string, this function will create a wrapper object and serialize the data using JSON.stringify. The outcoming string will always be escaped using Chartist.escapingMap. If called with null or undefined the function will return immediately with null or undefined. ## deserialize ```ts function deserialize(data: string): T function deserialize(data: string | null | undefined): T | null | undefined ``` This function de-serializes a string previously serialized with Chartist.serialize. The string will always be unescaped using Chartist.escapingMap before it's returned. Based on the input value the return type can be Number, String or Object. JSON.parse is used with try / catch to see if the unescaped string can be parsed into an Object and this Object will be returned on success. ## ensureUnit ```ts function ensureUnit(value: T, unit: string): string | T ``` Converts a number to a string with a unit. If a string is passed then this will be returned unmodified. ## quantity ```ts function quantity(input: T): { value: number; unit: string | undefined } | { unit?: undefined; value: number } ``` Converts a number or string to a quantity object. ## alphaNumerate ```ts function alphaNumerate(n: number): string ``` Generates a-z from a number 0 to 26 ## EPSILON ```ts const EPSILON: 2.221e-16 ``` A tiny number, about the gap between 1 and the next float: the tolerance of float comparisons. ## orderOfMagnitude ```ts function orderOfMagnitude(value: number): number ``` Calculate the order of magnitude for the chart scale ## projectLength ```ts function projectLength(axisLength: number, length: number, bounds: Bounds): number ``` Project a data length into screen coordinates (pixels) ## roundWithPrecision ```ts function roundWithPrecision(value: number, digits?: number): number ``` Rounds a value to a number of digits after the decimal point, to remove float errors such as `0.30000000000000004`. `0` digits round to whole numbers. ## rho ```ts function rho(num: number): number ``` Pollard Rho Algorithm to find smallest factor of an integer value. There are more efficient algorithms for factorization, but this one is quite efficient and not so complex. ## polarToCartesian ```ts function polarToCartesian(centerX: number, centerY: number, radius: number, angleInDegrees: number): { x: number; y: number } ``` Calculate cartesian coordinates of polar coordinates ## extend ```ts function extend(target: T): T function extend(target: T, a: A): T & A function extend(target: T, a: A, b: B): T & A & B ``` Merges the sources into `target`, deeply, and returns `target`. Plain objects merge key by key into an object already there, and replace any other value (an object over a number gives a copy of the object); arrays, from any realm, and other values replace what's there. Keys of the target's prototype are skipped, against prototype pollution. ## noop ```ts function noop(n: T): T ``` Helps to simplify functional style code ## times ```ts function times(length: number): undefined[] function times(length: number, filler: (index: number) => T): T[] ``` Functional style helper to produce array with given length initialized with undefined values ## sum ```ts function sum(previous: number, current: number): number ``` Sum helper to be used in reduce functions ## serialMap ```ts function serialMap(array: T[][], callback: (...args: T[]) => K): K[] ``` Map for multi dimensional arrays where their nested arrays will be mapped in serial. The output array will have the length of the largest nested array. The callback function is called with variable arguments where each argument is the nested array value (or undefined if there are no more values). For example: ## safeHasProperty ```ts function safeHasProperty(target: T, property: K): target is FilterByKey ``` This function safely checks if an objects has an owned property. ## isNumeric ```ts function isNumeric(value: number): true function isNumeric(value: unknown): boolean ``` Checks if a value can be safely coerced to a number. This includes all values except null which result in finite numbers when coerced. This excludes NaN, since it's not finite. ## isFalseyButZero ```ts function isFalseyButZero(value: unknown): value is false | '' | null | undefined ``` Returns true on all falsey values except the numeric value 0. ## getNumberOrUndefined ```ts function getNumberOrUndefined(value: number): number function getNumberOrUndefined(value: unknown): number | undefined ``` Returns a number if the passed parameter is a valid number or the function will return undefined. On all other values than a valid number, this function will return undefined. ## isArrayOfArrays ```ts function isArrayOfArrays(data: unknown): data is unknown[][] ``` Checks if value is array of arrays or not. ## each ```ts function each(list: T[], callback: (item: T, index: number, itemIndex: number) => void, reverse?: boolean): void ``` Loop over array. ## XAxisUnits ```ts type XAxisUnits = typeof axisUnits.x ``` The units of the X axis: `axisUnits.x`. ## YAxisUnits ```ts type YAxisUnits = typeof axisUnits.y ``` The units of the Y axis: `axisUnits.y`. ## AxisUnits ```ts type AxisUnits = XAxisUnits | YAxisUnits ``` The units of either axis. ## ChartRect ```ts interface ChartRect ``` The drawing area of a 1.x chart in px: `x1`, `y1` is its bottom left corner and `x2`, `y2` its top right (SVG y grows downward, so `y1` is the larger one). ## ViewBox ```ts interface ViewBox ``` The size of an SVG `viewBox`. ## Bounds ```ts interface Bounds ``` The scale [`getBounds`](https://chartist.dev/api/helpers/#getBounds) computes for an axis: its range and steps. ## Segment ```ts interface Segment ``` A run of points without holes, as [`splitIntoSegments`](https://chartist.dev/api/helpers/#splitIntoSegments) returns it. ## SegmentData ```ts interface SegmentData ``` A point's value, its index in the series and its meta. ## AxisName ```ts type AxisName = 'x' | 'y' ``` An axis dimension: `'x'` or `'y'`. ## FilterByKey ```ts type FilterByKey = T extends Record ? T : T extends Partial> ? T & { [key in K]: T[K] } : never ``` Narrows a union `T` to the members that have the key `K` (an optional `K` becomes required). ## RequiredKeys ```ts type RequiredKeys = T & Required> & { [key in V]: Required } ``` `T` with the keys `K` required, and the keys `V` required down one level. ## @chartist-js/terminal > Chartist charts in the terminal and as text art in HTML, from the ct-cli command or from your own CLI tool. Chartist builds every chart as a scene first, and SVG is one way to draw it. `@chartist-js/terminal` draws the same scene with characters: lines, points and areas in braille dots, bars in block characters, slices filled with dots, and the grid in box-drawing lines, all in Chartist's series colors. The layout is Chartist's own, so the labels, the scale and the options you know carry over. This is `lineChart()` with `showArea` and `lineSmooth`, 34 characters wide, as text art in the page, framed as a terminal window with a bit of CSS: **Text art** (runs in the browser): `src/main.ts`: ```ts import '@chartist-js/terminal/styles.css'; import './style.css'; import { lineChart } from '@chartist-js/terminal'; const data = { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [ [12, 9, 7, 8, 5], [2, 1, 3.5, 7, 3] ] }; // The options of Chartist's lineChart(), plus a size in characters, as an // element of text art instead of terminal escape codes. const art = document.querySelector('#art'); if (art) { art.replaceChildren( lineChart(data, { low: 0, showArea: true, lineSmooth: true, columns: 34, rows: 16, format: 'element' }) ); } ``` `src/style.css`: ```css /* A terminal window around the text art, for the looks. It takes the chart's size: more columns or rows make it bigger. */ .window { width: fit-content; max-width: 100%; margin: 0 auto; border-radius: 10px; overflow: hidden; background: #1c1718; box-shadow: 0 1px 0 rgba(255, 255, 255, 0.06) inset, 0 12px 32px rgba(0, 0, 0, 0.35); } .title-bar { display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: #2b2426; } .dot { width: 12px; height: 12px; border-radius: 50%; background: #ff5f57; } .dot:nth-child(2) { background: #febc2e; } .dot:nth-child(3) { background: #28c840; } .title { flex: 1; margin-right: 52px; text-align: center; color: #a99a92; font: 600 12px system-ui, sans-serif; } .screen { padding: 12px; color: #f3eadb; /* Text art reads the same custom properties as SVG charts. */ --ct-label-color: rgb(243 234 219 / 0.7); --ct-grid-color: rgb(243 234 219 / 0.16); } .screen .ct-terminal { margin: 0; font-size: 13px; /* Narrower than the chart, the window cuts it off. */ overflow: hidden; /* Bold labels; the dots and lines come from the stylesheet's own font. */ font-weight: 700; } /* On the dark screen, the area shading needs a little more. */ .screen .ct-area { opacity: 0.75; } ``` `index.html`: ```html Text art
``` ### Install ```sh npm install @chartist-js/terminal ``` It brings `chartist` along. The command needs Node.js 22 or later; the API runs in any JavaScript runtime, browsers included. To try the command without installing it: ```sh npx @chartist-js/terminal demo ``` ### Quick start In a terminal, give `ct-cli` a chart type and a series per argument: ```sh npx @chartist-js/terminal line 12,9,7,8,5 2,1,3.5,7,3 --labels Mon,Tue,Wed,Thu,Fri --area ``` In your own CLI tool, the chart functions take the data and options of Chartist's presets, plus a size in characters, and return the chart as a string: ```ts import { barChart } from '@chartist-js/terminal'; console.log( barChart( { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [ [5, 4, 3, 7], [3, 2, 9, 5] ] }, { stackBars: true, columns: 50, rows: 12 } ) ); ``` ### Next - [Terminal rendering](https://chartist.dev/packages/terminal/terminal-rendering/): the command's input and options, the API for CLI tools, and text art in HTML. - [Customizing charts](https://chartist.dev/packages/terminal/customizing-charts/): interpolation, axes, stacked bars and gauges, with Chartist's options. - [Plugins](https://chartist.dev/packages/terminal/plugins/): what the terminal draws of the nodes a plugin adds. - [API](https://chartist.dev/packages/terminal/api/): every function and option of the package. - [Server rendering](https://chartist.dev/docs/server-rendering/): the same scene as SVG markup, without a browser. - [Rendering](https://chartist.dev/api/rendering/) in the Chartist API: `toScene()`, the renderers and text measuring this package builds on. ## Terminal rendering > Draw charts from the ct-cli command or from your own CLI tool, and as text art in HTML, with the data and options of Chartist's presets. `@chartist-js/terminal` draws Chartist's charts with characters. This guide covers the three ways to use it: the `ct-cli` command, the API for your own CLI tools, and text art in HTML. ### From the command line `ct-cli` draws a chart from numbers. Each argument is one series: ```sh npx @chartist-js/terminal line 12,9,7,8,5 2,1,3.5,7,3 --labels Mon,Tue,Wed,Thu,Fri --area npx @chartist-js/terminal bar 5,4,3,7 3,2,9,5 --labels Q1,Q2,Q3,Q4 --stack npx @chartist-js/terminal pie 20,10,30 --labels Bananas,Apples,Kiwis --donut ``` Installed globally (`npm install -g @chartist-js/terminal`), the command is `ct-cli`. `ct-cli demo` shows one of each chart type. Without series arguments, it reads the data from a file (`--data`) or from a pipe. JSON works, and so do rows of numbers, one series per row, with optional labels in the first row and series names in the first column: ```sh printf ',Mon,Tue,Wed\nVisitors,3,5,4\nSign-ups,1,2,3\n' | ct-cli bar --stack ``` `--width` and `--height` set the size in columns and rows, and `ct-cli --help` lists the rest. Colors follow your terminal: 24-bit or 256 colors in one, none when the output goes into a file or a pipe, or when `NO_COLOR` is set. ### In your CLI tool Your tool has numbers and a terminal, so give it a chart. `lineChart()`, `barChart()` and `pieChart()` from `@chartist-js/terminal` take the data and options of Chartist's presets, plus a size in cells, and return a string: ```ts import { detectColorMode, legend, lineChart } from '@chartist-js/terminal'; const data = { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [ { name: 'Visitors', data: [12, 9, 7, 8, 5] }, { name: 'Sign-ups', data: [2, 1, 3.5, 7, 3] } ] }; const color = detectColorMode(process.stdout, process.env); console.log(lineChart(data, { columns: 60, rows: 14, showArea: true, color })); console.log(legend(data, { columns: 60, color })); ``` Every preset option works: `stackBars`, `horizontalBars`, `low` and `high`, `axisY.labelTransform` and so on. The terminal adds `columns` and `rows`, `color` (`truecolor`, `256`, `16` or `none`), `palette` for other series colors, and `format`. #### Any scene The chart functions cover the presets. For a [composed chart](https://chartist.dev/docs/composed-charts/), or a scene a plugin changed, lay it out with `terminalSize()` and render it with `renderToTerminal()`: ```ts import { barChartConfig, toScene } from 'chartist'; import { renderToTerminal, terminalSize } from '@chartist-js/terminal'; const scene = toScene( barChartConfig({ labels: ['Q1', 'Q2', 'Q3'], series: [[5, 4, 3]] }), terminalSize(40, 10) ); console.log(renderToTerminal(scene, { color: 'none' })); ``` [`lineChartConfig()`](https://chartist.dev/api/charts/#lineChartConfig), `barChartConfig()` and `pieChartConfig()` come from `chartist`: they build the config a preset draws, for any renderer. [`toScene()`](https://chartist.dev/api/rendering/#toScene) and the other renderers are in the [Rendering](https://chartist.dev/api/rendering/) part of the API reference. A terminal cell stands for 8 × 16 px of the scene, so a braille dot is 4 × 4 px and circles stay round. Labels are measured one cell per character, so they land on the character grid. ### Text art in HTML `format: 'element'` (or `renderToElement(scene)`) gives the text art as a `pre` element to add to the page, with classes where the terminal has colors: `ct-series-a`, `ct-grid`, `ct-label` and so on. `format: 'html'` (or `renderToHtml(scene)`) gives the same markup as a string, for a server or a file; in the browser, prefer the element, which needs no `innerHTML`. Its stylesheet colors them with [the custom properties](https://chartist.dev/docs/styling-with-css/#custom-properties) your charts already use, so a theme colors the text art too: ```ts import '@chartist-js/terminal/styles.css'; ``` The stylesheet also brings a small font of its own for the dots, grid lines and bars, drawn to fill each character cell, so the text art looks the same in every browser. It's embedded as a `data:` URL, so a page with a Content Security Policy needs `font-src data:` ([Security and CSP](https://chartist.dev/docs/security-and-csp/) has the rest). `ct-cli pie 3,2,1 --html --standalone` writes a whole page. This example draws a donut as text art, framed as a terminal window: **A donut as text art** (runs in the browser): `src/main.ts`: ```ts import '@chartist-js/terminal/styles.css'; import './style.css'; import { pieChart } from '@chartist-js/terminal'; const art = document.querySelector('#art'); if (art) { art.replaceChildren( pieChart( { labels: ['Bananas', 'Apples', 'Kiwis'], series: [20, 10, 30] }, { donut: true, columns: 34, rows: 16, format: 'element' } ) ); } ``` `src/style.css`: ```css /* A terminal window around the text art, for the looks. It takes the chart's size: more columns or rows make it bigger. */ .window { width: fit-content; max-width: 100%; margin: 0 auto; border-radius: 10px; overflow: hidden; background: #1c1718; box-shadow: 0 1px 0 rgba(255, 255, 255, 0.06) inset, 0 12px 32px rgba(0, 0, 0, 0.35); } .title-bar { display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: #2b2426; } .dot { width: 12px; height: 12px; border-radius: 50%; background: #ff5f57; } .dot:nth-child(2) { background: #febc2e; } .dot:nth-child(3) { background: #28c840; } .title { flex: 1; margin-right: 52px; text-align: center; color: #a99a92; font: 600 12px system-ui, sans-serif; } .screen { padding: 12px; color: #f3eadb; /* Text art reads the same custom properties as SVG charts. */ --ct-label-color: rgb(243 234 219 / 0.7); --ct-grid-color: rgb(243 234 219 / 0.16); } .screen .ct-terminal { margin: 0; font-size: 13px; /* Narrower than the chart, the window cuts it off. */ overflow: hidden; /* Bold labels; the dots and lines come from the stylesheet's own font. */ font-weight: 700; } /* On the dark screen, the area shading needs a little more. */ .screen .ct-area { opacity: 0.75; } ``` `index.html`: ```html A donut as text art
``` ### Fonts and limits In a terminal, braille and box-drawing characters need a font that has them. Most terminal fonts do; if the dots look scattered or the grid has gaps, the font is missing some. HTML output doesn't depend on the reader's fonts: the stylesheet brings its own for those characters. Each character of a label is one cell, so wide characters (CJK, emoji) push the columns after them. And it's a printout: no tooltips, no animation. Next: Chartist's options in the terminal in [Customizing charts](https://chartist.dev/packages/terminal/customizing-charts/), [plugins](https://chartist.dev/packages/terminal/plugins/), or the same scene as SVG markup in [server rendering](https://chartist.dev/docs/server-rendering/). ## Customizing charts > Interpolation, axes, options per series, stacked bars and gauges. Chartist's options change the text art the way they change the SVG. The terminal's chart functions take the options of Chartist's presets, and the layout is Chartist's own. So an option changes the text art the way it changes the SVG chart, and what you know from the [Chartist guides](https://chartist.dev/docs/line-charts/) carries over. This page goes through the options you'll reach for first. ### Line interpolation `lineSmooth` takes a function from `Interpolation`, as in the browser. `Interpolation.step()` turns each value into a step, which suits counts that change at a point in time: **A stepped line** (runs in the browser): `src/main.ts`: ```ts import '@chartist-js/terminal/styles.css'; import './style.css'; import { Interpolation } from 'chartist'; import { lineChart } from '@chartist-js/terminal'; const art = document.querySelector('#art'); if (art) { art.replaceChildren( lineChart( { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [ [3, 5, 2, 6, 4], [1, 2, 1, 3, 2] ] }, { // Options of Chartist's lineChart(): a step for each value. lineSmooth: Interpolation.step(), showArea: true, showPoint: false, low: 0, axisY: { onlyInteger: true }, columns: 34, rows: 16, format: 'element' } ) ); } ``` `src/style.css`: ```css /* A terminal window around the text art, for the looks. It takes the chart's size: more columns or rows make it bigger. */ .window { width: fit-content; max-width: 100%; margin: 0 auto; border-radius: 10px; overflow: hidden; background: #1c1718; box-shadow: 0 1px 0 rgba(255, 255, 255, 0.06) inset, 0 12px 32px rgba(0, 0, 0, 0.35); } .title-bar { display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: #2b2426; } .dot { width: 12px; height: 12px; border-radius: 50%; background: #ff5f57; } .dot:nth-child(2) { background: #febc2e; } .dot:nth-child(3) { background: #28c840; } .title { flex: 1; margin-right: 52px; text-align: center; color: #a99a92; font: 600 12px system-ui, sans-serif; } .screen { padding: 12px; color: #f3eadb; /* Text art reads the same custom properties as SVG charts. */ --ct-label-color: rgb(243 234 219 / 0.7); --ct-grid-color: rgb(243 234 219 / 0.16); } .screen .ct-terminal { margin: 0; font-size: 13px; /* Narrower than the chart, the window cuts it off. */ overflow: hidden; /* Bold labels; the dots and lines come from the stylesheet's own font. */ font-weight: 700; } /* On the dark screen, the area shading needs a little more. */ .screen .ct-area { opacity: 0.75; } ``` `index.html`: ```html A stepped line
``` The other interpolations work the same way: `Interpolation.none()` for straight segments, `simple()` and `cardinal()` for curves, and `monotoneCubic()`, the default. A missing value (`null`) breaks the line, and `fillHoles: true` bridges it. [Smooth or straight](https://chartist.dev/docs/line-charts/#smooth-or-straight) and [Gaps in your data](https://chartist.dev/docs/line-charts/#gaps-in-your-data) show them in SVG. ### Options per series Name your series, and `series` sets options for one of them. Here the signups are a stepped area next to the smooth visits: ```ts import { Interpolation } from 'chartist'; import { lineChart } from '@chartist-js/terminal'; console.log( lineChart( { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [ { name: 'visits', data: [5, 2, 4, 2, 7] }, { name: 'signups', data: [1, 3, 2, 5, 3] } ] }, { series: { signups: { lineSmooth: Interpolation.step(), showArea: true, showPoint: false } } } ) ); ``` ### Axes and scales `low` and `high`, `onlyInteger`, `labelTransform` and the axis types work as in [Axes and scales](https://chartist.dev/docs/axes-and-scales/). With `autoScaleAxis()` on the X axis, a line chart becomes a scatter plot: ```ts import { autoScaleAxis } from 'chartist'; import { lineChart } from '@chartist-js/terminal'; console.log( lineChart( { series: [ [ { x: 1, y: 2 }, { x: 3, y: 7 }, { x: 6, y: 4 }, { x: 9, y: 8 } ] ] }, { showLine: false, low: 0, axisX: { type: autoScaleAxis(), onlyInteger: true }, axisY: { labelTransform: value => `${value}k` } } ) ); ``` `fixedScaleAxis()` with `ticks` puts the ticks where you want them. The automatic scales keep their ticks at least two rows apart, so a short chart gets fewer of them, as a small SVG chart does. ### Bars and pies Stacked and horizontal bars, `distributeSeries` and `reverseData` work as on [Bar charts](https://chartist.dev/docs/bar-charts/). Pies take `donut`, `startAngle`, `total` and `labelTransform`, so a gauge is half a donut: ```ts import { barChart, pieChart } from '@chartist-js/terminal'; console.log( barChart( { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [ [5, 4, 3, 7], [3, 2, 5, 3] ] }, { stackBars: true, axisY: { onlyInteger: true } } ) ); // Half a donut, starting on the left. console.log( pieChart( { series: [20, 10, 30, 40] }, { donut: true, startAngle: 270, total: 200 } ) ); ``` Where two stacked bars meet inside a cell, the cell shows both: the upper series as a block on the lower one's background. ### What stays in the browser - **Styling.** A cell has a character and a color. The terminal colors by series, from `palette` or, in HTML, from [the custom properties](https://chartist.dev/docs/styling-with-css/#custom-properties); line widths, dashes and your own classes don't reach it. - **Size.** There are no `responsiveOptions` and no container to measure: `columns` and `rows` set the size. - **Interaction.** No animation, no tooltips: the output is text. Next: [plugins](https://chartist.dev/packages/terminal/plugins/) in the terminal, or every option of the chart functions on the [API page](https://chartist.dev/packages/terminal/api/). ## Plugins > Plugins and layers change the scene, and the terminal draws the scene. A plugin that adds lines, circles and text works in the terminal as it does in SVG. A Chartist plugin listens to the phases of a chart and changes its scene: it adds nodes, replaces them or drops them. The terminal draws that same scene, so a plugin needs no terminal version. Pass it in `plugins`, as you would on an SVG chart. ### Marking values This plugin keeps a circle on each value that reaches a target and drops the other points. The target line itself is a layer from the core, `targetLine()`, added with the `layers` option. The plugin is an ordinary 2.x plugin, written with nothing but `chartist`: **A plugin that marks reached values** (runs in the browser): `src/reached.ts`: ```ts import { node, type ChartPlugin } from 'chartist'; /** * Keeps a circle on each value that reaches the target. The other points * go. */ export const reached = (target: number): ChartPlugin => ({ name: 'reached', install(chart) { chart.on('compose', current => { if (current.role !== 'point') { return; } const { x, y, value } = current.ctx; const hit = value && typeof value === 'object' && 'y' in value ? (value.y ?? 0) >= target : false; // A circle with the point's role, context and key, or null to drop it. return hit ? node('circle', { cx: x, cy: y, r: 6 }, 'ct-hit', { role: current.role, ctx: current.ctx, key: current.key }) : null; }); } }); ``` `src/main.ts`: ```ts import '@chartist-js/terminal/styles.css'; import './style.css'; import { targetLine } from '@chartist-js/essentials'; import { lineChart } from '@chartist-js/terminal'; import { reached } from './reached'; // The same layer and plugin work on lineChart() from chartist, in SVG. const art = document.querySelector('#art'); if (art) { art.replaceChildren( lineChart( { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], series: [[620, 780, 940, 1050, 990, 1210]] }, { low: 0, layers: { above: [ targetLine({ value: 1000, label: 'target', labelPosition: 'start' }) ] }, plugins: [reached(1000)], columns: 34, rows: 16, format: 'element' } ) ); } ``` `src/style.css`: ```css /* A terminal window around the text art, for the looks. It takes the chart's size: more columns or rows make it bigger. */ .window { width: fit-content; max-width: 100%; margin: 0 auto; border-radius: 10px; overflow: hidden; background: #1c1718; box-shadow: 0 1px 0 rgba(255, 255, 255, 0.06) inset, 0 12px 32px rgba(0, 0, 0, 0.35); } .title-bar { display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: #2b2426; } .dot { width: 12px; height: 12px; border-radius: 50%; background: #ff5f57; } .dot:nth-child(2) { background: #febc2e; } .dot:nth-child(3) { background: #28c840; } .title { flex: 1; margin-right: 52px; text-align: center; color: #a99a92; font: 600 12px system-ui, sans-serif; } .screen { padding: 12px; color: #f3eadb; /* Text art reads the same custom properties as SVG charts. */ --ct-label-color: rgb(243 234 219 / 0.7); --ct-grid-color: rgb(243 234 219 / 0.16); } .screen .ct-terminal { margin: 0; font-size: 13px; /* Narrower than the chart, the window cuts it off. */ overflow: hidden; /* Bold labels; the dots and lines come from the stylesheet's own font. */ font-weight: 700; } /* On the dark screen, the area shading needs a little more. */ .screen .ct-area { opacity: 0.75; } ``` `index.html`: ```html A plugin that marks reached values
``` How it works: - The `compose` listener sees every point with its value in `ctx.value`. - It returns a `circle` with the point's `role`, `ctx` and `key`, or `null` to drop the point. The circles stay in their series' group, so they take its color. On an SVG chart, the layer and the plugin go into the same options: ```ts nocheck import { lineChart } from 'chartist'; import { targetLine } from '@chartist-js/essentials'; import { reached } from './reached'; lineChart('#chart', data, { low: 0, layers: { above: [ targetLine({ value: 1000, label: 'target', labelPosition: 'start' }) ] }, plugins: [reached(1000)] }); ``` [Events and plugins](https://chartist.dev/docs/events-and-plugins/) covers the phases, the scene nodes and [writing a plugin](https://chartist.dev/docs/events-and-plugins/#writing-a-plugin). ### What the terminal draws Nodes with one of Chartist's roles (`line`, `point`, `bar`, `slice`, `band` and the others) are drawn as the terminal draws that role. Every other node is drawn by its element: | Element | Drawn as | | ----------------------- | ---------------------------------------------------- | | `line` | a braille line between its two ends | | `path` | its outline as a braille line, curves flattened | | `circle` | a filled disc of its radius, or a point if it's tiny | | `text`, `foreignObject` | a label, one cell per character | | anything else | nothing, but its children are drawn | That goes for nodes a plugin replaced too. The circles above keep the role `point`, but they're circles now, so the terminal draws them as circles. A node in a series' group, or with a `ct-series-x` class, takes that series' color. Other lines and points use the text color, and text the label color. Styles (`stroke-width`, dashes) and the plugin's own classes don't show: a cell has a character and a color, and in HTML its classes are the kind of mark and the series. ### Plugins that need a page A terminal chart is laid out and drawn, never mounted. Listeners of the `render` and `mount` phases don't run, and calling `update()` on the chart throws. So plugins that work with the page, such as tooltips, hover effects and zoom, have nothing to do here. Plugins that change the scene in `compose` or `scene` work. Next: Chartist's options in the terminal in [Customizing charts](https://chartist.dev/packages/terminal/customizing-charts/), or every function on the [API page](https://chartist.dev/packages/terminal/api/). ## @chartist-js/terminal API > Every function and type @chartist-js/terminal exports. Types from `chartist` link to the Chartist API reference. ### renderToTerminal ```ts function renderToTerminal(scene: SceneNode, options?: TerminalRenderOptions): string ``` Renders a scene as text for a terminal: lines, points and areas in braille dots, bars in block characters, slices filled, the grid in dim box-drawing characters, labels as text, all colored by series with ANSI escape codes. Lay the scene out with [`terminalSize`](https://chartist.dev/packages/terminal/api/#terminalSize), so its px map to cells. Parameters: - `scene: SceneNode` - `options?: TerminalRenderOptions` ```ts import { lineChartConfig, toScene } from 'chartist'; import { renderToTerminal, terminalSize } from '@chartist-js/terminal'; const scene = toScene( lineChartConfig({ labels: ['Mon', 'Tue', 'Wed'], series: [[5, 2, 4]] }), terminalSize(60, 15) ); console.log(renderToTerminal(scene, { color: '256' })); ``` ### lineChart ```ts function lineChart(data: LineChartData, options: Omit & TerminalChartOptions & AsElement): HTMLPreElement function lineChart(data: LineChartData, options?: Omit & TerminalChartOptions & AsString): string function lineChart(data: LineChartData, options?: TerminalLineChartOptions): string | HTMLPreElement ``` Renders a line chart for the terminal, from the data and options of Chartist's `lineChart`, at `columns` × `rows` cells: lines and areas in braille dots, colored by series. Parameters: - `data: LineChartData` - `options: Omit & TerminalChartOptions & AsElement` ```ts import { lineChart } from '@chartist-js/terminal'; console.log( lineChart( { labels: ['Mon', 'Tue', 'Wed', 'Thu'], series: [[12, 9, 7, 8], [2, 1, 3.5, 7]] }, { columns: 60, rows: 15, showArea: true, color: '256' } ) ); ``` ### barChart ```ts function barChart(data: BarChartData, options: Omit & TerminalChartOptions & AsElement): HTMLPreElement function barChart(data: BarChartData, options?: Omit & TerminalChartOptions & AsString): string function barChart(data: BarChartData, options?: TerminalBarChartOptions): string | HTMLPreElement ``` Renders a bar chart for the terminal, from the data and options of Chartist's `barChart` (stacked, horizontal and the others), at `columns` × `rows` cells: bars in block characters, colored by series. Parameters: - `data: BarChartData` - `options: Omit & TerminalChartOptions & AsElement` ```ts import { barChart } from '@chartist-js/terminal'; console.log( barChart( { labels: ['Q1', 'Q2', 'Q3'], series: [[5, 4, 3], [3, 2, 9]] }, { stackBars: true, columns: 50, rows: 12 } ) ); ``` ### pieChart ```ts function pieChart(data: PieChartData, options: Omit & TerminalChartOptions & AsElement): HTMLPreElement function pieChart(data: PieChartData, options?: Omit & TerminalChartOptions & AsString): string function pieChart(data: PieChartData, options?: TerminalPieChartOptions): string | HTMLPreElement ``` Renders a pie or donut chart for the terminal, from the data and options of Chartist's `pieChart`, at `columns` × `rows` cells: slices filled with braille dots, each label on its slice. Parameters: - `data: PieChartData` - `options: Omit & TerminalChartOptions & AsElement` ```ts import { pieChart } from '@chartist-js/terminal'; console.log( pieChart( { labels: ['Bananas', 'Apples', 'Kiwis'], series: [20, 10, 30] }, { donut: true, columns: 40, rows: 16 } ) ); ``` ### detectColorMode ```ts function detectColorMode(stream: ColorStream, env: Readonly>): ColorMode ``` Picks the color mode for an output stream from environment variables, the way most CLI tools do: - none when `NO_COLOR` is set, `TERM` is `dumb` or the stream isn't a terminal; - `FORCE_COLOR` overrides that: `0` none, `1` 16 colors, `2` 256 colors, `3` 24-bit; - otherwise 24-bit when `COLORTERM` is `truecolor` or `24bit`, else 256 colors. It reads only what it's given, so it works outside Node.js too. Parameters: - `stream: ColorStream` The output, usually `process.stdout` - `env: Readonly>` The environment variables, usually `process.env` ```ts import { detectColorMode, lineChart } from '@chartist-js/terminal'; const color = detectColorMode(process.stdout, process.env); console.log(lineChart({ series: [[1, 3, 2]] }, { color })); ``` ### CELL_WIDTH ```ts const CELL_WIDTH: 8 ``` The width in px a terminal cell stands for in a scene: a glyph is half as wide as high. ### CELL_HEIGHT ```ts const CELL_HEIGHT: 16 ``` The height in px a terminal cell stands for in a scene: one line. ### measureCells ```ts const measureCells: MeasureText ``` Measures text in terminal cells: each character is one cell wide and one cell high, so labels are laid out on the character grid. ### terminalSize ```ts function terminalSize(columns: number, rows: number): SceneSize ``` The size to lay out a chart at for `columns` × `rows` terminal cells, for `toScene()`: in px, with text measured in cells and SVG text labels. Parameters: - `columns: number` - `rows: number` ### renderToHtml ```ts function renderToHtml(scene: SceneNode): string ``` Renders a scene as HTML text art: the terminal output in a `pre` element (class `ct-terminal`), each run of cells in a span with classes instead of colors: `ct-series-a` to `ct-series-z`, `ct-bg-series-a` for the series behind a cell, and the kind (`ct-grid`, `ct-label`, `ct-area`, `ct-bar`, `ct-line`, `ct-point`, `ct-slice`). Load `@chartist-js/terminal/styles.css` to color it; it reads Chartist's `--ct-series-a` and other custom properties, so themes apply, and draws braille, blocks and grid lines with a font of its own that fills each cell. Each character is an `i` element: one cell. Lay the scene out with [`terminalSize`](https://chartist.dev/packages/terminal/api/#terminalSize). Parameters: - `scene: SceneNode` ```ts import { pieChartConfig, toScene } from 'chartist'; import { renderToHtml, terminalSize } from '@chartist-js/terminal'; import '@chartist-js/terminal/styles.css'; const scene = toScene( pieChartConfig({ labels: ['A', 'B'], series: [3, 2] }), terminalSize(30, 12) ); document.querySelector('#chart')!.innerHTML = renderToHtml(scene); ``` ### renderToElement ```ts function renderToElement(scene: SceneNode, options?: ElementOptions): HTMLPreElement ``` Renders a scene as HTML text art like [`renderToHtml`](https://chartist.dev/packages/terminal/api/#renderToHtml), but as a `pre` element built with DOM methods instead of a string. The markup is the same; a page appends the element and needs no `innerHTML`, so it works with a Content Security Policy that requires Trusted Types. Throws where there is no document, as in Node.js. Parameters: - `scene: SceneNode` - `options?: ElementOptions` ```ts import { pieChartConfig, toScene } from 'chartist'; import { renderToElement, terminalSize } from '@chartist-js/terminal'; import '@chartist-js/terminal/styles.css'; const scene = toScene( pieChartConfig({ labels: ['A', 'B'], series: [3, 2] }), terminalSize(30, 12) ); document.querySelector('#chart')!.replaceChildren(renderToElement(scene)); ``` ### legend ```ts function legend(data: LegendData, options: LegendOptions & AsElement): HTMLPreElement function legend(data: LegendData, options?: LegendOptions & AsString): string function legend(data: LegendData, options?: LegendOptions): string | HTMLPreElement ``` Makes a legend for a chart's data: a colored marker and the name of each named series (series objects with a `name`), in series colors. For pie data, whose series are values, the names are the labels. Series without a name are left out but keep their color. Returns an empty string, or an empty `pre` element, when nothing has a name. Parameters: - `data: LegendData` - `options: LegendOptions & AsElement` ```ts import { legend, lineChart } from '@chartist-js/terminal'; const data = { labels: ['Mon', 'Tue', 'Wed'], series: [ { name: 'Visitors', data: [12, 9, 7] }, { name: 'Sign-ups', data: [2, 1, 3] } ] }; console.log(lineChart(data, { columns: 60, rows: 12 })); console.log(legend(data, { columns: 60 })); ``` ### TerminalRenderOptions ```ts interface TerminalRenderOptions ``` Options of [`renderToTerminal`](https://chartist.dev/packages/terminal/api/#renderToTerminal). Properties: - `color?: ColorMode` How colors are written; `none` writes plain text. - `palette?: readonly string[]` Series colors as hex, by series letter (`a` first); Chartist's default series colors otherwise. Series past the end start over. ### TerminalChartOptions ```ts interface TerminalChartOptions ``` The terminal options of the chart functions, next to the 1.x options. Properties: - `columns?: number` The width in terminal columns. - `rows?: number` The height in terminal rows. - `color?: ColorMode` How text output is colored; HTML output uses classes instead. - `palette?: readonly string[]` Series colors as hex for text output, by series letter (`a` first). - `format?: 'html' | 'text' | 'element'` `text` for terminals, `html` for [`renderToHtml`](https://chartist.dev/packages/terminal/api/#renderToHtml) markup, or `element` for the same markup as a `pre` element ([`renderToElement`](https://chartist.dev/packages/terminal/api/#renderToElement)), which a page adds without `innerHTML`. - `document?: Document` The document `element` output is created in; the global `document` by default. ### TerminalLineChartOptions ```ts type TerminalLineChartOptions = Omit & TerminalChartOptions ``` Options of the terminal [`lineChart`](https://chartist.dev/packages/terminal/api/#lineChart): the options of Chartist's `lineChart` and the terminal options. Properties: - `columns?: number` The width in terminal columns. - `rows?: number` The height in terminal rows. - `color?: ColorMode` How text output is colored; HTML output uses classes instead. - `palette?: readonly string[]` Series colors as hex for text output, by series letter (`a` first). - `format?: 'html' | 'text' | 'element'` `text` for terminals, `html` for [`renderToHtml`](https://chartist.dev/packages/terminal/api/#renderToHtml) markup, or `element` for the same markup as a `pre` element ([`renderToElement`](https://chartist.dev/packages/terminal/api/#renderToElement)), which a page adds without `innerHTML`. - `document?: Document` The document `element` output is created in; the global `document` by default. ### TerminalBarChartOptions ```ts type TerminalBarChartOptions = Omit & TerminalChartOptions ``` Options of the terminal [`barChart`](https://chartist.dev/packages/terminal/api/#barChart): the options of Chartist's `barChart` and the terminal options. Properties: - `columns?: number` The width in terminal columns. - `rows?: number` The height in terminal rows. - `color?: ColorMode` How text output is colored; HTML output uses classes instead. - `palette?: readonly string[]` Series colors as hex for text output, by series letter (`a` first). - `format?: 'html' | 'text' | 'element'` `text` for terminals, `html` for [`renderToHtml`](https://chartist.dev/packages/terminal/api/#renderToHtml) markup, or `element` for the same markup as a `pre` element ([`renderToElement`](https://chartist.dev/packages/terminal/api/#renderToElement)), which a page adds without `innerHTML`. - `document?: Document` The document `element` output is created in; the global `document` by default. ### TerminalPieChartOptions ```ts type TerminalPieChartOptions = Omit & TerminalChartOptions ``` Options of the terminal [`pieChart`](https://chartist.dev/packages/terminal/api/#pieChart): the options of Chartist's `pieChart` and the terminal options. Properties: - `columns?: number` The width in terminal columns. - `rows?: number` The height in terminal rows. - `color?: ColorMode` How text output is colored; HTML output uses classes instead. - `palette?: readonly string[]` Series colors as hex for text output, by series letter (`a` first). - `format?: 'html' | 'text' | 'element'` `text` for terminals, `html` for [`renderToHtml`](https://chartist.dev/packages/terminal/api/#renderToHtml) markup, or `element` for the same markup as a `pre` element ([`renderToElement`](https://chartist.dev/packages/terminal/api/#renderToElement)), which a page adds without `innerHTML`. - `document?: Document` The document `element` output is created in; the global `document` by default. ### ColorMode ```ts type ColorMode = 'truecolor' | '256' | '16' | 'none' ``` How text output is colored: 24-bit colors, the 256-color palette, the 16 standard colors, or none (plain text without escape codes). ### ColorStream ```ts interface ColorStream ``` What [`detectColorMode`](https://chartist.dev/packages/terminal/api/#detectColorMode) reads from an output stream. Properties: - `isTTY?: boolean` Whether the stream is a terminal, as `process.stdout.isTTY`. ### ElementOptions ```ts interface ElementOptions ``` Options of [`renderToElement`](https://chartist.dev/packages/terminal/api/#renderToElement). Properties: - `document?: Document` The document to create the element in; the global `document` by default. ### LegendOptions ```ts interface LegendOptions ``` Options of [`legend`](https://chartist.dev/packages/terminal/api/#legend). Properties: - `color?: ColorMode` How text output is colored; HTML output uses classes instead. - `palette?: readonly string[]` Series colors as hex for text output, by series letter (`a` first). - `columns?: number` The width in columns; entries that don't fit go on the next line. - `format?: 'html' | 'text' | 'element'` `text` for terminals, `html` for markup like [`renderToHtml`](https://chartist.dev/packages/terminal/api/#renderToHtml)'s, or `element` for the same markup as a `pre` element, which a page adds without `innerHTML`. - `document?: Document` The document `element` output is created in; the global `document` by default. ## @chartist-js/essentials > The additions people ask for most, as layers you add to a chart, kept out of the core so charts without them stay small. Some things almost every chart grows sooner or later: a title for an axis, a value next to a bar, a legend. In 1.x they came from community plugins, each with its own way to load and its own guess at the padding. `@chartist-js/essentials` makes them layers like `line()` and `targetLine()`, made and released together with Chartist. They're a package of their own so the core stays small: a chart pays only for the layers it imports. Today the package holds axis titles, which take the room they need and never land on your labels, [target](https://chartist.dev/packages/essentials/target-lines/) and [trend lines](https://chartist.dev/packages/essentials/trend-lines/), [value labels](https://chartist.dev/packages/essentials/value-labels/), and a [log axis](https://chartist.dev/packages/essentials/log-axis/) for values that span several orders of magnitude: **Axis titles** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; import { lineChart } from 'chartist'; import { axisTitle } from '@chartist-js/essentials'; // Visits per day, with both axes named. Each title reserves its own room. lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], series: [[1200, 1450, 1380, 1620, 1500, 980, 860]] }, { low: 0, layers: { above: [ axisTitle({ axis: 'x', text: 'Day of the week' }), axisTitle({ axis: 'y', text: 'Visits' }) ] } } ); ``` `index.html`: ```html
``` ### Install ```sh npm install @chartist-js/essentials ``` It brings `chartist` along. Load its stylesheet after Chartist's: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; ``` #### Without a bundler From a CDN, map both packages in an import map. The package imports `chartist` by name, so the map gives it the same copy your page uses: ```html ``` ### Quick start The layers go where Chartist's own extra layers go: into `layers` on the presets, or into the list of a composed chart. ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; import { barChart } from 'chartist'; import { axisTitle } from '@chartist-js/essentials'; barChart( '#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [[5, 4, 3, 7]] }, { layers: { above: [ axisTitle({ axis: 'x', text: 'Quarter' }), axisTitle({ axis: 'y', text: 'Orders (thousands)' }) ] } } ); ``` They're part of the chart's scene, so they render on the server, hydrate and show in the terminal like everything else Chartist draws. ### Next - [Axis titles](https://chartist.dev/packages/essentials/axis-titles/): sides, alignment, several axes and styling. - [Target lines](https://chartist.dev/packages/essentials/target-lines/): goals, limits and averages, across or up and down. - [Trend lines](https://chartist.dev/packages/essentials/trend-lines/): the trend of a series and how well it fits. - [Value labels](https://chartist.dev/packages/essentials/value-labels/): each value next to its point or bar. - [Log axis](https://chartist.dev/packages/essentials/log-axis/): decades, minor grid lines, zero values and trend lines. - [API](https://chartist.dev/packages/essentials/api/): every function and option of the package. - [Custom layers](https://chartist.dev/docs/custom-layers/): how layers like these are made, including the room they reserve. ## Axis titles > Name an axis on any side of the chart. The title reserves its own room, so it never overlaps the labels. A chart of "1200, 1450, 1380" says little until you know they're visits. `axisTitle()` puts a name next to an axis. It measures its text and reserves that much room beside the axis labels, so the drawing area shrinks a little and you don't have to tune `chartPadding` until the title stops covering the labels. **Visits per day** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; import { lineChart } from 'chartist'; import { axisTitle } from '@chartist-js/essentials'; // Visits per day, with both axes named. Each title reserves its own room. lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], series: [[1200, 1450, 1380, 1620, 1500, 980, 860]] }, { low: 0, layers: { above: [ axisTitle({ axis: 'x', text: 'Day of the week' }), axisTitle({ axis: 'y', text: 'Visits' }) ] } } ); ``` `index.html`: ```html
``` ### One title per axis A layer draws one title, for the axis you name with `axis`. On the presets, the axes are `x` and `y`, wherever they sit: ```ts import { lineChart } from 'chartist'; import { axisTitle } from '@chartist-js/essentials'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed'], series: [[12, 9, 7]] }, { axisX: { position: 'start' }, layers: { above: [ axisTitle({ axis: 'x', text: 'Day' }), axisTitle({ axis: 'y', text: 'Visits' }) ] } } ); ``` With `position: 'start'` the X axis is at the top, and so is its title. On horizontal bars, `x` is the value axis at the bottom. An empty `text` draws nothing and takes no room, which is handy in [responsive options](https://chartist.dev/docs/responsive/) that drop the title on small screens. ### Several axes In a [composed chart](https://chartist.dev/docs/composed-charts/), use the ids you gave the axes. Each title sits outside its axis's labels, on the axis's side: **Two Y axes, each named** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; import { autoScaleAxis, axisGrid, axisLabels, bar, cartesianAxes, chart, line, stepAxis } from 'chartist'; import { axisTitle } from '@chartist-js/essentials'; const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun']; // Revenue in EUR as bars on the left axis, ad spend in USD as a line on the // right one, and a title for each axis, by its id. chart('#chart', { data: { labels: months, series: [ { name: 'revenue', data: [42, 48, 51, 47, 58, 63] }, { name: 'ads', data: [6.2, 7.1, 6.8, 5.9, 8.4, 9.0] } ] }, axes: cartesianAxes([ { id: 'month', side: 'bottom', type: stepAxis() }, { id: 'eur', side: 'left', type: autoScaleAxis(), referenceValue: 0, labelTransform: value => `${value}k` }, { id: 'usd', side: 'right', type: autoScaleAxis(), low: 0, labelTransform: value => `${value}k` } ]), layers: [ axisGrid({ axes: ['month', 'eur'] }), bar({ series: ['revenue'], x: 'month', y: 'eur' }), line({ series: ['ads'], x: 'month', y: 'usd', align: 'center' }), axisLabels(), axisTitle({ axis: 'month', text: '2026', align: 'end' }), axisTitle({ axis: 'eur', text: 'Revenue (EUR)' }), // Turned to read from top to bottom, toward the chart. axisTitle({ axis: 'usd', text: 'Ad spend (USD)', flip: true }) ] }); ``` `index.html`: ```html
``` Titles on the same side stack outward in the order of `layers`. With two Y axes on the left, list their titles in the order of the axes, so each title lines up with its own labels. ### Placement | Option | What it does | | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `align` | `start`, `middle` (default) or `end` along the axis. The start of an X axis is its left, the start of a Y axis its bottom. | | `flip` | Left and right titles read from bottom to top; `flip: true` turns them to read from top to bottom. Titles of top and bottom axes don't change. | | `gap` | The space between the title and the axis labels, in px (4 by default). | The right axis above uses `flip: true`, so its title faces the chart, and the year is aligned to the `end` of the X axis. ### Styling The title is an SVG `text` with the classes `ct-axis-title`, `ct-horizontal` (top and bottom) or `ct-vertical` (left and right), and `ct-start` (top and left) or `ct-end` (bottom and right). It looks like the axis labels until you say otherwise: ```css .ct-axis-title { --ct-axis-title-color: #555; --ct-axis-title-size: 0.875rem; font-weight: 600; } ``` `--ct-axis-title-color` and `--ct-axis-title-size` fall back to `--ct-label-color` and `--ct-label-size`, so a page that restyles the labels restyles the titles too. The room follows the size: a larger font reserves a taller line. `className` adds classes (`ct-series-b` gives a title the color of the second series), and `classNames: { title }` replaces `ct-axis-title`. In `compose` listeners and plugins, a title has the role `axisTitle`, with its axis, side, text and position in `ctx`. ### On the server and in the terminal Titles are SVG text in every label mode, so [server rendering](https://chartist.dev/docs/server-rendering/) draws them, `applyStyles()` styles them, and hydration takes them over. On the server, the room comes from Chartist's text estimate. In the browser it comes from the stylesheet's font; a chart that takes over server markup keeps the server's room until its next resize, so hydration repairs nothing. `@chartist-js/terminal` draws them too, vertical titles in a column of their own. ### Coming from chartist-plugin-axistitle The 1.x plugin drew titles in the `created` event and reserved nothing, so its titles needed an `offset` and a `chartPadding` that left room for them. `axisTitle()` reserves the room itself: | `chartist-plugin-axistitle` | `axisTitle()` | | --------------------------- | ----------------------------------------------------------------------------------- | | `axisX` / `axisY` | One layer per axis: `axis: 'x'` or `axis: 'y'` | | `axisTitle` | `text` | | `axisClass` | `classNames: { title }` | | `textAnchor` | `align` | | `flipTitle` | `flip` | | `offset` | Not needed: the title sits in its own room. `gap` sets the space to the labels. | | A function as title | Change the text with `update()` or [responsive options](https://chartist.dev/docs/responsive/). | Next: every option on the [API page](https://chartist.dev/packages/essentials/api/), or how a layer reserves room in [Custom layers](https://chartist.dev/docs/custom-layers/). ## Target lines > Draw goals, limits and averages across a chart with targetLine(), on a preset or in a composed chart, horizontal or vertical. A number on its own says little. Is 950 good? Against a goal of 1000, you see it at a glance. `targetLine()` draws that line: a goal, a limit or an average, across the chart at a value, with a label if you want one. It works on the presets and in composed charts, and you style it with CSS like the rest of the chart. For the trend of a series, see [trend lines](https://chartist.dev/packages/essentials/trend-lines/). It comes from `@chartist-js/essentials`, with its stylesheet after Chartist's: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; ``` ### A target line Presets take extra layers in their `layers` option: `above` draws them on top of the bars or lines, `below` behind them. **A goal line** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; import { barChart } from 'chartist'; import { targetLine } from '@chartist-js/essentials'; barChart( '#chart', { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], series: [[820, 950, 1100, 760, 1240, 980]] }, { low: 0, // Extra layers on top of the bars. layers: { above: [targetLine({ value: 1000, label: 'Goal' })] } } ); ``` `index.html`: ```html
``` [Responsive options](https://chartist.dev/docs/responsive/) can change the layers too: a `layers: { above: [...] }` entry replaces the `above` list and keeps `below`. The Y axis makes room for the line: a goal above all the bars still shows. A `low` or `high` you set on the axis wins, and a line outside it isn't drawn. | Option | What it does | | --------------- | ------------------------------------------------------------------------- | | `value` | Where the line goes, on the axis | | `label` | Text next to the line (optional) | | `className` | Extra class for the line and its label, such as `ct-series-b` for a color | | `axis` | The id of the axis the value is on; by default the first Y axis | | `labelPosition` | `'end'` (default) or `'start'` of the line | ### Several lines One layer draws one line. For a minimum and a goal, add two, and give one a series color with `className`: **A goal and a minimum** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; import { barChart } from 'chartist'; import { targetLine } from '@chartist-js/essentials'; // Monthly sales against a goal and a minimum, on the bar chart preset. barChart( '#chart', { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], series: [[820, 950, 1100, 760, 1240, 780]] }, { low: 0, // One layer per line. layers: { above: [ targetLine({ value: 1000, label: 'Goal' }), targetLine({ value: 800, label: 'Minimum', className: 'ct-series-b' }) ] } } ); ``` `index.html`: ```html
``` ### Vertical lines On an X axis, the lines are vertical. That's where the values are on horizontal bars: the presets call their axes `x` and `y`, so use `targetLine({ axis: 'x', value })`. In a [composed chart](https://chartist.dev/docs/composed-charts/), use your own axis ids. Here the line is the average, computed from the data: **An average on horizontal bars** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; import { barChart } from 'chartist'; import { targetLine } from '@chartist-js/essentials'; const minutes = [34, 52, 41, 27, 48]; const average = minutes.reduce((sum, value) => sum + value, 0) / minutes.length; // Minutes per visit on horizontal bars: the values are on the X axis, so // the average is a vertical line there. barChart( '#chart', { labels: ['Home', 'Docs', 'Examples', 'Plugins', 'Playground'], series: [minutes] }, { horizontalBars: true, reverseData: true, axisY: { offset: 80 }, low: 0, layers: { above: [targetLine({ axis: 'x', value: average, label: 'Average' })] } } ); ``` `index.html`: ```html
``` A target line next to a range: put a [band](https://chartist.dev/docs/bands/) behind it. ### Styling A target line draws in the text color by default, dashed, so it reads as context behind the data. Change it with custom properties, or give it a series color with `className: 'ct-series-c'`: ```css .ct-chart { --ct-target-line-color: #c8102e; --ct-target-line-width: 3px; --ct-target-line-dasharray: none; } ``` The markup: a `line.ct-target-line` and a `text.ct-target-label` per line. In listeners and plugins, find them by the roles `targetLine` and `targetLabel`, as in [events and plugins](https://chartist.dev/docs/events-and-plugins/). Importing `@chartist-js/essentials` declares the roles, so TypeScript knows their contexts. > **Coming from the target line plugin?** `chartist-plugin-targetline` with > `{ value: 1000 }` becomes `layers: { above: [targetLine({ value: 1000 })] }`. > The line keeps the class `ct-target-line`, so your CSS still applies. You > also get labels, any axis, and server rendering. ## Trend lines > Fit a straight line through each series with trendLine(), read how well it fits, and draw exponential trends on a log axis. Values go up and down. Is the series growing anyway? `trendLine()` answers with one straight line through it, dashed and in the series' color. It works on the presets and in composed charts, on bars, lines and points. For a fixed goal or limit, see [target lines](https://chartist.dev/packages/essentials/target-lines/). It comes from `@chartist-js/essentials`, with its stylesheet after Chartist's: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; ``` ### A trend through bars Presets take extra layers in their `layers` option: `above` draws them on top of the bars or lines. On bar charts, `align: 'center'` lines the trend up with the middle of the bars: **Bars with a trend line** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; import { barChart } from 'chartist'; import { trendLine } from '@chartist-js/essentials'; // Monthly sign-ups with the trend they follow. barChart( '#chart', { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug'], series: [[42, 38, 51, 47, 58, 54, 66, 63]] }, { low: 0, // Centered, so the line runs through the middle of the bars. layers: { above: [trendLine({ align: 'center' })] } } ); ``` `index.html`: ```html
``` The line runs from the series' first to its last value. Where it ends below or above the values, the Y axis grows to keep it in view. Holes are skipped, and a series with fewer than two values gets no line. Without options, every series gets a line; `series` picks some by name or index. | Option | What it does | | ------------ | ---------------------------------------------------------------------- | | `series` | The series to fit, by name or index; by default all of them | | `x`, `y` | The ids of the axes to draw on, in a composed chart | | `align` | `'start'` (default) or `'center'` of each step, to line up with bars | | `classNames` | `{ series, line }` replace the classes `ct-series` and `ct-trend-line` | ### Points on a scale The fit is least squares: on a step axis it uses the values' order, on a scale axis their `x`. So a scatter plot gets its regression line: **A regression line** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; import { autoScaleAxis, lineChart } from 'chartist'; import { trendLine } from '@chartist-js/essentials'; // Daily ice cream sales against the day's high, in °C. const days = [ [14, 215], [16, 325], [11, 185], [15, 332], [18, 406], [22, 522], [19, 412], [25, 614], [23, 544], [18, 421], [22, 445], [17, 408], [24, 590], [27, 621], [21, 489], [13, 230], [20, 470], [26, 632] ]; lineChart( '#chart', { series: [days.map(([x, y]) => ({ x, y }))] }, { // Points only: a scatter plot. showLine: false, axisX: { type: autoScaleAxis(), onlyInteger: true }, low: 0, // The least-squares line through the points. layers: { above: [trendLine()] } } ); ``` `index.html`: ```html
``` ### How well it fits A rising line says little when the values jump around it. Each trend line's node has the line's `slope` and `intercept`, and its `correlation`: Pearson's r, from -1 for values that all lie on a falling line, through 0 for no straight trend, to 1 for a rising one. Its square is the R² that spreadsheets show next to a trendline, the share of the ups and downs the line explains. A listener can read it from the scene: **Two trends, two fits** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; import './style.css'; import { alphaNumerate, findAll, lineChart } from 'chartist'; import { trendLine } from '@chartist-js/essentials'; const fit = document.querySelector('#fit')!; const shops = ['Basel', 'Bern']; // Two shops over twelve weeks: one grows steadily, one jumps around. Both // trend lines rise, but only one fits its values well. lineChart( '#chart', { labels: Array.from({ length: 12 }, (_, week) => `${week + 1}`), series: [ { name: shops[0], data: [20, 22, 25, 24, 28, 30, 31, 34, 33, 37, 39, 41] }, { name: shops[1], data: [18, 34, 15, 29, 40, 22, 38, 19, 44, 27, 35, 46] } ] }, { showPoint: false, lineSmooth: false, layers: { above: [trendLine()] }, on: { // Each trend line's node knows its slope and its correlation r; r² // is the share of the ups and downs the line explains. scene: ({ scene }) => { fit.replaceChildren( ...findAll(scene, { role: 'trendLine' }).map(({ ctx }) => { const row = document.createElement('span'); // The series class gives the name its series' color. row.className = `ct-series-${alphaNumerate(ctx.seriesIndex)}`; const r2 = (ctx.correlation ?? 0) ** 2; row.textContent = `${shops[ctx.seriesIndex]}: +${ctx.slope.toFixed(1)} a week, R² ${r2.toFixed(2)}`; return row; }) ); } } } ); ``` `src/style.css`: ```css #fit { display: grid; gap: 0.25em; margin: 0.5em 0 0; font-size: 13px; } #fit span { color: var(--ct-color); } ``` `index.html`: ```html

``` Both shops grow, but only Basel's line describes its weeks. With two values r is always 1 or -1, so a fit needs a few values before it means much. A [tooltip](https://chartist.dev/packages/tooltips/content-and-styling/) can show the same numbers on hover. ### Exponential growth On a [log axis](https://chartist.dev/packages/essentials/log-axis/), the line is fitted to the logarithms of the values, so it's straight there: an exponential trend on a log Y axis, a power law on two log axes. Here an animal's metabolic rate grows with its mass to the power of about ¾: **A power law** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; import { axisGrid, axisLabels, cartesianAxes, chart, line } from 'chartist'; import { logAxis, trendLine } from '@chartist-js/essentials'; // Body mass (kg) and resting metabolic rate (W) from a mouse to an // elephant. The rate grows with the mass to the power of about 3/4 // (Kleiber's law): on two log axes that's a straight line, and the trend // line fitted to the logarithms finds it. const animals = [ { x: 0.02, y: 0.17, meta: 'Mouse' }, { x: 0.3, y: 1.45, meta: 'Rat' }, { x: 4, y: 9.1, meta: 'Cat' }, { x: 30, y: 46, meta: 'Dog' }, { x: 70, y: 80, meta: 'Human' }, { x: 600, y: 395, meta: 'Horse' }, { x: 4000, y: 1800, meta: 'Elephant' } ]; chart('#chart', { data: { series: [animals] }, axes: cartesianAxes([ { id: 'mass', side: 'bottom', type: logAxis({ minorTicks: true }) }, { id: 'rate', side: 'left', type: logAxis({ minorTicks: true }) } ]), layers: [axisGrid(), trendLine(), line({ showLine: false }), axisLabels()], // Room for the last X label, which starts at its tick. padding: { right: 32 } }); ``` `index.html`: ```html
``` The `slope` is then in powers of the base per step, here about 0.75: the rate grows by a factor of 10 to the 0.75 for every tenfold mass. ### Styling A trend line takes its series' color. Its width and dash pattern are custom properties: ```css .ct-chart { --ct-trend-line-width: 1px; --ct-trend-line-dasharray: 2px 2px; } ``` The markup: a `line.ct-trend-line` in a series group (`g.ct-series.ct-series-a`) per line. In listeners and plugins, find them by the role `trendLine`, as in [events and plugins](https://chartist.dev/docs/events-and-plugins/). Importing `@chartist-js/essentials` declares the role, so TypeScript knows its context. ## Value labels > Write each value next to its point or bar with valueLabels(), format the text like a tooltip's, and let labels that don't fit move to where they do. Some charts are read for their numbers. A reader who needs to know that Q4 made 15, not "about 15", shouldn't have to hover. `valueLabels()` writes each value next to the point or bar that shows it: above points, past the ends of bars, below them for negative values, and in the middle of stacked segments. It comes from `@chartist-js/essentials`, with its stylesheet after Chartist's: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; ``` ### Labels on bars Add it as an extra layer, `above` on a preset: **Profit per quarter** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; import './style.css'; import { barChart } from 'chartist'; import { valueLabels } from '@chartist-js/essentials'; // Profit per quarter in thousands: labels past the end of each bar, below // it for a loss. `high` leaves room above the highest bar for its label. barChart( '#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4', 'Q5', 'Q6'], series: [[12, -4, 8, 15, -2, 9]] }, { high: 18, layers: { above: [ valueLabels({ locale: 'en-US', numberFormat: { signDisplay: 'exceptZero' } }) ] } } ); ``` `src/style.css`: ```css #chart { --ct-bar-width: 24px; } ``` `index.html`: ```html
``` The layer reads the bars and points after the other layers drew them, so it follows stacking, `seriesBarDistance`, horizontal bars and anything a `compose` listener changed, without knowing their options. A bar a listener dropped gets no label. ### Text The default text is the value along the value axis, written with `Intl.NumberFormat`. `numberFormat` takes its options, and `locale` its locale: ```ts import { valueLabels } from '@chartist-js/essentials'; export const money = valueLabels({ locale: 'de-CH', numberFormat: { style: 'currency', currency: 'CHF' } }); ``` Set `locale` when charts render on a server: without it, the server's locale writes the numbers. For anything else, `format(item)` returns the text. It gets the same item as the `format` of [`@chartist-js/tooltips`](https://chartist.dev/packages/tooltips/content-and-styling/): the value, its `meta`, the series and its name, the label at the value's index, and the node's `context`. Return `undefined` to keep the default text, `null` or an empty string for no label, and `\n` for a second line. Here the text comes from each value's `meta`: **Work orders on three machines** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; import './style.css'; import { barChart } from 'chartist'; import { valueLabels } from '@chartist-js/essentials'; // Hours per work order on three machines, stacked: each segment says // which order it is, from the value's meta. const order = (value: number, meta: string) => ({ value, meta }); barChart( '#chart', { labels: ['Press', 'Lathe', 'Mill'], series: [ [order(14, 'WO 4182'), order(9, 'WO 4190'), order(11, 'WO 4201')], [order(8, 'WO 4185'), order(12, 'WO 4193'), order(6, 'WO 4204')], [order(5, 'WO 4187'), order(7, 'WO 4198'), order(10, 'WO 4207')] ] }, { horizontalBars: true, stackBars: true, axisY: { offset: 60 }, layers: { above: [ valueLabels({ format: item => `${String(item.meta)}\n${String(item.value)} h` }) ] } } ); ``` `src/style.css`: ```css /* Bars thick enough for two lines of text inside them. */ #chart { --ct-bar-width: 40px; } /* Dark text on the light third series. */ .ct-value-label-inside.ct-series-c { --ct-value-label-inside-color: #453d3f; } ``` `index.html`: ```html
``` Holes get no label, and `format` isn't called for them. A value of 0 is labeled. ### Where they go | Option | What it does | | --------------- | ------------------------------------------------------------------------------------------ | | `pointPosition` | `above` (default), `below`, `left`, `right` or `center` of a point | | `barPosition` | `end` (default), `inside-end`, `center` or `inside-base`, from the bar's base to its value | | `offset` | The gap in px between the point or the bar's end and the label: 8 for points, 4 for bars | | `roles` | `['point']`, `['bar']` or both (default) | | `series` | The series to label, by name or index; default: all of them | | `overflow` | `fit` (default) or `show` | | `labels` | `svg` (default) or `html` | Segments of stacked bars are labeled in their middle, since past a segment's end is the next one. A line drawn without points (`showPoint: false`) gets its labels where the points would be. #### When they don't fit With `overflow: 'fit'`, a label that doesn't fit inside its bar goes past its end instead; in a stacked segment, it's left out. A label that would leave the chart moves inward, and a bar's label that would leave it at the top goes inside the bar's end. Bars are 10 px thin by default, so a label inside one is cramped: give the chart room instead, with a `high` above the highest value or more `chartPadding`, or make the bars wider with `--ct-bar-width`, as the examples do. `overflow: 'show'` draws every label where its position puts it. ### Styling Labels look like the axis labels until you say otherwise. Each one has the class `ct-value-label` and its series' class, so one line gives them their series' colors: **Temperatures in two cities** (runs in the browser): `src/style.css`: ```css /* Each label in its series' color, set by its ct-series-* class. */ .ct-value-label { --ct-value-label-color: var(--ct-color); } ``` `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; import './style.css'; import { lineChart } from 'chartist'; import { valueLabels } from '@chartist-js/essentials'; // Temperatures in two cities, each labeled in its series' color. lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [ { name: 'Basel', data: [18, 21, 19, 24, 22] }, { name: 'Chur', data: [11, 13, 15, 14, 17] } ] }, { low: 5, high: 30, layers: { above: [valueLabels({ format: item => `${String(item.value)} °C` })] } } ); ``` `index.html`: ```html
``` | Property | What it sets | | ------------------------------- | ------------------------------------------------------------- | | `--ct-value-label-color` | The text color; default: the axis label color, light and dark | | `--ct-value-label-inside-color` | The color of labels inside a bar; default: white | | `--ct-value-label-size` | The font size; default: the axis label size | Labels inside or centered in a bar also have `ct-value-label-inside`. `classNames` replaces `group` (`ct-value-labels`), `label` and `inside`. ### SVG, HTML, server and terminal Labels are SVG `text` by default, whatever the chart's `labels` option says, so they render the same on a server, in an exported image and in [the terminal](https://chartist.dev/packages/terminal/). `labels: 'html'` draws each one as a `span` in a `foreignObject` instead, for CSS boxes: a background, padding, rounded corners. In listeners and plugins, find them by the role `valueLabel`. Its context has the labeled value, its indexes, the text and where the label ended up. They're keyed like their values, so with [live updates](https://chartist.dev/docs/live-updates/) a label keeps its element and moves with its bar. ### Coming from the label plugins `chartist-plugin-pointlabels` and `chartist-plugin-barlabels` were 1.x `draw` listeners. `valueLabels()` covers both: | Plugin option | With `valueLabels()` | | ------------------------------------ | ------------------------------------------------------------- | | `labelInterpolationFnc` | `format`, which gets the value and its meta, series and label | | `labelOffset`, `textAnchor`, `align` | `pointPosition`, `barPosition` and `offset` | | `labelClass` | `classNames: { label }`; the default is `ct-value-label` | | `showZeroLabels` | Zero is labeled; return `null` from `format` to leave it out | | `thresholdPercentage`, `startAtBase` | `barPosition: 'inside-end'` or `'inside-base'`, with fitting | Add the layer to `layers.above` instead of `plugins`. It draws in `g.ct-value-labels` above all series, so a later series doesn't cover an earlier series' labels. ## Log axis > A logarithmic scale for values that span several orders of magnitude, with ticks at the powers of ten, minor grid lines and straight trend lines for exponential growth. An app that grew from 120 users to 850,000 makes a dull chart on a linear scale: a flat line along the bottom for four years, then a wall. On a log scale every tenfold step is the same height, so the first hundred users get as much room as the last hundred thousand, and steady growth looks like a straight line. `logAxis()` is an axis type, like `autoScaleAxis()`. It goes in the `type` of an axis: **Users per year** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import { lineChart } from 'chartist'; import { logAxis } from '@chartist-js/essentials'; // Users of an app that grew from 120 to 850,000 in six years. On a log axis // each tenfold step is the same height, so the early years stay readable. lineChart( '#chart', { labels: ['2019', '2020', '2021', '2022', '2023', '2024'], series: [[120, 980, 4100, 36000, 210000, 850000]] }, { axisY: { type: logAxis(), offset: 64, labelTransform: value => value.toLocaleString('en-US') } } ); ``` `index.html`: ```html
``` Use it when the values span several orders of magnitude, or when what matters is the rate of growth rather than the amount: a line that's straight on a log axis grows by the same factor in every step. ### Linear or log Moore's law says the transistors on a chip double about every two years. Here are fifteen processors from the Intel 4004 in 1971 to the Apple M1 Ultra in 2022, from 2,250 transistors to 114 billion. Switch the Y axis to a linear scale and back, and watch the points move: **Moore's law** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import './style.css'; import { autoScaleAxis, lineChart } from 'chartist'; import { liveDomRenderer, smilAnimations } from 'chartist/live'; import { logAxis } from '@chartist-js/essentials'; import { processors } from './processors'; // Moore's law: the transistors on a chip double about every two years. On // a linear axis that's a flat line and a wall at the end; on a log axis // with minor grid lines, a straight climb from 2,250 to 114 billion. const compact = new Intl.NumberFormat('en-US', { notation: 'compact' }); const log = logAxis({ minorTicks: true }); const linear = autoScaleAxis(); const chart = lineChart( '#chart', { series: [processors.map(({ name, x, y }) => ({ x, y, meta: name }))] }, { showLine: false, // Room for the last X label, which starts at its tick. chartPadding: { right: 32 }, axisX: { type: autoScaleAxis(), onlyInteger: true }, axisY: { type: log, offset: 48, labelTransform: value => compact.format(Number(value)) }, // The points move from one scale to the other. renderer: liveDomRenderer({ animations: smilAnimations() }) } ); const button = document.querySelector('#scale')!; let onLog = true; button.addEventListener('click', () => { onLog = !onLog; chart.setOptions({ axisY: { type: onLog ? log : linear } }); button.textContent = `Show on a ${onLog ? 'linear' : 'log'} scale`; }); ``` `src/processors.ts`: ```ts // Transistors per processor, as Wikipedia's "Transistor count" list gives // them: from Intel's 4004 to Apple's M1 Ultra. export const processors = [ { name: 'Intel 4004', x: 1971, y: 2_250 }, { name: 'Intel 8080', x: 1974, y: 6_000 }, { name: 'Intel 8086', x: 1978, y: 29_000 }, { name: 'Intel 80286', x: 1982, y: 134_000 }, { name: 'Intel 80386', x: 1985, y: 275_000 }, { name: 'Intel 80486', x: 1989, y: 1_180_235 }, { name: 'Pentium', x: 1993, y: 3_100_000 }, { name: 'Pentium II', x: 1997, y: 7_500_000 }, { name: 'Pentium III', x: 1999, y: 9_500_000 }, { name: 'Pentium 4', x: 2000, y: 42_000_000 }, { name: 'Core 2 Duo', x: 2006, y: 291_000_000 }, { name: 'Core i7', x: 2008, y: 731_000_000 }, { name: 'Apple A7', x: 2013, y: 1_000_000_000 }, { name: 'Apple M1', x: 2020, y: 16_000_000_000 }, { name: 'Apple M1 Ultra', x: 2022, y: 114_000_000_000 } ]; ``` `src/style.css`: ```css #chart { height: 50vh; } .controls { margin: 0.5em 0 0; } ``` `index.html`: ```html

``` On the linear scale, thirteen of the fifteen processors sit on the bottom edge: next to 114 billion, a billion is a rounding error. On the log scale every chip gets its place, and the straight climb is the doubling itself. Switching is `setOptions({ axisY: { type } })`, and [`liveDomRenderer()`](https://chartist.dev/docs/live-updates/) moves the points from one scale to the other. ### Range and ticks Without `low` and `high`, the axis runs from the power of ten at or below the smallest value to the power at or above the largest: 120 to 850,000 becomes 100 to 1,000,000. The ticks sit at the powers in between, and when they'd come closer than `scaleMinSpace` (20 px by default), the axis keeps every second or third. The axis's own options work as on the core's axes: ```ts import { lineChart } from 'chartist'; import { logAxis } from '@chartist-js/essentials'; lineChart( '#chart', { labels: ['A', 'B', 'C'], series: [[12, 180, 2400]] }, { axisY: { type: logAxis(), // The range exactly, without rounding to powers. low: 10, high: 4000, // Ticks by hand, at their places on the log scale. ticks: [10, 25, 63, 160, 400, 1000, 2500] } } ); ``` When the range is narrower than a power, say from 20 to 80, the ticks are the whole numbers between: 20, 30, 40 and so on. `base` changes the base of the logarithm. `logAxis({ base: 2 })` puts the ticks at 1, 2, 4, 8, 16, which suits values that double, such as memory sizes. ### Minor grid lines `minorTicks: true` draws lighter grid lines without labels between the ticks: at 2, 3, … 9 times each power, the way log paper has them. When the axis shows only every second power, the ones it leaves out get the minor lines instead. Moore's law above has them on its Y axis only. On a log-log chart, both axes are logarithmic, and both can have them: **Kleiber’s law** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; import { axisGrid, axisLabels, cartesianAxes, chart, line } from 'chartist'; import { logAxis, trendLine } from '@chartist-js/essentials'; // Body mass (kg) and resting metabolic rate (W) from a mouse to an // elephant. The rate grows with the mass to the power of about 3/4 // (Kleiber's law): on two log axes that's a straight line, and the trend // line fitted to the logarithms finds it. const animals = [ { x: 0.02, y: 0.17, meta: 'Mouse' }, { x: 0.3, y: 1.45, meta: 'Rat' }, { x: 4, y: 9.1, meta: 'Cat' }, { x: 30, y: 46, meta: 'Dog' }, { x: 70, y: 80, meta: 'Human' }, { x: 600, y: 395, meta: 'Horse' }, { x: 4000, y: 1800, meta: 'Elephant' } ]; chart('#chart', { data: { series: [animals] }, axes: cartesianAxes([ { id: 'mass', side: 'bottom', type: logAxis({ minorTicks: true }) }, { id: 'rate', side: 'left', type: logAxis({ minorTicks: true }) } ]), layers: [axisGrid(), trendLine(), line({ showLine: false }), axisLabels()], // Room for the last X label, which starts at its tick. padding: { right: 32 } }); ``` `index.html`: ```html
``` They're grid lines with the class `ct-grid-minor` next to `ct-grid`, and Chartist's stylesheet styles them with three custom properties: ```css .ct-chart { --ct-grid-minor-color: rgba(0, 0, 0, 0.05); --ct-grid-minor-width: 1px; --ct-grid-minor-dasharray: 1px 3px; } ``` ### Labels The labels show the tick values. Long numbers read better with separators, and `labelTransform` formats them as on any axis. Give the axis an `offset` wide enough for the longest label: ```ts import { lineChart } from 'chartist'; import { logAxis } from '@chartist-js/essentials'; lineChart( '#chart', { labels: ['2022', '2023', '2024'], series: [[3200, 41000, 870000]] }, { axisY: { type: logAxis(), offset: 64, labelTransform: value => value.toLocaleString('en-US') } } ); ``` ### Zero and negative values A log scale has no place for zero: you can halve a value forever and never reach it. A zero in your data doesn't break the chart, though. Zero and negative values are left out of the range and drawn at the start of the axis, on its bottom edge. A bar for zero has no length. That's what you want for a day with no sales, but it can read as "very small" for a missing measurement. Use `null` where a value is missing: it's a gap in the line, as on every axis. ### Bars Bars grow from the start of the axis, so on a log axis their length shows the value's power of ten rather than the value: a bar for 1,000 is three times as long as one for 10 when the axis starts at 1. Set `low` to say where the bars start. Stacked bars work, but stacking adds values while a log axis compares factors, so the segments' lengths don't add up; grouped bars or lines read better. ### Trend lines A [`trendLine()`](https://chartist.dev/packages/essentials/trend-lines/) on a log axis is fitted to the logarithms of the values, so it's straight on the chart and follows exponential growth on a log Y axis. On two log axes, as in the log-log chart above, it finds a power law: the animals' metabolic rate grows with their mass to the power of about ¾, and the trend line's `slope` says so. ```ts import { lineChart } from 'chartist'; import { logAxis, trendLine } from '@chartist-js/essentials'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [[30, 75, 210, 480, 1300]] }, { axisY: { type: logAxis() }, layers: { above: [trendLine()] } } ); ``` The trend line's context has its `slope` in powers of the base per step: a slope of 0.4 on a base 10 axis means the values grow about 2.5 times (100.4) a day. ### Next to a linear axis In a [composed chart](https://chartist.dev/docs/composed-charts/), a log axis is one axis among others. Each axis fits its range to the series of the layers drawn on it: ```ts import { autoScaleAxis, axisGrid, axisLabels, bar, cartesianAxes, chart, line, stepAxis } from 'chartist'; import { logAxis } from '@chartist-js/essentials'; chart('#chart', { data: { labels: ['Jan', 'Feb', 'Mar', 'Apr'], series: [ { name: 'posts', data: [3, 5, 4, 6] }, { name: 'reach', data: [240, 3100, 18000, 95000] } ] }, axes: cartesianAxes([ { id: 'month', side: 'bottom', type: stepAxis() }, { id: 'posts', side: 'left', type: autoScaleAxis(), low: 0 }, { id: 'reach', side: 'right', type: logAxis(), offset: 50 } ]), layers: [ axisGrid({ axes: ['month', 'posts'] }), bar({ series: ['posts'], y: 'posts' }), line({ series: ['reach'], y: 'reach', align: 'center' }), axisLabels() ], className: 'ct-chart-bar' }); ``` ### Coming from chartist-logaxis The `chartist-logaxis` plugin patched 1.x's auto-scale axis with `scale: 'log10'`. With `logAxis()`, change the axis type: `type: logAxis()` instead of `type: Chartist.AutoScaleAxis, scale: 'log10'`, and `logAxis({ minorTicks: true })` instead of `showMinorGrid: true`. The minor grid lines keep the class `ct-grid-minor`, so your CSS for them still applies. Zeros no longer throw an error. ## @chartist-js/essentials API > Every function and type @chartist-js/essentials exports. Types from `chartist` link to the Chartist API reference. ### axisTitle ```ts function axisTitle(options: AxisTitleOptions): Layer ``` Names an axis with a title next to its labels, on any side of the chart. The layer reserves the room the title needs, so the drawing area shrinks and the title never overlaps the labels, whatever font size the stylesheet gives it. One layer draws one title; give each axis its own. Titles on the same side stack outward in the order of `layers`. The title is an SVG `text` with the classes `ct-axis-title`, `ct-horizontal` or `ct-vertical` and `ct-start` or `ct-end`, styled with `--ct-axis-title-color` and `--ct-axis-title-size` from `@chartist-js/essentials/styles.css`. Parameters: - `options: AxisTitleOptions` ```ts import { lineChart } from 'chartist'; import { axisTitle } from '@chartist-js/essentials'; import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed'], series: [[12, 9, 7]] }, { layers: { above: [ axisTitle({ axis: 'x', text: 'Day' }), axisTitle({ axis: 'y', text: 'Visits' }) ] } } ); ``` ### logAxis ```ts function logAxis(options?: LogAxisOptions): AxisFactory ``` Creates a logarithmic axis, for values that span several orders of magnitude: each power of the base (1, 10, 100, … for base 10) is the same distance from the next, so growth by a factor looks the same everywhere. It goes in the `type` of a preset's `axisX` or `axisY`, or of an axis of `cartesianAxes()`. Without `low` and `high`, the range runs from the power at or below the smallest value to the power at or above the largest; the axis's own `low` and `high` set it exactly. The ticks are the powers, every second or third when they'd be closer than `scaleMinSpace`; the axis's `ticks` replace them. Zero and negative values have no place on a log scale: they don't count towards the range and are drawn at the start of the axis, so a bar for zero has no length. Use `null` for a gap. Trend lines on a log axis are fitted to the logarithms of the values, so they're straight on the axis: an exponential trend on a log Y axis. Parameters: - `options?: LogAxisOptions` The base and whether to draw minor grid lines. ```ts import { lineChart } from 'chartist'; import { logAxis } from '@chartist-js/essentials'; lineChart( '#chart', { labels: ['2019', '2020', '2021', '2022', '2023', '2024'], series: [[120, 980, 4100, 36000, 210000, 850000]] }, { axisY: { type: logAxis({ minorTicks: true }), labelTransform: value => value.toLocaleString('en-US') } } ); ``` ### targetLine ```ts function targetLine(options: TargetLineOptions): Layer ``` Creates a layer that draws a line across the chart area at a constant value on one axis, such as a goal or a limit (`line.ct-target-line`). On a Y axis the line is horizontal, on an X axis vertical. It can have a label, drawn as SVG text (`text.ct-target-label`) next to it inside the chart area, and a class, e.g. `ct-series-b` for a series' color. The axis widens to include the value, unless its own `low` or `high` leave it out; a line outside the axis isn't drawn. For several lines, add a layer per line. Parameters: - `options: TargetLineOptions` ```ts import { lineChart } from 'chartist'; import { targetLine } from '@chartist-js/essentials'; import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; lineChart( '#chart', { labels: ['Jan', 'Feb', 'Mar', 'Apr'], series: [[620, 810, 740, 930]] }, { low: 0, layers: { above: [ targetLine({ value: 700, label: 'Min' }), targetLine({ value: 1000, label: 'Goal', className: 'ct-series-b' }) ] } } ); ``` ### trendLine ```ts function trendLine(options?: TrendLineOptions): Layer ``` Creates a layer that draws a trend line for each series: the straight line that fits its values best (least squares), from its smallest to its largest X, as a `line.ct-trend-line` in a series group (`g.ct-series.ct-series-a`), so it takes the series' color. On a step axis X is the value's index, on a scale axis the value's `x`. Holes are skipped, and a series with fewer than two values gets no line. The Y axis widens to include the line's ends. On an axis with a transform, such as a log axis, the line is fitted to the transformed values, so it's straight on that axis: an exponential trend on a log Y axis. Add it next to the `line()` or `bar()` layer that draws the values. Parameters: - `options?: TrendLineOptions` ```ts import { lineChart } from 'chartist'; import { trendLine } from '@chartist-js/essentials'; import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; lineChart( '#chart', { labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'], series: [{ name: 'sales', data: [620, 810, 740, 930, 880, 1020] }] }, { layers: { above: [trendLine()] } } ); ``` ### valueLabels ```ts function valueLabels(options?: ValueLabelsOptions): Layer ``` Creates a layer that writes each value next to the point or bar that shows it: above points, past the ends of bars, and in the middle of stacked segments. It draws from where the chart's other layers drew, after stacking, offsets and `compose` listeners, so add it as an extra layer: `layers.above` on a preset, anywhere in a composed chart. The text is the value along the value axis, written with `Intl.NumberFormat`; `format` gets the same item as the `format` of `@chartist-js/tooltips`. Labels that don't fit their bar, or would leave the chart, move to a place where they fit. They're SVG `text`, so they render on servers and in the terminal, styled with `--ct-value-label-color`, `--ct-value-label-inside-color` and `--ct-value-label-size` from `@chartist-js/essentials/styles.css`. Parameters: - `options?: ValueLabelsOptions` ```ts import { barChart } from 'chartist'; import { valueLabels } from '@chartist-js/essentials'; import 'chartist/styles.css'; import '@chartist-js/essentials/styles.css'; barChart( '#chart', { labels: ['Q1', 'Q2', 'Q3'], series: [[1250, 1830, 1470]] }, { layers: { above: [ valueLabels({ format: item => `${item.value} units` }) ] } } ); ``` ### AxisTitleOptions ```ts interface AxisTitleOptions ``` Options of [`axisTitle`](https://chartist.dev/packages/essentials/api/#axisTitle). Properties: - `axis: string` The id of the axis the title names: `x` or `y` on the presets, the ids you gave in `cartesianAxes()` in composed charts. - `text: string` The title's text; an empty text draws nothing and takes no room. - `align?: 'start' | 'middle' | 'end'` Where along the axis the title sits: at the start (left on X axes, the bottom on Y axes), in the middle or at the end. - `flip?: boolean` Turns titles of left and right axes to read from top to bottom instead of from bottom to top. Titles of top and bottom axes don't change. - `gap?: number` Space between the title and the axis labels, in px. - `className?: string` Extra class names for the title, e.g. `ct-series-b` for a color. - `classNames?: { … }` Replaces the default class: `title` instead of `ct-axis-title`. ### LogAxisOptions ```ts interface LogAxisOptions ``` Options of [`logAxis`](https://chartist.dev/packages/essentials/api/#logAxis). Properties: - `base?: number` The base of the logarithm: the ticks are its powers. Any finite number above 1, such as 2 for a scale that doubles; default 10. - `minorTicks?: boolean` Draws minor grid lines between the ticks, without labels: at 2 to 9 times each power for base 10, or at the powers left out when the ticks skip some. Style them with `--ct-grid-minor-*`. ### TargetLineContext ```ts interface TargetLineContext extends NoSeries ``` The context of a `targetLine` node: the line of a `targetLine()` layer, across the chart area at a value on an axis. Its ends are in px in the SVG. Properties: - `axis: ScaleAxis` The axis the value is on; `axis.dimension` is `x` or `y`. - `value: number` The value the line is drawn at. - `label?: string` The line's label, if it has one. - `x1: number` The X of the line's start, in px. - `y1: number` The Y of the line's start, in px. - `x2: number` The X of the line's end, in px. - `y2: number` The Y of the line's end, in px. - `seriesIndex?: undefined` Always absent: the node belongs to no series. - `valueIndex?: undefined` Always absent: the node stands for no value. ### TargetLabelContext ```ts interface TargetLabelContext extends NoSeries ``` The context of a `targetLabel` node: the label of a target line, and where it's anchored, in px in the SVG. Properties: - `axis: ScaleAxis` The axis of the line's value. - `value: number` The value of the line. - `text: string` The label as drawn. - `x: number` Px from the left edge of the SVG. - `y: number` Px from the top edge of the SVG. - `seriesIndex?: undefined` Always absent: the node belongs to no series. - `valueIndex?: undefined` Always absent: the node stands for no value. ### TrendLineContext ```ts interface TrendLineContext ``` The context of a `trendLine` node: the line a `trendLine()` layer fits through a series' values, `y = slope * x + intercept` in the axes' values (X is the value's index on a step axis). Its ends are in px in the SVG. Properties: - `seriesIndex: number` The series' index in the chart's data, across layers. - `valueIndex?: undefined` Always absent: the line stands for the whole series. - `slope: number` How much the fitted value changes per unit of X. On axes with a transform, such as a log axis, it's in their transformed values: on a base 10 log Y axis, the powers of ten per unit of X. - `intercept: number` The fitted value at X 0, in transformed values like `slope`. - `correlation?: number` How closely the values follow the line: Pearson's correlation coefficient r, from -1 (every value on a falling line) through 0 (no straight trend) to 1 (every value on a rising line). Its square is R², the share of the values' variation the line explains, as spreadsheets show it next to a trendline. It describes the fit, not how reliable it is: with two values it's always 1 or -1. On axes with a transform, it's computed in their transformed values, so it says how straight the values look on the chart. Absent when all values are equal, where it isn't defined. - `x1: number` The X of the line's start (the series' smallest X), in px. - `y1: number` The Y of the line's start, in px. - `x2: number` The X of the line's end (the series' largest X), in px. - `y2: number` The Y of the line's end, in px. ### TargetLineOptions ```ts interface TargetLineOptions ``` The options of the [`targetLine`](https://chartist.dev/packages/essentials/api/#targetLine) layer: the line's value, its label and class, and the axis it's on. For several lines, add a layer per line. Properties: - `value: number` The value the line is drawn at, on the layer's axis. On a step axis, the position by index, as for the axis's ticks. - `label?: string` The text drawn next to the line; no label without it. - `className?: string` Extra classes for the line and its label, e.g. `ct-series-b`. - `axis?: string` The id of the axis the value is on: a Y axis draws a horizontal line, an X axis a vertical one. Default: the first Y axis. - `labelPosition?: 'start' | 'end'` Where the label sits along the line: at the `end` (right, or top for a vertical line) or the `start` (left, or bottom). - `classNames?: { … }` Replaces the classes of what the layer draws: `line` (`ct-target-line`) and `label` (`ct-target-label`). ### TrendLineOptions ```ts interface TrendLineOptions ``` The options of the [`trendLine`](https://chartist.dev/packages/essentials/api/#trendLine) layer. Without them, it draws a trend line for every series, on the first X and Y axis. Properties: - `series?: readonly (string | number)[]` Series to draw a trend line for, by index or name; default: all series. - `x?: string` The id of the X axis to draw on; default: the first X axis. - `y?: string` The id of the Y axis to draw on; default: the first Y axis. - `align?: 'start' | 'center'` Where the line's ends sit on a step axis: at the `start` of their steps, as the points of `line()`, or at the `center`, where bars are centered. - `classNames?: { … }` Replaces the classes of what the layer draws: `series` (`ct-series`) and `line` (`ct-trend-line`). ### ValueLabelRole ```ts type ValueLabelRole = 'point' | 'bar' ``` The roles whose nodes get labels. ### PointLabelPosition ```ts type PointLabelPosition = 'above' | 'below' | 'left' | 'right' | 'center' ``` Where a point's label sits. ### BarLabelPosition ```ts type BarLabelPosition = 'end' | 'inside-end' | 'center' | 'inside-base' ``` Where a bar's label sits, from the bar's base to its value: past its end, inside its end, in its middle or inside its base. ### ValueLabelItem ```ts type ValueLabelItem = { [K in R]: { readonly role: K; readonly seriesIndex: number; readonly valueIndex: number; readonly value: number | { readonly x: number; readonly y: number }; readonly meta: unknown; readonly series: unknown; readonly seriesName?: string; readonly label?: string; readonly context: NodeRoles[K]['ctx'] } }[R] ``` The value a label is about: what `format` gets. It has the fields of the item `@chartist-js/tooltips` gives its `format`, under the same names, so a function written for one mostly works for the other. Checking `item.role` narrows `item.context` to that role's context. ### ValueLabelContext ```ts interface ValueLabelContext ``` The context of a `valueLabel` node. Properties: - `role: ValueLabelRole` The role of the node the label is for. - `seriesIndex: number` The series' index in the chart's data. - `valueIndex: number` The value's index in its series. - `value: unknown` The labeled value, as the node has it. - `meta: unknown` The value's `meta`. - `series: unknown` The series as given in the data. - `text: string` The label's text. - `position: 'left' | 'right' | 'end' | 'center' | 'above' | 'below' | 'inside-end' | 'inside-base'` Where the label ended up, after fitting. - `x: number` Px from the left edge of the SVG: where the text is anchored. - `y: number` Px from the top edge of the SVG: the first line's baseline. ### ValueLabelsOptions ```ts interface ValueLabelsOptions ``` Options of [`valueLabels`](https://chartist.dev/packages/essentials/api/#valueLabels). Properties: - `roles?: readonly R[]` The nodes that get labels: `point`, `bar` or both (the default). - `series?: readonly (string | number)[]` The series to label, by index or name; default: all of them. - `pointPosition?: PointLabelPosition` Where point labels sit; default `above`. - `barPosition?: BarLabelPosition` Where bar labels sit; default `end`, and `center` for segments of stacked bars. - `offset?: number` The gap in px between a point's center, or a bar's end or base, and the label's nearest edge; default 8 for points and 4 for bars. - `format?: (item: ValueLabelItem) => string | number | null | undefined` The label's text for a value: a string or a number. `undefined` keeps the default text, `null` or `''` draw no label, and `\n` starts a new line. - `numberFormat?: NumberFormatOptions` `Intl.NumberFormat` options for the default text. - `locale?: string | readonly string[]` The locale of the default text; default: the runtime's. Set it when charts render on a server, so the text doesn't depend on its locale. - `overflow?: 'fit' | 'show'` `fit` (the default) moves labels that don't fit their bar or would leave the chart; `show` draws every label where its position puts it. - `labels?: 'svg' | 'html'` SVG `text` (the default) or HTML in a `foreignObject`. - `classNames?: { … }` Replace the classes: `group` (`ct-value-labels`), `label` (`ct-value-label`) and `inside` (`ct-value-label-inside`). ## @chartist-js/tooltips > Tooltips for mouse, touch and keyboard that show a value's label, series and number, stay on their value while the chart updates, and take their looks from CSS. Chartist's core has no tooltips: a tooltip is interaction in the browser, and a chart drawn on a server or in a terminal has nothing to hover. `@chartist-js/tooltips` adds them to the charts that need them, as a plugin. Hover a point, tap it, or press **Tab** and use the arrow keys: **Tooltips** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/tooltips/styles.css'; import { lineChart } from 'chartist'; import { tooltips } from '@chartist-js/tooltips'; // Hover a point, tap it, or press Tab and use the arrow keys. lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [ { name: 'Visitors', data: [1200, 1500, 1350, 1600, 1100] }, { name: 'Sign-ups', data: [300, 420, 380, 510, 290] } ] }, { low: 0, plugins: [tooltips()] } ); ``` `index.html`: ```html
``` ### Install ```sh npm install @chartist-js/tooltips ``` It brings `chartist` along. Load its stylesheet after Chartist's. #### Without a bundler The browser build imports nothing, so it works next to any copy of Chartist 2, without an import map: ```html
``` ### Quick start Add `tooltips()` to a chart's `plugins`: points, bars and slices get a tooltip with the value's label, its series' name and the value. ```ts import 'chartist/styles.css'; import '@chartist-js/tooltips/styles.css'; import { barChart } from 'chartist'; import { tooltips } from '@chartist-js/tooltips'; barChart( '#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [{ name: 'Sales', data: [5, 4, 3, 7] }] }, { plugins: [tooltips()] } ); ``` A chart with tooltips is one stop in the page's tab order: **Tab** focuses a value, the arrow keys move between values and series, and **Escape** hides the tooltip. Screen readers announce the focused value with its tooltip's text. `keyboard: false` leaves the tab order alone. Charts that use a 1.x tooltip plugin can keep it: it runs through [`fromLegacyPlugin`](https://chartist.dev/docs/coming-from-1x/#community-plugins). `tooltips()` takes its place on the functional API, with `format`, `numberFormat` and `render` where the 1.x plugins had their callbacks. The tooltip shows in the top layer, so an ancestor's `overflow: hidden` can't cut it off, and it's placed through the CSSOM, so it works under a strict [Content Security Policy](https://chartist.dev/docs/security-and-csp/). Without a page, in `toScene()` or on a server, the plugin does nothing, so one config serves both. ### Next - [Content and styling](https://chartist.dev/packages/tooltips/content-and-styling/): your own text, your own markup or framework template, numbers, roles, positions and the custom properties. - [API](https://chartist.dev/packages/tooltips/api/): every option of the plugin. - [Events and plugins](https://chartist.dev/docs/events-and-plugins/): how plugins like this one hook into a chart. ## Content and styling > Your own text with format, your own markup or a framework's template with render, numbers, roles, positions, live charts and the custom properties of the tooltip. A tooltip shows up to three parts by default: a title, the series' name and the value. The title is the value's `meta` when it's a string or a number, otherwise its label; a part that's empty or repeats an earlier one is left out. This page changes what a tooltip says, what it looks like and where it shows. ### Your own text `format` gets the hovered value and returns its tooltip's text. Line breaks show as line breaks. `null` or an empty string means no tooltip for that value, and `undefined` keeps the default text: **Text with format** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/tooltips/styles.css'; import { barChart } from 'chartist'; import { tooltips } from '@chartist-js/tooltips'; const orders = new Intl.NumberFormat('en-US'); barChart( '#chart', { labels: ['Q1', 'Q2', 'Q3', 'Q4'], series: [{ name: 'Orders', data: [1830, 2410, 0, 2950] }] }, { plugins: [ tooltips({ // Text with a line break; no tooltip for a quarter without orders. format: item => item.value === 0 ? null : `${item.label}\n${orders.format(Number(item.value))} orders` }) ] } ); ``` `index.html`: ```html
``` The item `format` gets has the value's `role`, `seriesIndex` and `valueIndex`, its `value` (a number, or `{ x, y }` when it has both, as in a scatter chart), its `meta`, the `series` and its `seriesName`, its `label` and its `element`. The text is also the focused value's accessible name, so write it to be read out too. For numbers, the default text is often enough: `numberFormat` takes `Intl.NumberFormat` options, in the reader's locale. ```ts import { barChart } from 'chartist'; import { tooltips } from '@chartist-js/tooltips'; barChart( '#chart', { labels: ['Q1', 'Q2', 'Q3'], series: [[1250, 1830, 1470]] }, { plugins: [ tooltips({ numberFormat: { style: 'currency', currency: 'CHF' } }) ] } ); ``` ### Your own markup The default text is text only: data, `meta` and `format` can't put markup into the page. For markup, `render` fills the tooltip yourself. It gets the tooltip's content element, emptied for each value, the hovered item and the chart's data, so a tooltip can compare a value with its neighbours: **Markup with render** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/tooltips/styles.css'; import './style.css'; import { lineChart } from 'chartist'; import { tooltips } from '@chartist-js/tooltips'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [{ name: 'Visitors', data: [1200, 1500, 1350, 1600, 1100] }] }, { low: 0, plugins: [ tooltips({ // Your own markup: the value, and the change since the day before, // read from the chart's data. render(content, item, data) { const values = data.series[item.seriesIndex ?? 0].values as number[]; const value = Number(item.value); const before = values[(item.valueIndex ?? 0) - 1]; const title = document.createElement('strong'); title.textContent = `${item.label}: ${value} visitors`; content.append(title); if (before !== undefined) { const change = Math.round(((value - before) / before) * 100); const trend = document.createElement('span'); trend.className = change < 0 ? 'down' : 'up'; trend.textContent = `${change < 0 ? '' : '+'}${change}% since ${ data.labels[(item.valueIndex ?? 0) - 1] }`; content.append(trend); } } }) ] } ); ``` `src/style.css`: ```css .ct-tooltip strong, .ct-tooltip span { display: block; } .ct-tooltip .up { color: light-dark(#6ee7b7, #047857); } .ct-tooltip .down { color: light-dark(#fca5a5, #b91c1c); } ``` `index.html`: ```html
``` `render` also takes a framework's template: mount a component into the content element, and return a function that unmounts it. The plugin calls it before the next value's `render`, when the tooltip hides and on `destroy()`. Content a framework fills in later is fine: the tooltip is placed again when its content changes size. ```ts nocheck tooltips({ render(content, item) { // Angular: a dynamic component in the tooltip. const ref = createComponent(ValueTooltip, { environmentInjector, hostElement: content }); ref.setInput('item', item); appRef.attachView(ref.hostView); return () => ref.destroy(); } }); ``` With `render`, `format` still decides which values have a tooltip and gives their accessible name; without `format`, the name is the content's text. What `render` does is your code: setting an HTML string with `innerHTML` needs your own Trusted Types policy on a page that requires them. ### Which values, and where `roles` picks the nodes that get tooltips: `point`, `bar` and `slice` by default. Any role works, including the roles of other layers and plugins: `roles: ['line']` gives each series of a line chart one tooltip, with its name, and a target line's tooltip shows its label and value. For what the default text doesn't know, `item.context` is the node's data context, so `format` can describe a trend line by its `slope`, and by its `correlation` how well it fits the values (squared, the R² spreadsheets show): ```ts import { lineChart } from 'chartist'; import { trendLine } from '@chartist-js/essentials'; import { tooltips } from '@chartist-js/tooltips'; lineChart( '#chart', { series: [{ name: 'Visitors', data: [12, 15, 14, 18, 21] }] }, { layers: { above: [trendLine()] }, plugins: [ tooltips({ roles: ['point', 'trendLine'], format: item => { if (item.role !== 'trendLine') { // Points keep the default text. return undefined; } const { slope, correlation } = item.context; const fit = correlation === undefined ? '' : `, R² ${(correlation ** 2).toFixed(2)}`; return `${item.seriesName}: ${slope >= 0 ? '+' : ''}${slope.toFixed(1)} a day${fit}`; } }) ] } ); ``` Thin lines are hard to hover: give them a wider stroke in your CSS, or use the keyboard, which reaches every role in `roles`. #### Roles from your own layers The plugin doesn't keep a list of roles it knows: it works from the chart's scene. So the roles of [your own layers](https://chartist.dev/docs/custom-layers/) and of layers from other packages get tooltips the same way, once you list them. This chart's dots come from the lollipop layer of the custom layers guide, with a role of its own: **Tooltips on a custom layer** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/tooltips/styles.css'; import './style.css'; import { autoScaleAxis, axisGrid, axisLabels, cartesianAxes, chart, stepAxis } from 'chartist'; import { tooltips } from '@chartist-js/tooltips'; import { lollipop } from './lollipop'; // The lollipop layer gives its dots a role of its own. Listed in `roles`, // they get tooltips. Their context follows Chartist's conventions // (seriesIndex, valueIndex, value), so the default text knows what to show. chart('#chart', { data: { labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'], series: [{ name: 'Visits', data: [12, 9, 14, 7, 11] }] }, axes: cartesianAxes({ x: { type: stepAxis() }, y: { type: autoScaleAxis() } }), layers: [axisGrid(), lollipop(), axisLabels()], plugins: [tooltips({ roles: ['lollipop'] })] }); ``` `src/lollipop.ts`: ```ts import { alphaNumerate, node, type Layer } from 'chartist'; // The roles this layer gives its nodes, so listeners and plugins can find // them with a typed context. declare module 'chartist' { interface NodeRoles { lollipop: { ctx: { readonly seriesIndex: number; readonly valueIndex: number; readonly value: number; }; attrs: { readonly cx: number; readonly cy: number; readonly r: number }; }; } } export interface LollipopOptions { /** Series to draw, by name or index; default: all. */ readonly series?: readonly (number | string)[]; /** Axis ids; default: the first X and the first Y axis. */ readonly x?: string; readonly y?: string; } /** A stem from zero and a dot for each value. */ export function lollipop(options: LollipopOptions = {}): Layer { return { type: 'lollipop', // Drawn where bars are, so the chart gets the bar chart's class, which // centers the X labels. chartType: 'bar', coordinates: ['cartesian'], axes: { x: options.x, y: options.y }, series: options.series, // The stems start at zero, so the Y axis must include it. extent: ({ axes }) => (axes.y ? { [axes.y]: { low: 0, high: 0 } } : {}), compose({ area, axes, normalized, seriesIndexes }) { const { x, y } = axes; if (!x || !y || !normalized) { throw new Error('Layer "lollipop" needs an x and a y axis'); } // Centered in each step, where bars sit. const shift = x.kind === 'step' ? (x.step ?? 0) / 2 : 0; const base = area.y1 - y.project(0); return normalized.series.map((values, local) => { // The series' index in the whole chart, for its class and color. const seriesIndex = seriesIndexes?.[local] ?? local; let group = node( 'g', {}, `ct-series ct-series-${alphaNumerate(seriesIndex)}` ); values.forEach((value, valueIndex) => { // Values are normalized to { x, y }, or undefined for a hole. const v = value && typeof value === 'object' && 'y' in value ? value.y : undefined; if (v === undefined) { return; } const cx = area.x1 + shift + x.project(value, valueIndex, values); const cy = area.y1 - y.project(value, valueIndex, values); group = group .append( node('line', { x1: cx, y1: base, x2: cx, y2: cy }, 'ct-stem') ) .append( node('circle', { cx, cy, r: 6 }, 'ct-lollipop', { role: 'lollipop', ctx: { seriesIndex, valueIndex, value: v } }) ); }); return group; }); } }; } ``` `src/style.css`: ```css /* The series group sets --ct-color; stems and dots take it. */ .ct-stem { stroke: var(--ct-color); stroke-width: 2px; } .ct-lollipop { fill: var(--ct-color); } ``` `index.html`: ```html
``` What the default text shows depends on the node's context: a layer that gives its nodes `seriesIndex`, `valueIndex` and `value`, as Chartist's own layers do, gets the label, the series' name and the value without any setup. Anything else a layer puts into a node's context reaches `format` and `render` as `item.context`. It's typed by the roles you list: checking `item.role` narrows `item.context` to that role's context, for Chartist's roles and for the roles other layers declare in `NodeRoles` alike. ```ts import { lineChart } from 'chartist'; import { tooltips } from '@chartist-js/tooltips'; // A role of a forecast layer from another package, as such layers declare it. declare module 'chartist' { interface NodeRoles { forecast: { ctx: { readonly low: number; readonly high: number }; attrs: object; }; } } lineChart( '#chart', { series: [[12, 15, 14]] }, { plugins: [ tooltips({ roles: ['point', 'trendLine', 'forecast'], format: item => { if (item.role === 'forecast') { // Numbers here: TypeScript knows the forecast's context. return `Forecast: ${item.context.low} to ${item.context.high}`; } if (item.role === 'trendLine') { return `Trend: ${item.context.slope.toFixed(1)} a day`; } return undefined; } }) ] } ); ``` Returning `undefined` keeps the default text, so a `format` like this one handles its own roles and leaves points as they were. `position: 'pointer'` makes the tooltip follow the mouse while it moves over a value. Touch and keyboard always place it at the value: at a point, at a bar's end, at the middle of a slice's arc. It shows above, below when there's no room above, and moves sideways to stay in the viewport. ### Live charts With [`liveDomRenderer()`](https://chartist.dev/docs/live-updates/), a tooltip stays on its value while the chart updates, shows the new value, and moves in step with the update animation. Hover a point: **A tooltip on a live chart** (runs in the browser): `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/tooltips/styles.css'; import { lineChart } from 'chartist'; import { liveDomRenderer, smilAnimations } from 'chartist/live'; import { tooltips } from '@chartist-js/tooltips'; let visitors = [12, 15, 14, 18, 21, 19, 23, 22]; // Hover a point: the tooltip stays on it while the chart updates, and // moves with the update animation. const chart = lineChart( '#chart', { series: [{ name: 'Visitors', data: visitors }] }, { low: 0, renderer: liveDomRenderer({ animations: smilAnimations() }), plugins: [tooltips()] } ); setInterval(() => { const last = visitors[visitors.length - 1]; const next = Math.max(0, last + Math.round(Math.random() * 8 - 4)); visitors = [...visitors.slice(1), next]; chart.update({ data: { series: [{ name: 'Visitors', data: visitors }] } }); }, 1500); ``` `index.html`: ```html
``` ### Styling The tooltip is `.ct-tooltip`, with `.ct-tooltip-title`, `.ct-tooltip-series` and `.ct-tooltip-value` inside, or `.ct-tooltip-text` for `format`'s text. Its looks are custom properties. Set them on the page, or on a chart's container for one chart: **A styled tooltip** (runs in the browser): `src/style.css`: ```css /* The tooltip's look is custom properties: here on the chart's container. */ #chart { --ct-tooltip-background: #1e3a8a; --ct-tooltip-color: #fff; --ct-tooltip-radius: 0; --ct-tooltip-padding: 0.5rem 0.75rem; --ct-tooltip-offset: 14px; --ct-tooltip-hide-delay: 600ms; } /* And plain CSS, through the class the plugin adds. */ .fruit-tooltip .ct-tooltip-value { font-size: 1.25em; font-weight: 700; } /* * A callout arrow toward the value. The plugin says on which side the * tooltip is (data-placement) and where the value is from its left edge * (--ct-tooltip-anchor-x), even when the tooltip moved to stay in view. */ .fruit-tooltip::after { content: ''; position: absolute; left: var(--ct-tooltip-anchor-x, 50%); translate: -50% 0; border: 7px solid transparent; } .fruit-tooltip[data-placement='top']::after { top: 100%; border-top-color: #1e3a8a; } .fruit-tooltip[data-placement='bottom']::after { bottom: 100%; border-bottom-color: #1e3a8a; } ``` `src/main.ts`: ```ts import 'chartist/styles.css'; import '@chartist-js/tooltips/styles.css'; import './style.css'; import { barChart } from 'chartist'; import { tooltips } from '@chartist-js/tooltips'; barChart( '#chart', { labels: ['Apples', 'Kiwis', 'Pears'], series: [ { name: '2025', data: [12, 9, 7] }, { name: '2026', data: [14, 11, 6] } ] }, { plugins: [tooltips({ className: 'fruit-tooltip' })] } ); ``` `index.html`: ```html
``` | Property | What it sets | | --------------------------- | ------------------------------------------------------------------------------- | | `--ct-tooltip-background` | The background; dark on light pages and light on dark ones | | `--ct-tooltip-color` | The text color | | `--ct-tooltip-border` | The border, as the `border` shorthand | | `--ct-tooltip-radius` | The corner radius | | `--ct-tooltip-padding` | The padding | | `--ct-tooltip-font` | The font, as the `font` shorthand | | `--ct-tooltip-shadow` | The shadow | | `--ct-tooltip-max-width` | The widest the tooltip gets before its text wraps | | `--ct-tooltip-offset` | The distance from the value, in px | | `--ct-tooltip-hide-delay` | How long it stays after the pointer leaves | | `--ct-tooltip-focus-color` | The color of the focused value's outline | | `--ct-tooltip-focus-width` | The width of that outline | | `--ct-tooltip-series-color` | Set by the plugin: the series' color, for the swatch before its name | | `--ct-tooltip-anchor-x` | Set by the plugin: the value's place from the tooltip's left edge, for an arrow | The example also draws a callout arrow toward the value. The plugin sets `data-placement` on the tooltip (`top` or `bottom`, the side of the value it shows on) and `--ct-tooltip-anchor-x`, the value's place from the tooltip's left edge, which stays right when the tooltip moved sideways to stay in view. Set `--ct-tooltip-offset` to at least the arrow's size. The colors follow the page's `color-scheme`, like Chartist's own. `className` adds classes to the tooltip, for plain CSS beyond the properties. The tooltip stays while the pointer is over it, and the hide delay is a CSS transition, which reduced motion turns off. ## @chartist-js/tooltips API > Every function and type @chartist-js/tooltips exports. Types from `chartist` link to the Chartist API reference. ### tooltips ```ts function tooltips(options?: TooltipsOptions): ChartPlugin ``` Shows a tooltip for the values of a chart: under the pointer, under a finger, or in keyboard focus. Points, bars and slices get one by default, with the value's title (its `meta` or label), the series' name and the value; `format` and `render` change the content. The tooltip is a `.ct-tooltip` element in the chart's container, shown in the top layer, so `overflow: hidden` can't clip it, and styled with the `--ct-tooltip-*` properties of `@chartist-js/tooltips/styles.css`. Content is text unless you `render` it, so the plugin works under a strict Content Security Policy and Trusted Types. The values form one tab stop per chart: arrow keys move between them and `Escape` hides the tooltip. Where a chart has no container (`toScene()`, server rendering), the plugin does nothing. Parameters: - `options?: TooltipsOptions` ```ts import { lineChart } from 'chartist'; import { tooltips } from '@chartist-js/tooltips'; import 'chartist/styles.css'; import '@chartist-js/tooltips/styles.css'; lineChart( '#chart', { labels: ['Mon', 'Tue', 'Wed'], series: [{ name: 'Visitors', data: [1200, 1500, 1350] }] }, { plugins: [tooltips()] } ); ``` ### DefaultTooltipRole ```ts type DefaultTooltipRole = 'point' | 'bar' | 'slice' ``` The roles that get tooltips without a `roles` option. ### TooltipItemBase ```ts interface TooltipItemBase ``` What every tooltip item has, whatever its role. Properties: - `seriesIndex?: number` The series' index in the chart's data; absent for a node without one, such as a target line. - `valueIndex?: number` The value's index in its series; absent for a whole series. - `value?: unknown` The value: a number, or `{ x, y }` when it has both (a scatter chart's point). Absent for a whole series. - `meta: unknown` The value's `meta`, or the series' for a slice. - `series: unknown` The series as given in the data: an array, or an object with `data`. - `seriesName?: string` The series' `name`, when it has one. - `label?: string` The value's label: at its index, the slice's own, or a target line's. - `element: Element` The element drawn for the value. ### TooltipItem ```ts type TooltipItem = { [K in R]: TooltipItemBase & { readonly role: K; readonly context: NodeRoles[K]['ctx'] } }[R] ``` The value a tooltip is about: what `format` and `render` get, for the roles in `roles`. Checking `item.role` narrows `item.context` to that role's data context, for Chartist's roles and for the roles other layers declare in `NodeRoles` alike: after `item.role === 'trendLine'`, `item.context.slope` is a number. ### TooltipData ```ts interface TooltipData ``` The chart's data as `render` gets it: its labels and series, read-only. Properties: - `labels: readonly (string | number | Date)[]` The labels as given, or an empty array. - `series: readonly { … }[]` The series, each with its `values`, and its `name` and `meta` when given. - `flat?: boolean` The data was one flat series of values, as for pies. ### TooltipsOptions ```ts interface TooltipsOptions ``` Options of [`tooltips`](https://chartist.dev/packages/tooltips/api/#tooltips). `R` is the roles in `roles`, so `format` and `render` get items of those roles only. Properties: - `roles?: readonly R[]` The roles of the nodes that get tooltips: `point`, `bar` and `slice` by default. Any role works, of other layers and plugins too: `line` for a tooltip per series, `trendLine` and `targetLine` of `@chartist-js/essentials`. The default text shows what a node has (a target line's label and value, a trend line's series); for more, read `item.context` in `format` or `render`, where checking `item.role` narrows it to that role's context. - `format?: (item: TooltipItem) => string | null | undefined` Gives a tooltip's text: line breaks show as line breaks. Return `undefined` to keep the default text for that value (handy when you format some roles only), and `null` or an empty string for no tooltip. The text is also the focused value's accessible name. Without it, a tooltip shows the title (the value's `meta` or its label), the series' name and the value. - `render?: (content: HTMLElement, item: TooltipItem, data: TooltipData) => void | (() => void)` Fills the tooltip yourself, with DOM nodes or a framework's template: `content` is the tooltip's content element, emptied before each call. Return a function to undo it, such as unmounting a component: it runs before the next call, when the tooltip hides, and on `destroy()`. `format` still decides which values have a tooltip and gives their accessible name; without it, the name is the content's text. Setting HTML strings is your code's business: under Trusted Types, it needs your own policy. - `numberFormat?: NumberFormatOptions` How the default text formats values, as `Intl.NumberFormat` options in the browser's locale, e.g. `{ style: 'currency', currency: 'CHF' }`. - `position?: 'element' | 'pointer'` Where the tooltip shows: at the value (`element`) or at the mouse or pen while it moves (`pointer`). Touch and keyboard always use the value. - `keyboard?: boolean` Makes the values reachable by keyboard: one tab stop per chart, arrow keys between values, `Escape` to hide. `false` leaves the tab order and the chart's elements as they are. - `className?: string` Extra class names for the tooltip element.