LineChart
Change over time, a line per series, read at any point with a crosshair.
CI run time
Median minutes per run in October · sample data
- cennge-ui
- payload-cache
Arrow keys move between the values.
| Day | cennge-ui | payload-cache |
|---|---|---|
| Oct 1 | 11.2 min | 4.1 min |
| Oct 2 | 10.8 min | 4.3 min |
| Oct 3 | 11.5 min | 3.9 min |
| Oct 4 | 10.9 min | 4.6 min |
| Oct 5 | 9.8 min | 3.2 min |
| Oct 6 | 9.6 min | 3.1 min |
| Oct 7 | 11.9 min | 4.4 min |
| Oct 8 | 12.4 min | 4.2 min |
| Oct 9 | 12.1 min | 4.8 min |
| Oct 10 | 11.6 min | 4.5 min |
| Oct 11 | 12.8 min | 4.7 min |
| Oct 12 | 10.2 min | 3.4 min |
| Oct 13 | 9.9 min | – |
| Oct 14 | 12.5 min | – |
| Oct 15 | 12.9 min | – |
| Oct 16 | 13.4 min | 5.1 min |
| Oct 17 | 12.7 min | 4.9 min |
| Oct 18 | 13.1 min | 5.3 min |
| Oct 19 | 10.6 min | 3.8 min |
| Oct 20 | 10.4 min | 3.6 min |
| Oct 21 | 12.2 min | 4.6 min |
| Oct 22 | 11.8 min | 4.4 min |
| Oct 23 | 11.4 min | 4.9 min |
| Oct 24 | 11.9 min | 4.7 min |
| Oct 25 | 12.3 min | 4.2 min |
| Oct 26 | 10.1 min | 3.3 min |
| Oct 27 | 9.7 min | 3.5 min |
| Oct 28 | 11.3 min | 4.1 min |
| Oct 29 | 10.9 min | 3.9 min |
| Oct 30 | 10.6 min | 4.3 min |
| Oct 31 | 10.8 min | 4 min |
payload-cache response time
95th percentile by hour · sample data
Arrow keys move between the values.
| Hour | p95 |
|---|---|
| 00:00 | 212 ms |
| 04:00 | 198 ms |
| 08:00 | 246 ms |
| 12:00 | 301 ms |
| 16:00 | 287 ms |
| 20:00 | 233 ms |
- Hover a day or tap it: the crosshair snaps to the nearest day and reads every series there.
- A missing value breaks its line, and reads as a dash in the readout and the table.
- With startAtZero off the axis fits the data, so a change small next to the values still shows.
Field marks
- Import
import { LineChart } from "cennge-ui/charts";- Size
- 5.6 KB gzip for this import, its dependencies aside
- Pattern
- A
<figure>with a focusable group over the plot and a hidden<table>of the data - Keyboard
- Arrow left Arrow right Arrow up Arrow down HomeEndEsc
- SSR
- Rendered on the server in the Next.js check; hydrates without a warning
- Lines
- 2px, straight or a
smoothcurve that never overshoots its points - Tests
- Unit tests; end-to-end in Chromium, Firefox and WebKit, on desktop and phone viewports: line-chart
- Source
- src/charts
When to use#
Use a LineChart when the order of the values matters: days, weeks, releases, hours. The line shows the shape of a change across many points, and the crosshair gives the exact values at any one of them. Each series is a line in its own colour, named in the legend beside a sample of its stroke.
Similar#
- AreaChart is the same chart with the area under each line washed in its colour, and it can stack series into a whole.
- BarChart takes the same
data,indexandseriesand compares amounts by length, from an axis that takes in zero unless you set adomain. - Sparkline is a trend without axes or a readout, small enough to sit beside a number.
One line#
A single series has no legend; the aria-label names the chart and its table. The numbers on this page are sample data.
Arrow keys move between the values.
| Week of | Open issues |
|---|---|
| Aug 17 | 31 |
| Aug 24 | 34 |
| Aug 31 | 33 |
| Sep 7 | 38 |
| Sep 14 | 36 |
| Sep 21 | 29 |
| Sep 28 | 27 |
| Oct 5 | 30 |
| Oct 12 | 24 |
| Oct 19 | 22 |
| Oct 26 | 23 |
| Nov 2 | 19 |
import { LineChart } from "cennge-ui/charts";
const weeks = [
{ week: "Aug 17", open: 31 },
{ week: "Aug 24", open: 34 },
{ week: "Aug 31", open: 33 },
{ week: "Sep 7", open: 38 },
{ week: "Sep 14", open: 36 },
{ week: "Sep 21", open: 29 },
{ week: "Sep 28", open: 27 },
{ week: "Oct 5", open: 30 },
{ week: "Oct 12", open: 24 },
{ week: "Oct 19", open: 22 },
{ week: "Oct 26", open: 23 },
{ week: "Nov 2", open: 19 },
];
export default function OpenIssues() {
return (
<LineChart
aria-label="Open issues in ai-niche-scout per week"
data={weeks}
index="week"
indexLabel="Week of"
series={[{ key: "open", label: "Open issues" }]}
className="h-56"
/>
);
}Several series#
Series take the palette's slots in the order of series, the first in the accent, so a series added at the end never repaints the others. The readout lists them in the same order.
- Opened
- Merged
- Closed unmerged
Arrow keys move between the values.
| Month | Opened | Merged | Closed unmerged |
|---|---|---|---|
| May | 26 | 19 | 4 |
| Jun | 31 | 27 | 3 |
| Jul | 29 | 30 | 6 |
| Aug | 38 | 29 | 5 |
| Sep | 42 | 40 | 2 |
| Oct | 36 | 41 | 4 |
import { LineChart } from "cennge-ui/charts";
const months = [
{ month: "May", opened: 26, merged: 19, closed: 4 },
{ month: "Jun", opened: 31, merged: 27, closed: 3 },
{ month: "Jul", opened: 29, merged: 30, closed: 6 },
{ month: "Aug", opened: 38, merged: 29, closed: 5 },
{ month: "Sep", opened: 42, merged: 40, closed: 2 },
{ month: "Oct", opened: 36, merged: 41, closed: 4 },
];
export default function PullRequests() {
return (
<LineChart
aria-label="Pull requests in cennge-ui per month"
data={months}
index="month"
indexLabel="Month"
series={[
{ key: "opened", label: "Opened" },
{ key: "merged", label: "Merged" },
{ key: "closed", label: "Closed unmerged" },
]}
className="h-64"
/>
);
}A gap in the data#
playwright-msi-scraper did not run over the weekend. Give null where there is no value: the line stops and starts again after it instead of diving to zero, and the readout and the table show a dash. A value alone between two gaps is drawn as a dot.
Arrow keys move between the values.
| Day | Pages |
|---|---|
| Oct 27 | 1,840 |
| Oct 28 | 1,920 |
| Oct 29 | 2,310 |
| Oct 30 | 2,240 |
| Oct 31 | – |
| Nov 1 | – |
| Nov 2 | 2,405 |
| Nov 3 | 2,120 |
| Nov 4 | 2,260 |
| Nov 5 | 2,380 |
import { LineChart } from "cennge-ui/charts";
// The scraper did not run over the weekend: null, not 0.
const days = [
{ day: "Oct 27", pages: 1840 },
{ day: "Oct 28", pages: 1920 },
{ day: "Oct 29", pages: 2310 },
{ day: "Oct 30", pages: 2240 },
{ day: "Oct 31", pages: null },
{ day: "Nov 1", pages: null },
{ day: "Nov 2", pages: 2405 },
{ day: "Nov 3", pages: 2120 },
{ day: "Nov 4", pages: 2260 },
{ day: "Nov 5", pages: 2380 },
];
export default function PagesScraped() {
return (
<LineChart
aria-label="Pages scraped per day"
data={days}
index="day"
indexLabel="Day"
series={[{ key: "pages", label: "Pages" }]}
className="h-56"
/>
);
}Small changes, fitted axis#
The value axis starts at zero by default. With startAtZero={false} it fits round ends around the data, which suits a rate that never leaves the nineties. showDots marks every value, and axisFormat writes the axis as valueFormat writes the readout.
Each dot sits in a 2px ring of the colour behind the chart, so it stays clear of the line. The ring takes --cennge-chart-surface, which falls back to a Card's surface; set it when the chart sits on another one, as here.
Arrow keys move between the values.
| Day | Hit rate |
|---|---|
| Mon | 94.2% |
| Tue | 95.1% |
| Wed | 93.8% |
| Thu | 96% |
| Fri | 96.4% |
| Sat | 92.7% |
| Sun | 93.5% |
import { LineChart } from "cennge-ui/charts";
const days = [
{ day: "Mon", hits: 94.2 },
{ day: "Tue", hits: 95.1 },
{ day: "Wed", hits: 93.8 },
{ day: "Thu", hits: 96 },
{ day: "Fri", hits: 96.4 },
{ day: "Sat", hits: 92.7 },
{ day: "Sun", hits: 93.5 },
];
const percent = (value: number) => `${value}%`;
export default function CacheHitRate() {
return (
<LineChart
aria-label="payload-cache hit rate per day"
data={days}
index="day"
indexLabel="Day"
series={[{ key: "hits", label: "Hit rate" }]}
startAtZero={false}
showDots
valueFormat={percent}
axisFormat={percent}
// The dots sit in a ring of the colour behind the chart.
className="h-56 [--cennge-chart-surface:var(--cennge-surface-2)]"
/>
);
}A smooth curve#
curve="smooth" bends through the points without overshooting them: between two equal values the curve stays flat, and a count of zero never dips below the axis.
Arrow keys move between the values.
| Day | Failed runs |
|---|---|
| Mon | 0 |
| Tue | 3 |
| Wed | 0 |
| Thu | 0 |
| Fri | 5 |
| Sat | 1 |
| Sun | 0 |
import { LineChart } from "cennge-ui/charts";
const days = [
{ day: "Mon", failed: 0 },
{ day: "Tue", failed: 3 },
{ day: "Wed", failed: 0 },
{ day: "Thu", failed: 0 },
{ day: "Fri", failed: 5 },
{ day: "Sat", failed: 1 },
{ day: "Sun", failed: 0 },
];
export default function FailedRuns() {
return (
<LineChart
aria-label="Failed CI runs per day"
data={days}
index="day"
indexLabel="Day"
curve="smooth"
series={[{ key: "failed", label: "Failed runs", color: "danger" }]}
className="h-48"
/>
);
}Accessibility#
- The figure and its plot are named by
aria-label; the plot is a focusable group described bylabels.keyboardHint. - Focus from the keyboard puts the crosshair on the first point. The arrows move it, Home and End jump to the ends, Escape hides it, and a polite live region reads each place, as "Oct 2: cennge-ui 10.8 min, payload-cache 4.3 min".
- The readout is for the eye only: screen readers get every value in a table captioned with the chart's name, a missing value written as a dash.
- A finger scrubs along the chart while the page still scrolls up and down; a tap leaves the readout up until a tap elsewhere.
Server rendering#
The legend, the axes, the grid and the data table come from the server. The lines are drawn once the browser has measured the plot and draw in from the left; with reduced motion they appear at once.
Props#
Each entry of series is { key, label, color }: the field that holds its values, its name (the key without one), and a palette slot from chart-1 to chart-8, a theme colour or any CSS colour.