Skip to content
Folio

Charts

LineChart

Several series compared over a continuous axis.

Usage

When to reach for it

The reader is comparing series against each other. When the area under one line is the point, fill it — that is an AreaChart.

Ships from@misoto22/design/charts

TSX
import { LineChart } from '@misoto22/design/charts'

Notes

Several series compared over a continuous axis — the shape for "which of these is going where".

The difference from <AreaChart> is what the reader is asked to do: an area says "read the magnitude under this", a line says "compare these against each other". Filling four overlapping series makes the second question unanswerable, which is why a line chart has no fill variant to offer.

Anatomy

LineChart anatomy
ElementDescription
Figure framerequiredChartFigure’s <figure> and its sr-only caption. description is announced with the title and printed only under showTitle, which is where a note about a clipped axis belongs.
Linesrequired<LineChart.Line>, one per series at a 1.6px stroke. isClickable adds a second, fully transparent 15px line underneath the visible one, because a hairline is not a pointer target.
Point markers<LineChart.Dot> and <LineChart.ActiveDot>, both slots and both off by default. The resting dot shares the intro wipe mask so it arrives with its own line; the active dot is never masked, because it exists only on hover, long after the wipe has finished.
Buffer segmentbuffer draws the last leg dashed by measuring the real path with getPointAtLength, so a projection reads as a different kind of fact at any curve type. Fewer than two drawable points and it falls back to a plain curve.
Sonification control<LineChart.Sonify>, a real <button> above the plot that plays the visible rows as pitch. Sound never starts from an effect, only from that click, and it reads the brushed window rather than the whole series.
Hidden data tableThe sr-only table of the full data. Recharts’ accessibilityLayer gives a keyboard cursor that announces one point at a time, which is navigation; this is the figures.

Best practices

Do

  • This is the one chart in the family that survives a truncated value axis. A line encodes by SLOPE, so clipping the domain to the data’s own range is often what makes a two percent move visible at all — state the range in description when you do it.
  • Reach for buffer on a period still open rather than dropping it. A part-month plotted solid reads as a crash; plotted as a dashed final leg it reads as what it is, which is incomplete.
  • Compose <LineChart.Dot> when the series is sparse. With dots off — the default — five points are four segments, and the reader cannot tell a measured value from a bend in the interpolation.

Don’t

  • A single row draws nothing at all: one point has no segment, dot is false unless composed, and the empty state does not fire because a row exists. Guard the one-row case at the call site.
  • Eight lines in one frame is a hairball, and a 1.6px stroke over an eight-step grey ramp makes it a worse one than a chromatic chart would. Past about five series the answer is Facet, not a ninth ramp slot — SERIES_SLOTS is 8 and there is no ninth.
  • connectNulls turns a gap into a straight segment that looks measured. It costs more here than on an area, because the reader reads the slope of that invented segment as a rate.

Examples

default

Several series compared against each other, which is the one question a line chart exists for. There is no fill variant on offer here on purpose: filling four overlapping series makes that comparison unanswerable. The stroke carries the difference instead — the second line is dashed rather than left to a step of grey — and the legend names both, because neither the ramp nor the dash describes itself.

Visitors per month
Visitors per month
monthDesktopMobile
Jan18680
Feb305200
Mar237120
Apr273190
May209130
Jun314240

stroke dot curve

Three knobs on one line: how the stroke is drawn, what marks each point, and how the curve gets from one row to the next. Every dot is painted from its series gradient sampled at the x it sits on — a full-width rect filled with that gradient and clipped to a circle — because filling the circle directly would restart the gradient inside each marker, and every dot on the line would come out the same colour. The curve is the knob that changes what the chart claims rather than how it looks, and the rows themselves cannot tell you which setting is honest.

Visitors per month
Visitors per month
Desktop
186
305
237
273
209
314

legend variants

Seven swatch shapes, and the mark is doing more work here than it would in a chromatic system: two lines differ by a step of grey and a dash pattern, and neither names itself, which is why a legend is required above one series rather than decorative. Matching the swatch to the mark — a bar beside a bar chart, a dot beside a scatter — is often the fastest way a reader ties the key to the plot.

Visitors per month
Visitors per month
DesktopMobile
18680
305200
237120
273190

backgrounds

Eleven decorative plates, and the count is the point: with no hue to spend, a chart's personality has to come from somewhere, and the plate is the one place texture can be loud without competing with the marks. It is drawn to the plot rectangle rather than to the whole SVG, so it stops where the data stops and never runs underneath the tick labels. It is not a grid, though — this example composes it in place of one, and a plate helps nobody read a value off an axis.

Visitors per month — dots plate
Visitors per month — dots plate
Desktop
186
305
237
273
209
314

emphasis

Two ways to mark one line out from the other. A monochrome chart has no brighter colour to reach for, so glowing puts a halo behind the one series that is the point of the figure, and it is the only blur in the package. buffer dashes the final leg, which is the idiom for a value that is a projection rather than a measurement. showTitle prints each figure's name, hidden by default because most call sites already have a heading above the chart.

