Summary Line Chart
A React multi-line chart with a live summary column: lines draw in with a stroke animation, and as the pointer moves the summary's values and change rates roll digit by digit to that point in time. Click a summary row to hide a line and the Y axis re-tweens.
Plot several metrics that share a time axis, with a summary column on the right listing each line's value at the active point and its change versus the previous point. On entrance the lines draw in one after another; once drawn, the summary numbers roll digit by digit from 0 to the latest values. As the pointer moves over the plot, a crosshair and a dot on each line follow on a spring, every summary number rolls to that point, and leaving the plot returns to the default point. Summary rows double as the legend — click one to hide or show its line, and the Y ticks recompute from the remaining lines and tween into place. Hand-drawn SVG, no charting library.
Bring this into your project
Run this command in your project folder. The CLI adds the source and dependencies; then follow the usage example below.
npx webberui@latest add summary-line-chart.enPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<SummaryLineChart/>
The complete example includes the preview layout and data. Install the component first, then paste this file into your project.
Installation
npx webberui@latest add https://webberui.com/r/summary-line-chart.en.jsonOr, once registries are configured in components.json, install it as @webberui/summary-line-chart.
Usage
import { SummaryLineChart } from "@/components/ui/summary-line-chart";
<SummaryLineChart
labels={["Wk 1", "Wk 2", "Wk 3", "Wk 4"]}
series={[
{ key: "signups", label: "Sign-ups", values: [820, 910, 880, 1040] },
{ key: "paid", label: "Paid", values: [62, 70, 74, 81] },
]}
curve="smooth"
showArea
formatValue={(v) => `${v.toLocaleString("en-US")} users`}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
labels | string[] | — | X-axis labels, one per point in time |
series | SummaryLineSeries[] | — | Lines to draw; 1–4 recommended |
curve | "linear" | "smooth" | "smooth" | Line shape: straight segments, or a monotone cubic that never overshoots the data range |
showArea | boolean | false | Add a faint area fill under each line |
activeIndex | number | — | Controlled active point index; managed internally when omitted |
defaultActiveIndex | number | last point | Uncontrolled initial point, and where it returns when the pointer leaves the plot |
onActiveIndexChange | (index: number) => void | — | Called when the active point changes |
formatValue | (value: number, series: SummaryLineSeries) => string | Intl.NumberFormat("en-US") | Custom value formatting for the summary and table (currency, units) |
height | number | 260 | Plot height (px) |
ariaLabel | string | generated | Accessible name for the chart |
className | string | — | Passed through to the outer container |
SummaryLineSeries
| Field | Type | Default | Description |
|---|---|---|---|
key | string | — | Unique key |
label | string | — | Display name |
values | number[] | — | Values per point in time; length should match labels, missing values repeat the last one |
color | string | --wb-summary-line-chart-1…4 | Line color (any CSS color) |
CSS variables
| Variable | Default | Description |
|---|---|---|
--wb-summary-line-chart-duration | 1200ms | Draw duration per line (lines are staggered by a fixed 150ms) |
--wb-summary-line-chart-accent | #0d9488 | Accent: the first line and positive change badges |
--wb-summary-line-chart-accent-dark | #2dd4bf | Accent in dark mode |
--wb-summary-line-chart-1 … -4 | accent, #737373, #f59e0b, #e11d48 | Default line colors; only the first uses the accent |
How it works
- The smooth curve is a Fritsch–Carlson monotone cubic: flat stretches never bulge or dip, so the line always stays inside the data range.
- Rolling digits: each digit is a 0–9 column that springs (stiffness 300, damping 30) to its target. Columns are aligned by position from the right, so ones and tens stay put when the digit count changes. Commas, signs and custom units render as-is.
- The change rate is computed against the previous point; the first point, or a previous value of 0, shows "—". Positive values use the accent with a rising icon, negative values use rose with a falling icon.
- Hiding a line fades it out over 200ms; the Y axis recomputes "nice" ticks (multiples of 1, 2, 2.5, 5, 10) from the visible lines only, and the grid and every path tween to their new positions.
- Width is measured with
ResizeObserver; SSR and the pre-measure render use a 560px fallback. With enough room the summary sits on the right, otherwise it stacks below (container query). - The entrance fires once when the chart scrolls into view (
useInView).
Accessibility
- The SVG has
role="img"with a generated summaryaria-label, plus an sr-only<table>(header row of points in time, one row of values per line) - The plot is focusable with Tab; ←/→ switch points in time and Home/End jump to either end, with a focus-visible ring
- An
aria-live="polite"region in the summary column announces the point's values and change rates only on keyboard moves; pointer movement is not announced so screen readers are not flooded - Summary rows are buttons with
aria-pressed, and theiraria-labelincludes the name, current value, change rate and hide/show hint; the rolling digits are hidden from assistive tech - When the OS "reduce motion" setting is on, lines and areas render in their final state, numbers switch without rolling, and the crosshair and dots stop springing; the tooltip and keyboard control still work
Stacked Column Chart
A stacked column chart React component. Clicking a legend item collapses that layer while the remaining layers sink into place on a spring and the Y axis rescales with tweened ticks; hover or use the arrow keys for a per-column breakdown.
Paired Column Chart
A paired column chart comparing two series side by side: switching periods reorders and resizes the bars with a layout animation, and hovering shows the gap between the pair.