Home/ Cookbook/ Charts/ Multi-series line

Compare metrics with a multi-series line chart

By the end of this recipe you'll have two-to-four lines on a shared axis — colour-coded, legend-tagged, and honest about which series you intend the reader to compare against which.

Charts Intermediate ~12 min React · @corelithzw/react

Overview

Two-to-four ordered series on the same time axis. The reader compares slopes; you pick the colours; the legend names the lines.

A multi-series line is for when the story is comparison. Revenue this year vs. last; signups by plan; gold-price vs. scrap-price. The shared axis is what makes the comparison legitimate — if your series don't share the y-unit, you don't have a multi-line, you have two charts pretending to be one.

Skip multi-line above 5 series — the colours collide and the eye gives up. Switch to small multiples: a 2×2 grid of single lines beats a five-colour spaghetti every time.

Default opinion: two series share one axis; three is the upper bound for a single panel. Beyond that, pick a different chart.

The chart

Revenue, costs and refunds across one month. Solid for healthy series, dashed for the one you'd rather was lower.

Revenue, costs and refunds, last 30 days Three lines: revenue rising from $1,800 to $4,800; costs flat near $2,000; refunds low and rising slightly. $5k$4k$3k$2k$1k May 1May 8May 15May 22May 30
Revenue Costs Refunds (dashed)
Revenue climbs from $1,800 to $4,800 over thirty days. Costs hold flat around $2,000. Refunds inch up from $1,000 to $1,200.

Required pieces

Everything this recipe pulls from @corelithzw/react.

Intended exports used here: Chart.Line, Chart.Legend, Chart.Series.

Roadmap. Chart.Series ships in @corelithzw/react v0.2. Until then, render the SVG inline or use a headless lib — the API shape below is what we're committing to.

The React snippet

Pass an array of series. Each series sets its own colour and an optional dashed style.

RevenueCompare.tsx@corelithzw/react

Customising

Three forks — colours, axes, animation.

Pick safer palettes

Above 3 series, switch from semantic tones to a categorical palette so colours don't carry meaning the data doesn't.

<Chart.Line
  data={data}
  palette="categorical"  {/* brand-rotated */}
  series={mySeries}
  height={280}
/>

Pin and align the axes

If you redraw on filter changes, pin the y-domain so the line doesn't jump around between renders.

<Chart.Line
  data={data}
  yDomain={[0, 5000]}
  xTicks={6}
  legend="bottom"
  height={280}
/>

Stagger the draw

Animate each series 80ms after the last so the reader's eye follows them in order — most-important first.

<Chart.Line
  data={data}
  series={mySeries}
  animate="draw"
  animateStaggerMs={80}
  height={280}
/>

In context

Drop into a "compare two periods" report card. The legend doubles as the period picker.

This month vs. last
Revenue · daily
May April (dashed)

Accessibility

Multi-series adds one risk: telling the lines apart without colour.

  • Role and label. Root <svg> uses role="img" with an aria-labelledby pointing to inline <title> + <desc> that name every series.
  • Pattern, not only colour. Dashed strokes carry a second signal (here, "refunds is the dashed one"). Pair every colour with a legend entry that repeats the line style.
  • Screen-reader summary. A visually-hidden paragraph (.ck-sr-only) reads out each series' start, end and direction in one sentence.
  • Series-level navigation. Each series is a <g tabindex="0"> with an aria-label like "Revenue: 1,800 to 4,800" — Tab walks between series, Arrow keys walk between points within one.
  • Reduced motion. prefers-reduced-motion: reduce disables the stagger; all series render at final state.