Chartist

Search

Find a guide, an API entry or an example.

Try

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:

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

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:

src/main.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<HTMLButtonElement>('#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`;
});
Live

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

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:

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

They’re grid lines with the class ct-grid-minor next to ct-grid, and Chartist’s stylesheet styles them with three custom properties:

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

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

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, a log axis is one axis among others. Each axis fits its range to the series of the layers drawn on it:

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.