Charts overview
Five charts in the theme's tokens, drawn in SVG by cennge-ui itself: bars, lines, areas, a donut and a sparkline.
CI minutes in October
2,840
up from 1,700 in March
cennge-ui in October
1,610
57% of all the minutes
CI minutes by repository
Per month, stacked · sample data
- cennge-ui
- playwright-msi-scraper
- payload-cache
- ai-niche-scout
- spredo-crypto-screener
Arrow keys move between the values.
| Month | cennge-ui | playwright-msi-scraper | payload-cache | ai-niche-scout | spredo-crypto-screener |
|---|---|---|---|---|---|
| Mar | 820 | 310 | 240 | 180 | 150 |
| Apr | 910 | 290 | 260 | 210 | 140 |
| May | 1,040 | 340 | 230 | 260 | 170 |
| Jun | 1,180 | 360 | 280 | 240 | 190 |
| Jul | 1,320 | 330 | 310 | 220 | 160 |
| Aug | 1,250 | 380 | 290 | 270 | 180 |
| Sep | 1,460 | 410 | 330 | 300 | 200 |
| Oct | 1,610 | 390 | 350 | 280 | 210 |
- Hover a month, tap it, or Tab to the chart and use the arrows: the readout lists every series at that place.
- Series take the palette in order and the first slot is the accent: switch it at the top of the page.
- The cennge-ui tile gives its sparkline chart-1, the colour of the cennge-ui bars; the other keeps the default grey with the accent on its last value.
Field marks
- Pattern
- A named
<figure>with a hidden<table>of its data; a Sparkline isrole="img" - Keyboard
- Arrow left Arrow right Arrow up Arrow down HomeEndEsc
- Size
- 8.4 KB gzip for the whole entry; each chart's page gives its own
- Underneath
- No chart library: SVG and HTML from cennge-ui, ESM only
- Tests
- Unit tests
- Source
- src/charts
When to use#
cennge-ui/charts is a second entry of the same package, for the charts a product page or a dashboard needs. Pick the chart by the job the data does:
- Amounts compared across categories: BarChart, side by side, stacked, or from the side for long names.
- Change over time, a line per series: LineChart.
- One quantity over time, or a whole split into parts over time: AreaChart.
- The parts of one whole, six at most: DonutChart.
- A trend beside the number it belongs to, without axes: Sparkline.
import { BarChart, LineChart, Sparkline } from "cennge-ui/charts";The entry is ESM only. It needs nothing beyond the setup in Installation: the palette, the animations and the classes the charts use come with the library's stylesheet.
Similar#
- Table is for exact values a reader looks up, sorts or copies.
- Progress shows one value against a maximum.
- Data layers put values on a map, by place: choropleths and clusters.
One data shape#
BarChart, LineChart and AreaChart take the same props, CartesianChartProps: data with one item per category, index naming the field that labels it, and series listing the fields to draw. Switch the chart and the data stays as it is. The numbers in the examples are sample data.
- Opened
- Closed
Arrow keys move between the values.
| Month | Opened | Closed |
|---|---|---|
| May | 18 | 12 |
| Jun | 24 | 22 |
| Jul | 31 | 26 |
| Aug | 27 | 30 |
| Sep | 35 | 33 |
| Oct | 29 | 34 |
import { useState } from "react";
import { ToggleGroup, ToggleGroupItem } from "cennge-ui";
import { AreaChart, BarChart, LineChart, type CartesianChartProps } from "cennge-ui/charts";
const months = [
{ month: "May", opened: 18, closed: 12 },
{ month: "Jun", opened: 24, closed: 22 },
{ month: "Jul", opened: 31, closed: 26 },
{ month: "Aug", opened: 27, closed: 30 },
{ month: "Sep", opened: 35, closed: 33 },
{ month: "Oct", opened: 29, closed: 34 },
];
const chart: CartesianChartProps<(typeof months)[number]> = {
"aria-label": "Issues in cennge-ui per month",
data: months,
index: "month",
indexLabel: "Month",
series: [
{ key: "opened", label: "Opened" },
{ key: "closed", label: "Closed" },
],
className: "h-64",
};
export default function OneShape() {
const [kind, setKind] = useState("bar");
return (
<div className="flex w-full flex-col gap-4">
<ToggleGroup aria-label="Chart" size="sm" value={kind} onValueChange={(value) => value && setKind(value)}>
<ToggleGroupItem value="bar">BarChart</ToggleGroupItem>
<ToggleGroupItem value="line">LineChart</ToggleGroupItem>
<ToggleGroupItem value="area">AreaChart</ToggleGroupItem>
</ToggleGroup>
{kind === "bar" ? <BarChart {...chart} /> : kind === "line" ? <LineChart {...chart} /> : <AreaChart {...chart} />}
</div>
);
}The palette#
Series take eight slots in order, --cennge-chart-1 to --cennge-chart-8, which Tailwind has as chart-1 to chart-8 for legends and tiles of your own. A ninth series is grey, default, never a new colour. Switch the accent at the top of the page: the first slot follows it, and the order after it changes with the preset.
- chart-1
- chart-2
- chart-3
- chart-4
- chart-5
- chart-6
- chart-7
- chart-8
- default
const slots = [
{ name: "chart-1", swatch: "bg-chart-1" },
{ name: "chart-2", swatch: "bg-chart-2" },
{ name: "chart-3", swatch: "bg-chart-3" },
{ name: "chart-4", swatch: "bg-chart-4" },
{ name: "chart-5", swatch: "bg-chart-5" },
{ name: "chart-6", swatch: "bg-chart-6" },
{ name: "chart-7", swatch: "bg-chart-7" },
{ name: "chart-8", swatch: "bg-chart-8" },
{ name: "default", swatch: "bg-default-500" },
];
export default function Palette() {
return (
<ul className="grid w-full grid-cols-3 gap-3 sm:grid-cols-9">
{slots.map((slot) => (
<li key={slot.name} className="flex flex-col gap-1.5">
<span className={`h-10 rounded-medium ${slot.swatch}`} />
<span className="font-code text-xs text-muted">{slot.name}</span>
</li>
))}
</ul>
);
}The slots were checked by a script, not picked by eye:
- each sits in the lightness band charts read best in (OKLCH lightness 0.43 to 0.77 in the light theme, 0.48 to 0.67 in the dark), with a chroma of at least 0.1;
- each keeps a contrast of 3:1 on every surface;
- slots next to each other stay apart for colour-blind readers: their OKLab distance (×100) is at least 8.5 under simulated protanopia and deuteranopia, and at least 16.7 with full colour vision;
- after the accent, each preset has the order whose closest neighbours stay furthest apart. With the violet accent, orange comes second and blue fourth, because violet next to blue blurs.
Give a series a theme colour where the colour means something (failures in danger), default for the grey, or any CSS colour.
Numbers and words in your language#
Values follow the locale of CenngeProvider: in full in the readout and the table, short on the axis. valueFormat and axisFormat replace either, and labels replaces the words the charts say themselves: the keyboard hint, the empty state and, on a DonutChart, the total and the share. The provider sets no language on the page, so lang on an element around the chart tells screen readers to read it in French.
Les flèches passent d’une valeur à l’autre.
| Mois | Minutes |
|---|---|
| juil. | 2 340 |
| août | 2 370 |
| sept. | 2 700 |
| oct. | 2 840 |
import { CenngeProvider } from "cennge-ui";
import { BarChart } from "cennge-ui/charts";
const months = [
{ month: "juil.", minutes: 2340 },
{ month: "août", minutes: 2370 },
{ month: "sept.", minutes: 2700 },
{ month: "oct.", minutes: 2840 },
];
export default function FrenchChart() {
return (
<CenngeProvider locale="fr-FR">
<div lang="fr" className="w-full">
<BarChart
aria-label="Minutes de CI par mois"
data={months}
index="month"
indexLabel="Mois"
series={[{ key: "minutes", label: "Minutes" }]}
labels={{ keyboardHint: "Les flèches passent d’une valeur à l’autre.", empty: "Aucune donnée" }}
className="h-56"
/>
</div>
</CenngeProvider>
);
}Accessibility#
- BarChart, LineChart, AreaChart and DonutChart are each a
<figure>named by itsaria-label. Each holds a focusable group, described bylabels.keyboardHint, that answers to the arrow keys, Home, End and Escape; a polite live region reads each place the keys reach. - The readout is drawn for the eye only. Screen readers get the same data as a table captioned with the chart's name; in BarChart, LineChart and AreaChart a missing value is written as a dash.
- Colour never names a series alone: the legend puts each name beside a sample of its mark, a box for bars and areas, a stroke for lines.
- A Sparkline has no readout. It is one picture,
role="img", and itsaria-labelis all a screen reader hears. - The end-to-end tests run axe over every kind of chart in both themes, and again with a readout open on BarChart, LineChart and DonutChart.
Server rendering#
The axes are HTML and the grid an SVG, both placed in percent, so the server sends them where they will stay, with the legend and the data table. Bars, lines and areas are drawn in pixels once the browser has measured the plot. DonutChart and Sparkline draw in a fixed viewBox and are whole on the server.
Styling#
- Size a chart with
className. The defaulth-72of BarChart, LineChart and AreaChart takes in the axes and the legend; a Sparkline ish-8 w-24until you say otherwise. - A dot on a line sits in a ring of the colour behind the chart,
--cennge-chart-surface, which falls back to a Card's surface. Set it when the chart sits on another one. - Bars grow, lines and areas draw in and the donut pops in as they appear; with reduced motion nothing moves.
Props#
Shared by BarChart, LineChart and AreaChart#
Each entry of series is { key, label, color }: the field that holds its values, its name in the legend, the readout and the table (the key without one), and its colour.