Deck
Browse the catalog

Build / Molecules

TrendChart

  • molecule
  • play-fn

One measure over time, as columns.

Deliberately small: a shape-of-the-trend chart for a dashboard panel, not an explorable plot. It draws one series, because two measures on one set of axes is the mistake this component exists to make unavailable — a second measure gets a second chart.

Columns rather than a line, since the periods are discrete buckets: a week is a total, not a reading taken at a moment, and a line between two totals draws values that were never measured.

Every column carries its full reading as text for screen readers, and the tallest and the most recent are labelled on the cap — the two a reader actually looks for. Hovering or focusing a column shows the rest, so no value is only available by measuring a bar against nothing.

import { TrendChart } from '@meetcard/deck'

Example

<TrendChart label="Profile views over time" unit="views" points={[ { label: 'W1', value: 312 }, { label: 'W2', value: 428 }, ]} />

Stories

Every preview below is the real component, rendered in this page from the same story Storybook runs. The width control resizes each preview's container; CSS media queries still follow the browser window, so breakpoint behaviour is worth checking in Storybook.

Default
Source
const Default = () => <Card style={{ maxWidth: 420 }}>
    <TrendChart
        label="Profile views over time"
        unit="views"
        points={[
          { label: 'W1', value: 312, fullLabel: 'Week 1' },
          { label: 'W2', value: 428, fullLabel: 'Week 2' },
          { label: 'W3', value: 517, fullLabel: 'Week 3' },
          { label: 'W4', value: 427, fullLabel: 'Week 4' },
        ]} />
</Card>;

Twelve buckets still read, because the columns thin rather than crowd.

A Year
Source
const AYear = () => <Card style={{ maxWidth: 420 }}>
    <TrendChart
        label="Profile views by month"
        unit="views"
        points={[
          { label: 'J', value: 180, fullLabel: 'January' },
          { label: 'F', value: 212, fullLabel: 'February' },
          { label: 'M', value: 264, fullLabel: 'March' },
          { label: 'A', value: 301, fullLabel: 'April' },
          { label: 'M', value: 288, fullLabel: 'May' },
          { label: 'J', value: 342, fullLabel: 'June' },
          { label: 'J', value: 398, fullLabel: 'July' },
          { label: 'A', value: 371, fullLabel: 'August' },
          { label: 'S', value: 455, fullLabel: 'September' },
          { label: 'O', value: 512, fullLabel: 'October' },
          { label: 'N', value: 486, fullLabel: 'November' },
          { label: 'D', value: 402, fullLabel: 'December' },
        ]} />
</Card>;

When the run ends on its high point, the peak label and the latest label are the same column — one label, not two stacked on each other.

Peak Is Latest
Source
const PeakIsLatest = () => <Card style={{ maxWidth: 420 }}>
    <TrendChart
        label="Profile views over time"
        unit="views"
        points={[
          { label: 'W1', value: 180 },
          { label: 'W2', value: 246 },
          { label: 'W3', value: 388 },
          { label: 'W4', value: 512 },
        ]} />
</Card>;

A week with no traffic. Flat at the baseline, not an empty box.

Nothing Measured Yet
Source
const NothingMeasuredYet = () => <Card style={{ maxWidth: 420 }}>
    <TrendChart
        label="Profile views over time"
        unit="views"
        points={[
          { label: 'W1', value: 0 },
          { label: 'W2', value: 0 },
          { label: 'W3', value: 0 },
          { label: 'W4', value: 0 },
        ]} />
</Card>;

Props

PropTypeDefaultDescription
labelrequiredstring—Names the chart for assistive tech, e.g. "Profile views over time".
pointsrequiredTrendPoint[]—Points in time order, oldest first.
formatValue(value: number) => string(value: number) => value.toLocaleString()—
unitstring''What is being counted, for the spoken reading — "views", "scans".

Also accepts everything from Omit, so id, data-* and event handlers pass straight through.

Open in Storybookfor controls, the a11y panel and the interaction runner.