Data Storytelling

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.en

New project or installation trouble? Start here →

Playground

Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.

260
1200
Active edited usageDefault props
<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.json

Or, 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

PropTypeDefaultDescription
labelsstring[]—X-axis labels, one per point in time
seriesSummaryLineSeries[]—Lines to draw; 1–4 recommended
curve"linear" | "smooth""smooth"Line shape: straight segments, or a monotone cubic that never overshoots the data range
showAreabooleanfalseAdd a faint area fill under each line
activeIndexnumber—Controlled active point index; managed internally when omitted
defaultActiveIndexnumberlast pointUncontrolled 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) => stringIntl.NumberFormat("en-US")Custom value formatting for the summary and table (currency, units)
heightnumber260Plot height (px)
ariaLabelstringgeneratedAccessible name for the chart
classNamestring—Passed through to the outer container

SummaryLineSeries

FieldTypeDefaultDescription
keystring—Unique key
labelstring—Display name
valuesnumber[]—Values per point in time; length should match labels, missing values repeat the last one
colorstring--wb-summary-line-chart-1…4Line color (any CSS color)

CSS variables

VariableDefaultDescription
--wb-summary-line-chart-duration1200msDraw duration per line (lines are staggered by a fixed 150ms)
--wb-summary-line-chart-accent#0d9488Accent: the first line and positive change badges
--wb-summary-line-chart-accent-dark#2dd4bfAccent in dark mode
--wb-summary-line-chart-1 … -4accent, #737373, #f59e0b, #e11d48Default 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 summary aria-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 their aria-label includes 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