Skip to content
cennge-ui
GitHubv0.3.1, changelog

AreaChart

A line with the area under it washed in its colour: one quantity over time, or a whole split into the parts it is made of.

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.

CI minutes per month by repository
Monthcennge-uiplaywright-msi-scraperpayload-cacheai-niche-scoutspredo-crypto-screener
Mar820310240180150
Apr910290260210140
May1,040340230260170
Jun1,180360280240190
Jul1,320330310220160
Aug1,250380290270180
Sep1,460410330300200
Oct1,610390350280210

Requests to payload-cache

Every two hours, yesterday ยท sample data

Arrow keys move between the values.

Requests to payload-cache every two hours
FromRequests
00:002,180
02:001,650
04:001,920
06:005,100
08:008,600
10:009,300
12:009,050
14:009,350
16:008,200
18:006,100
20:004,400
22:003,100
  1. Stacked, each part fills down to the one below it, so the top line is the total.
  2. Hover a month or tap it: the readout gives each part's own value, not the running sum.
  3. Tab to a chart: the arrows move the crosshair, Home and End jump to the ends, Escape hides it.
Live: hover it, tap it, use the keys.

Field marks

Import
import { AreaChart } 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
Fill
The series' colour, fading towards the floor; the axis takes in zero unless you set domain
Tests
Unit tests; end-to-end in Chromium, Firefox and WebKit, on desktop and phone viewports: line-chart

When to use#

Use an AreaChart for one quantity over time when its size matters as much as its shape: the wash down to zero gives the line weight. With isStacked it shows how a whole is split, and how that split changes: minutes per repository adding up to the month's total.

Similar#

  • LineChart is the same chart without the wash. Its axis can fit the data with startAtZero={false}; an AreaChart has no such prop, and its axis takes in zero unless you set a domain.
  • BarChart stacks parts too, one bar per category, when the categories are not a sequence in time.
  • DonutChart splits a single whole, at one moment, into parts.

One quantity#

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.

CI minutes per month
MonthMinutes
Mar1,700
Apr1,810
May2,040
Jun2,250
Jul2,340
Aug2,370
Sep2,700
Oct2,840
basic.tsx
import { AreaChart } from "cennge-ui/charts";

const months = [
  { month: "Mar", minutes: 1700 },
  { month: "Apr", minutes: 1810 },
  { month: "May", minutes: 2040 },
  { month: "Jun", minutes: 2250 },
  { month: "Jul", minutes: 2340 },
  { month: "Aug", minutes: 2370 },
  { month: "Sep", minutes: 2700 },
  { month: "Oct", minutes: 2840 },
];

export default function CiMinutes() {
  return (
    <AreaChart
      aria-label="CI minutes per month"
      data={months}
      index="month"
      indexLabel="Month"
      series={[{ key: "minutes", label: "Minutes" }]}
      className="h-56"
    />
  );
}

Stacked, in colours that mean something#

isStacked piles the series up in the order of series, the first at the bottom. Each part fills down to the one below it, so the top edge is the total, and the readout and the table give each part's own value. In a stack a missing value counts as zero, so the parts above it still rest on something.

Without a color a series takes the palette slot of its place, as on the plate. Here theme colours carry a meaning instead: passed in success, flaky in warning, failed in danger.

  • Passed
  • Flaky
  • Failed

Arrow keys move between the values.

CI runs per week by result
Week ofPassedFlakyFailed
Aug 175246
Aug 245834
Aug 315567
Sep 76153
Sep 146425
Sep 215948
Sep 286834
Oct 57153
Oct 126626
Oct 197432
Oct 267913
Nov 28322
stacked.tsx
import { AreaChart } from "cennge-ui/charts";

const weeks = [
  { week: "Aug 17", passed: 52, flaky: 4, failed: 6 },
  { week: "Aug 24", passed: 58, flaky: 3, failed: 4 },
  { week: "Aug 31", passed: 55, flaky: 6, failed: 7 },
  { week: "Sep 7", passed: 61, flaky: 5, failed: 3 },
  { week: "Sep 14", passed: 64, flaky: 2, failed: 5 },
  { week: "Sep 21", passed: 59, flaky: 4, failed: 8 },
  { week: "Sep 28", passed: 68, flaky: 3, failed: 4 },
  { week: "Oct 5", passed: 71, flaky: 5, failed: 3 },
  { week: "Oct 12", passed: 66, flaky: 2, failed: 6 },
  { week: "Oct 19", passed: 74, flaky: 3, failed: 2 },
  { week: "Oct 26", passed: 79, flaky: 1, failed: 3 },
  { week: "Nov 2", passed: 83, flaky: 2, failed: 2 },
];

export default function CiRunsByResult() {
  return (
    <AreaChart
      aria-label="CI runs per week by result"
      data={weeks}
      index="week"
      indexLabel="Week of"
      isStacked
      series={[
        { key: "passed", label: "Passed", color: "success" },
        { key: "flaky", label: "Flaky", color: "warning" },
        { key: "failed", label: "Failed", color: "danger" },
      ]}
      className="h-64"
    />
  );
}

