Chartist

Search

Find a guide, an API entry or an example.

Try

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 functions

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.

import {
  autoScaleAxis,
  axisGrid,
  axisLabels,
  cartesianAxes,
  line,
  renderToString,
  stepAxis,
  toScene
} from 'chartist';

export function weekChart(values: number[]): string {
  const labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
  const scene = toScene(
    {
      data: { labels, series: [values] },
      axes: cartesianAxes([
        { id: 'x', side: 'bottom', type: stepAxis() },
        { id: 'y', side: 'left', type: autoScaleAxis() }
      ]),
      layers: [axisGrid(), line(), axisLabels()]
    },
    { width: 600, height: 300 }
  );
  return renderToString(scene);
}

toScene() takes a config, so it works with chart()’s building blocks: axes and layers. 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). The composed charts page has more on axes and layers.

The browser can run the same code. This example builds the markup with toScene() and renderToString() and puts it into the page with innerHTML:

src/main.ts
import 'chartist/styles.css';
import './style.css';
import {
  autoScaleAxis,
  axisGrid,
  axisLabels,
  cartesianAxes,
  line,
  renderToString,
  stepAxis,
  toScene
} from 'chartist';

const labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];

// The same code runs on a server: no DOM needed up to here.
const scene = toScene(
  {
    data: {
      labels,
      series: [
        [12, 9, 7, 8, 5],
        [2, 1, 3.5, 7, 3]
      ]
    },
    axes: cartesianAxes([
      { id: 'x', side: 'bottom', type: stepAxis() },
      { id: 'y', side: 'left', type: autoScaleAxis() }
    ]),
    layers: [axisGrid(), line(), axisLabels()],
    viewBox: { width: 400, height: 250 }
  },
  { width: 400, height: 250 }
);
const svg = renderToString(scene);

// In the browser, the string goes into the page like any other markup.
const container = document.querySelector('#chart');
if (container) {
  container.innerHTML = svg;
}
Live

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:

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

Measuring text

SVG labels (see below) 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:

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 <span> inside a <foreignObject>, as 1.x did. CSS lays them out, wraps long text and aligns it with flexbox. Only browsers draw them.
  • svg: an SVG <text>, 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.

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 put 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 inside the SVG. Rasterizers support less CSS than browsers: keep those styles to plain selectors and values.

To put styles into the SVG, add a <style> element to the scene with a scene listener:

import {
  autoScaleAxis,
  axisGrid,
  axisLabels,
  cartesianAxes,
  line,
  node,
  renderToString,
  stepAxis,
  toScene
} from 'chartist';

const css = `
  .ct-grid { stroke: #ccc; stroke-dasharray: 2px; }
  .ct-label { fill: #666; font: 12px sans-serif; }
  .ct-line { fill: none; stroke: #d70206; stroke-width: 4px; }
  .ct-point { stroke: #d70206; stroke-width: 10px; stroke-linecap: round; }
`;

const labels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
const scene = toScene(
  {
    data: { labels, series: [[12, 9, 7, 8, 5]] },
    axes: cartesianAxes([
      { id: 'x', side: 'bottom', type: stepAxis() },
      { id: 'y', side: 'left', type: autoScaleAxis() }
    ]),
    layers: [axisGrid(), line(), axisLabels()],
    on: {
      scene: ({ scene }) => ({
        scene: scene.append(node('style').text(css), true)
      })
    }
  },
  { width: 600, height: 300 }
);

export const markup = renderToString(scene);

In the API reference: rendering.

Next: update charts with new data.