Charts
LineChart
Several series compared over a continuous axis.
Usage
When to reach for it
Ships from@misoto22/design/charts
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
| Element | Description |
|---|---|
| Figure framerequired | ChartFigure’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 segment | buffer 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 table | The 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.
| month | Desktop | Mobile |
|---|---|---|
| Jan | 186 | 80 |
| Feb | 305 | 200 |
| Mar | 237 | 120 |
| Apr | 273 | 190 |
| May | 209 | 130 |
| Jun | 314 | 240 |
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.
| 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.
| Desktop | Mobile |
|---|---|
| 186 | 80 |
| 305 | 200 |
| 237 | 120 |
| 273 | 190 |
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.
| 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.
| Desktop | Mobile |
|---|---|
| 186 | 80 |
| 305 | 200 |
| 237 | 120 |
| 273 | 190 |
| 209 | 130 |
| 314 | 240 |
| Desktop | Mobile |
|---|---|
| 186 | 80 |
| 305 | 200 |
| 237 | 120 |
| 273 | 190 |
| 209 | 130 |
| 314 | 240 |
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.
| day | Desktop | Mobile |
|---|---|---|
| D1 | 160 | 140 |
| D2 | 177 | 139 |
| D3 | 194 | 136 |
| D4 | 208 | 131 |
| D5 | 219 | 125 |
| D6 | 226 | 117 |
| D7 | 230 | 108 |
| D8 | 229 | 98 |
| D9 | 224 | 89 |
| D10 | 214 | 79 |
| D11 | 202 | 69 |
| D12 | 187 | 61 |
| D13 | 170 | 53 |
| D14 | 152 | 47 |
| D15 | 135 | 43 |
| D16 | 120 | 41 |
| D17 | 107 | 40 |
| D18 | 97 | 42 |
| D19 | 92 | 45 |
| D20 | 90 | 50 |
| D21 | 93 | 57 |
| D22 | 100 | 65 |
| D23 | 111 | 75 |
| D24 | 124 | 84 |
| D25 | 140 | 94 |
| D26 | 158 | 104 |
| D27 | 175 | 113 |
| D28 | 192 | 122 |
| D29 | 206 | 129 |
| D30 | 218 | 134 |
| D31 | 226 | 138 |
| D32 | 230 | 140 |
| D33 | 229 | 140 |
| D34 | 225 | 138 |
| D35 | 216 | 133 |
| D36 | 204 | 128 |
| D37 | 189 | 120 |
| D38 | 172 | 112 |
| D39 | 155 | 103 |
| D40 | 138 | 93 |
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.
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 |
|---|
| 1.42 |
| 1.61 |
| 2.04 |
| 1.88 |
| 1.34 |
| 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.
| week | p95 latency | p50 latency |
|---|---|---|
| W1 | 142 | 61 |
| W2 | 161 | 64 |
| W3 | 204 | 66 |
| W4 | 188 | 63 |
| W5 | 134 | 58 |
| W6 | 129 | 57 |
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.
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
| day | Desktop | Mobile |
|---|---|---|
| D1 | 160 | 140 |
| D2 | 172 | 140 |
| D3 | 183 | 139 |
| D4 | 194 | 137 |
| D5 | 203 | 135 |
| D6 | 212 | 132 |
| D7 | 219 | 129 |
| D8 | 224 | 126 |
| D9 | 228 | 122 |
| D10 | 230 | 117 |
| D11 | 230 | 112 |
| D12 | 228 | 107 |
| D13 | 224 | 102 |
| D14 | 218 | 96 |
| D15 | 211 | 91 |
| D16 | 202 | 85 |
| D17 | 192 | 80 |
| D18 | 181 | 74 |
| D19 | 170 | 69 |
| D20 | 158 | 64 |
| D21 | 147 | 60 |
| D22 | 135 | 55 |
| D23 | 125 | 52 |
| D24 | 115 | 48 |
| D25 | 107 | 46 |
| D26 | 100 | 43 |
| D27 | 95 | 42 |
| D28 | 92 | 41 |
| D29 | 90 | 40 |
| D30 | 91 | 40 |
| D31 | 93 | 41 |
| D32 | 97 | 42 |
| D33 | 103 | 44 |
| D34 | 111 | 47 |
| D35 | 120 | 50 |
| D36 | 130 | 53 |
| D37 | 140 | 57 |
| D38 | 152 | 62 |
| D39 | 164 | 66 |
| D40 | 175 | 71 |
| D41 | 186 | 77 |
| D42 | 197 | 82 |
| D43 | 206 | 88 |
| D44 | 214 | 93 |
| D45 | 221 | 99 |
| D46 | 226 | 104 |
| D47 | 229 | 109 |
| D48 | 230 | 114 |
| D49 | 229 | 119 |
| D50 | 227 | 123 |
| D51 | 222 | 127 |
| D52 | 216 | 131 |
| D53 | 208 | 134 |
| D54 | 199 | 136 |
| D55 | 189 | 138 |
| D56 | 178 | 139 |
| D57 | 166 | 140 |
| D58 | 155 | 140 |
| D59 | 143 | 139 |
| D60 | 132 | 138 |
| D61 | 122 | 136 |
| D62 | 113 | 134 |
| D63 | 105 | 131 |
| D64 | 98 | 128 |
| D65 | 94 | 124 |
| D66 | 91 | 120 |
| D67 | 90 | 115 |
| D68 | 91 | 110 |
| D69 | 94 | 105 |
| D70 | 99 | 99 |
| D71 | 105 | 94 |
| D72 | 113 | 88 |
| D73 | 122 | 83 |
| D74 | 133 | 77 |
| D75 | 144 | 72 |
| D76 | 155 | 67 |
| D77 | 167 | 62 |
| D78 | 178 | 58 |
| D79 | 189 | 54 |
| D80 | 200 | 50 |
| D81 | 209 | 47 |
| D82 | 216 | 44 |
| D83 | 222 | 42 |
| D84 | 227 | 41 |
| D85 | 229 | 40 |
| D86 | 230 | 40 |
| D87 | 229 | 40 |
| D88 | 225 | 41 |
| D89 | 220 | 43 |
| D90 | 214 | 45 |
Types
export type LineStrokeVariant = AreaStrokeVariantAccessibility
- 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.