Glowing — one series is the point
Glowing — one series is the point
DesktopMobile
18680
305200
237120
273190
209130
314240
Buffer — the last leg is a projection
Buffer — the last leg is a projection
DesktopMobile
18680
305200
237120
273190
209130
314240

brush

Forty rows and two series, which is where a line chart starts crossing itself. The strip is a miniature of the whole series with a lit window over the part the plot above is showing, so the reader can see what they are choosing from while they choose it. Its handles are positioned by inline style rather than by start and end classes on purpose: a cartesian plot is drawn first row first in every writing direction, so mirroring them under dir="rtl" would put the earlier handle at the end of a chart that still runs the other way.

Visitors per day
Visitors per day
dayDesktopMobile
D1160140
D2177139
D3194136
D4208131
D5219125
D6226117
D7230108
D822998
D922489
D1021479
D1120269
D1218761
D1317053
D1415247
D1513543
D1612041
D1710740
D189742
D199245
D209050
D219357
D2210065
D2311175
D2412484
D2514094
D26158104
D27175113
D28192122
D29206129
D30218134
D31226138
D32230140
D33229140
D34225138
D35216133
D36204128
D37189120
D38172112
D39155103
D4013893

loading

The state every chart on a page is in on first paint. isLoading swaps the marks for a skeleton and keeps the chart's measured height, so nothing on the page moves when the rows land. The skeleton's rows are re-rolled as the shimmer leaves the plot rather than on a timer, because an interval drifts against the animation and eventually re-rolls them in full view, which reads as the chart glitching rather than as it loading.

Visitors per month
Loading

axis labels and format

Naming what an axis measures, and writing its numbers the way the reader thinks of them. An axis reading 0, 100, 200 says nothing about whether those are people, milliseconds or dollars, so name the unit once — on the axis, or in the series label, but once, because the second copy is noise. formatNumber returns a function rather than a string so it can be handed straight to a tickFormatter, which Recharts calls once per tick and which must not rebuild an Intl.NumberFormat each time; a formatted tick is also wider, which is what the explicit width is for.

p95 latency
p95 latency
p95 latency
1.42
1.61
2.04
1.88
1.34
Ad spend
Ad spend
Ad spend
4,210
5,180
4,890
6,320
7,010

sonify

Two series play one after the other, each introduced by name, rather than together with one panned to each ear. Panning is the better demo and the worse choice: it assumes a stereo output and two usable ears, so a mono speaker or a single hearing aid collapses both runs into one melody the listener cannot unpick, and it caps the feature at two series. Both are pitched against the same range, exactly as they share one value axis on screen, so p50's flatness low in the range is audible as flatness rather than renormalised into a second dramatic line.

Latency per week, milliseconds
Latency per week, milliseconds
weekp95 latencyp50 latency
W114261
W216164
W320466
W418863
W513458
W612957

toolbar

The toolbar and the brush drive one window, so they cannot disagree: step the zoom and the handles below move, drag a handle and reset zoom lights up. Ninety days is the case that wants both — the strip shows the shape you are choosing from, the buttons step into it precisely. Composing the toolbar is also what switches the plot's own gestures on: it becomes focusable, arrow keys pan, plus and minus zoom, zero resets, and dragging across the plot zooms to that span. The wheel is gated behind Ctrl or Cmd, because a chart that swallowed a plain wheel event would trap the page's scroll under the pointer.

Visitors per day

Arrow keys pan by one point, Page Up and Page Down by a screenful, Home and End jump to the ends. Plus and minus zoom, zero resets. Ctrl and the wheel zoom around the pointer, and dragging across the plot zooms to that span.

Showing D1 to D90, 90 of 90 points

Visitors per day
dayDesktopMobile
D1160140
D2172140
D3183139
D4194137
D5203135
D6212132
D7219129
D8224126
D9228122
D10230117
D11230112
D12228107
D13224102
D1421896
D1521191
D1620285
D1719280
D1818174
D1917069
D2015864
D2114760
D2213555
D2312552
D2411548
D2510746
D2610043
D279542
D289241
D299040
D309140
D319341
D329742
D3310344
D3411147
D3512050
D3613053
D3714057
D3815262
D3916466
D4017571
D4118677
D4219782
D4320688
D4421493
D4522199
D46226104
D47229109
D48230114
D49229119
D50227123
D51222127
D52216131
D53208134
D54199136
D55189138
D56178139
D57166140
D58155140
D59143139
D60132138
D61122136
D62113134
D63105131
D6498128
D6594124
D6691120
D6790115
D6891110
D6994105
D709999
D7110594
D7211388
D7312283
D7413377
D7514472
D7615567
D7716762
D7817858
D7918954
D8020050
D8120947
D8221644
D8322242
D8422741
D8522940
D8623040
D8722940
D8822541
D8922043
D9021445

Types

TSX
export type LineStrokeVariant = AreaStrokeVariant

Accessibility

  • title is required; the rows are also rendered as a visually hidden table.
  • A clickable line gets a 15px transparent line underneath it, because a 1.6px stroke is not a pointer target.
  • buffer draws the last segment dashed by measuring the real path length, so a projection is visibly a different kind of fact at any curve type.