A smooth curve#

curve="smooth" bends through the points without overshooting them, so the wash never dips below zero where spredo-crypto-screener stops scanning, and stays flat between two equal values.

Arrow keys move between the values.

Screener scans every two hours
HourScans
06:000
08:00140
10:00410
12:00410
14:00380
16:00520
18:00260
20:000
22:000
smooth.tsx
import { AreaChart } from "cennge-ui/charts";

const hours = [
  { hour: "06:00", scans: 0 },
  { hour: "08:00", scans: 140 },
  { hour: "10:00", scans: 410 },
  { hour: "12:00", scans: 410 },
  { hour: "14:00", scans: 380 },
  { hour: "16:00", scans: 520 },
  { hour: "18:00", scans: 260 },
  { hour: "20:00", scans: 0 },
  { hour: "22:00", scans: 0 },
];

export default function ScreenerScans() {
  return (
    <AreaChart
      aria-label="Screener scans every two hours"
      data={hours}
      index="hour"
      indexLabel="Hour"
      curve="smooth"
      series={[{ key: "scans", label: "Scans" }]}
      className="h-56"
    />
  );
}

Without axes, in a card#

showXAxis, showYAxis and showGrid turn off for a chart that sits under a number. The readout, the keyboard and the table stay: hover the chart to read the requests in each two hours. On a Card the dots of the readout sit in a ring of its surface by themselves.

Requests to payload-cache yesterday

68,950

Arrow keys move between the values.

Requests to payload-cache every two hours
FromRequests
00:002,180
02:001,650
04:001,920
06:005,100
08:008,600
10:009,300
12:009,050
14:009,350
16:008,200
18:006,100
20:004,400
22:003,100
no-axes.tsx
import { Card, CardBody } from "cennge-ui";
import { AreaChart } from "cennge-ui/charts";

// Requests in each two hours, from midnight.
const perTwoHours = [
  2180, 1650, 1920, 5100, 8600, 9300,
  9050, 9350, 8200, 6100, 4400, 3100,
];
const requests = perTwoHours.map((count, i) => ({ hour: `${String(i * 2).padStart(2, "0")}:00`, count }));
const total = perTwoHours.reduce((sum, count) => sum + count, 0);

export default function RequestsCard() {
  return (
    <Card variant="flat" className="w-full max-w-sm">
      <CardBody>
        <p className="text-sm text-muted">Requests to payload-cache yesterday</p>
        <p className="text-2xl font-semibold tabular-nums">{total.toLocaleString("en-US")}</p>
        <AreaChart
          aria-label="Requests to payload-cache every two hours"
          data={requests}
          index="hour"
          indexLabel="From"
          curve="smooth"
          series={[{ key: "count", label: "Requests" }]}
          showXAxis={false}
          showYAxis={false}
          showGrid={false}
          className="mt-2 h-24"
        />
      </CardBody>
    </Card>
  );
}

Accessibility#

  • The figure and its plot are named by aria-label; the plot is a focusable group described by labels.keyboardHint.
  • Focus from the keyboard puts the crosshair on the first point, and a polite live region reads each place as the arrows move it, as "Apr: cennge-ui 910, playwright-msi-scraper 290, payload-cache 260, ai-niche-scout 210, spredo-crypto-screener 140".
  • Screen readers get every value in a table captioned with the chart's name. In a stack the table holds each part, never the sums.
  • The legend names each series beside a sample of its colour, so colour never says which is which alone.

Server rendering#

The legend, the axes, the grid and the data table come from the server. The areas 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.

PropTypeDefault
isStacked
booleanThe series piled up, each filling down to the one below: a whole made of parts.
โ€“
className
stringSize it here: the default height, h-72, includes the axes and the legend.
โ€“
aria-labelrequired
stringNames the chart for screen readers, and the table that gives them its data.
โ€“
labels
Partial<ChartLabels>
โ€“
datarequired
readonly T[]One item per category: a month, a repositoryโ€ฆ
โ€“
curve
"linear""smooth""smooth" bends through the points without overshooting them: a count never dips below zero.
"linear"
showDots
booleanA dot on every value, not only where the readout is.
false
indexrequired
Key<T>The field that names each category, on the axis, in the tooltip and in the table.
โ€“
seriesrequired
readonly ChartSeries<T>[]
โ€“
indexLabel
stringWhat the categories are, heading the table's first column, e.g. "Month".
index
indexFormat
(value: T[Key<T>], item: T) => string
โ€“
valueFormat
(value: number) => stringValues in the tooltip and the table.
numbers in the provider's locale
axisFormat
(value: number) => stringValues on the axis.
short numbers in the provider's locale (1.2K)
domain
readonly [number, number]The value axis' ends, instead of round ones around the data.
โ€“
showLegend
boolean
true with two or more series
showGrid
boolean
true
showXAxis
booleanThe axis along the bottom.
true
showYAxis
booleanThe axis up the side.
true
emptyContent
React.ReactNodeShown in place of the plot when there is no data.
labels.empty