Chartist

Search

Find a guide, an API entry or an example.

Try

Coming from 1.x

Your Chartist 1.x charts keep running on v2. Here's what stays the same, what's new, and how to move to the functions one chart at a time.

Good news first: your 1.x code keeps working. v2 adds a functional API next to the classes you know, and both run on the same engine. Upgrade, check your events (one line, see below), and move to the functions whenever a chart needs something new.

What keeps working

The classes are still there, imported the same way, with the same constructor arguments: the container, the data, the options and the responsive options.

src/main.ts
// Chartist 1.x code, running on v2 unchanged.
import 'chartist/styles.css';
import { LineChart } from 'chartist';

new LineChart(
  '#chart',
  {
    labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
    series: [
      [12, 9, 7, 8, 5],
      [2, 1, 3.5, 7, 3]
    ]
  },
  {
    fullWidth: true,
    chartPadding: { right: 40 }
  }
);
Live
  • 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, 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:
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:

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:

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.

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

// 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 Updating charts
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

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.

// 1.x
chart.on('draw', data => {
  if (data.type === 'point' && data.index === 4) {
    data.element.addClass('is-friday');
  }
});
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.

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):

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.

Community plugins

Most community plugins, like the tooltips, axis titles and legends on the plugins page, 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.

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.

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.

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/chartist@2.0.0-rc.2/dist/index.min.css"
/>
<script type="module">
  import 'https://cdn.jsdelivr.net/npm/chartist@2.0.0-rc.2/dist/compat.min.js';
</script>
<script
  defer
  src="https://cdn.jsdelivr.net/npm/chartist-plugin-axistitle@0.0.7/dist/chartist-plugin-axistitle.js"
></script>

<div id="chart" class="ct-golden-section"></div>
<script type="module">
  import { lineChart } from 'https://cdn.jsdelivr.net/npm/chartist@2.0.0-rc.2/dist/index.min.js';
  import { fromLegacyPlugin } from 'https://cdn.jsdelivr.net/npm/chartist@2.0.0-rc.2/dist/compat.min.js';

  lineChart(
    '#chart',
    { series: [[1, 3, 2, 4]] },
    {
      plugins: [
        fromLegacyPlugin(
          Chartist.plugins.ctAxisTitle({ axisY: { axisTitle: 'Visits' } })
        )
      ]
    }
  );
</script>

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:

<script src="https://cdn.jsdelivr.net/npm/chartist@2.0.0-rc.2/dist/index.umd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartist@2.0.0-rc.2/dist/compat.umd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartist-plugin-axistitle@0.0.7/dist/chartist-plugin-axistitle.js"></script>

<div id="chart" class="ct-golden-section"></div>
<script>
  Chartist.lineChart(
    '#chart',
    { series: [[1, 3, 2, 4]] },
    {
      plugins: [
        Chartist.fromLegacyPlugin(
          Chartist.plugins.ctAxisTitle({ axisY: { axisTitle: 'Visits' } })
        )
      ]
    }
  );
</script>

On the 1.x classes, list the plugin next to compat as before: new Chartist.LineChart('#chart', data, { plugins: [Chartist.compat, Chartist.plugins.ctAxisTitle()] }).

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:

import { lineChart } from 'chartist';
import { fromLegacyPlugin } from 'chartist/compat';

function loadScript(src: string) {
  return new Promise<void>((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-axistitle@0.0.7/dist/chartist-plugin-axistitle.js'
);

lineChart('#chart', data, {
  plugins: [
    fromLegacyPlugin(
      Chartist.plugins.ctAxisTitle({ axisY: { axisTitle: 'Visits' } })
    )
  ]
});

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:

declare const Chartist: {
  plugins: Record<string, (options?: unknown) => 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.

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.
  • Container queries for responsive options, next to media queries (experimental). See Responsive sugar topping.
  • Server rendering with toScene() and renderToString(), and SVG text labels. See Server rendering.
  • v2 plugins that hook into every step of the pipeline. See Events and plugins.
  • setOptions() changes a chart’s options after creation, and the on option listens from the first render. See Updating charts.
  • 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.

Classic script tags

The UMD build is still published for pages that load Chartist with a classic <script> tag. It’s there for backwards compatibility only: for new pages, use a <script type="module"> instead. The compat plugin has a UMD build that adds itself to the global Chartist:

<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/chartist@2.0.0-rc.2/dist/index.min.css"
/>
<script src="https://cdn.jsdelivr.net/npm/chartist@2.0.0-rc.2/dist/index.umd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartist@2.0.0-rc.2/dist/compat.umd.js"></script>

<div id="chart" class="ct-golden-section"></div>
<script>
  new Chartist.LineChart(
    '#chart',
    { series: [[1, 3, 2, 4]] },
    { plugins: [Chartist.compat] }
  );
</script>

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 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.

In the API reference: chartist/compat and the 1.x classes.

Next: draw a chart with the functions, or see what Chartist leaves to CSS.