Chartist

Search

Find a guide, an API entry or an example.

Try

Target lines and bands

Draw goals, limits, ranges and trends on line and bar charts with the targetLine(), band() and trendLine() layers, on a preset or in a composed chart.

A number on its own says little. Is 950 good? Against a goal of 1000, you see it at a glance. Two layers draw that context: targetLine() for goals and limits, band() for ranges. Both are part of the core, work on the presets and in composed charts, and you style them with CSS like the rest of the chart.

A target line

Presets take extra layers in their layers option: above draws them on top of the bars or lines, below behind them.

src/main.ts
import 'chartist/styles.css';
import { barChart, targetLine } from 'chartist';

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' })] }
  }
);
Live

Responsive options 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, and vertical ones

One layer draws one line. For a minimum and a goal, add two:

import { targetLine } from 'chartist';

export const limits = [
  targetLine({ value: 1000, label: 'Goal' }),
  targetLine({ value: 800, label: 'Minimum', className: 'ct-series-b' })
];

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: 1000 }). In a composed chart, use your own axis ids.

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: how does Bern do compared to the other stores?

src/main.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()
  ]
});
Live

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. Where an edge has no value, the band breaks.

On a preset

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:

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.

src/main.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 })] }
  }
);
Live

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.

Trend lines

trendLine() fits a straight line through each series it picks (all of them by default) and draws it from the series’ first to its last value, dashed and in the series’ color. The fit is least squares: on a step axis it uses the values’ order, on a scale axis their x.

import { lineChart, trendLine } from 'chartist';

lineChart(
  '#chart',
  {
    labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
    series: [[3, 5, 4, 7, 6, 9]]
  },
  { layers: { above: [trendLine()] } }
);

Where the line 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. On bar charts, align: 'center' lines it up with the bars, as for lines. Its role is trendLine, with the slope and intercept in its context. In the gallery: bars with a trend line and a scatter plot with a regression line.

Styling

The target line and the band draw in the text color by default: a dashed target line and a light band, so they read as context behind the data. Change them with custom properties, or give them a series color with className: 'ct-series-c':

.ct-chart {
  --ct-target-line-color: #c8102e;
  --ct-target-line-width: 2px;
  --ct-target-line-dasharray: none;
  --ct-band-color: #b07800;
  --ct-band-opacity: 0.2;
}

A trend line takes its series’ color; --ct-trend-line-width and --ct-trend-line-dasharray change its look.

The markup: a line.ct-target-line and a text.ct-target-label per target line, a line.ct-trend-line in a series group per trend line, and a g.ct-band group with a path.ct-band-area per piece of the band. In listeners and plugins, find them by the roles targetLine, targetLabel, trendLine and band, as in events and plugins.

Next: compose your own charts with several axes, or style the chart to match your page.