Skip to content
WANCRAFT

G.147Data & Feedback

Brush Chart

A time-series chart with an overview brush: drag or resize the window and the main chart glides to the new range.

NewReactSVGTailwindadvanced

Overview

Brush Chart pairs a detailed time-series view with an overview strip of the full history. Drag the brush window to pan, pull its handles to resize, or click anywhere in the overview to jump; the main chart chases the new domain with a smooth exponential glide, rescaling its value axis to the visible points as it goes. You can also wheel-zoom around the cursor and drag to pan on the main chart. Paths, ticks, brush and crosshair are written straight to the DOM from refs, so gestures never re-render React; the selected range only commits to state (and onRangeChange) when it settles. The brush handles are real sliders, the main chart has a keyboard crosshair with a polite read-out, and a screen-reader summary describes the selected range. Width follows the container through a ResizeObserver.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/brush-chart.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/brush-chart.json

Method 02 · Copy and paste

  1. 1. No extra dependencies — React and Tailwind CSS only.
  2. 2. Copy the source from the Code tab into components/ui/brush-chart.tsx.
  3. 3. Import it and adjust props.
Dependencies
None
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { BrushChart, type BrushChartDatum } from "@/components/ui/brush-chart";

export function BrushChartExample() {
  const data: BrushChartDatum[] = Array.from({ length: 180 }, (_, i) => ({
    t: Date.UTC(2026, 0, 1) + i * 86_400_000,
    v: 1200 + Math.round(Math.sin(i / 9) * 180 + i * 2.5),
  }));

  return (
    <BrushChart
      label="Daily active users"
      data={data}
      onRangeChange={([start, end]) => console.log(start, end)}
    />
  );
}

03Props

PropTypeDefaultDescription
data{ t: number | string; v: number }[]—Points (required). t is epoch ms or an ISO string; unsorted input is sorted.
labelstring—Accessible name (required); also opens the screen-reader summary.
type"area" | "line""area"Filled area (gradient) or a plain line.
curve"monotone" | "linear""monotone"Monotone cubic (never overshoots) or straight segments.
heightnumber260Main chart height in px; the width follows the container.
overviewHeightnumber56Overview strip height in px.
defaultSpannumber0.35Initial window as a fraction of the full range, anchored to the latest data.
defaultRange[number, number]—Initial window as [start, end] in epoch ms (overrides defaultSpan).
durationnumber450Glide time in ms for domain changes (0 = instant; instant with reduced motion).
showGridbooleantrueHorizontal grid lines.
includeZerobooleanfalseKeep 0 inside the value axis instead of fitting the visible points.
accentstring"#FF7448"Line, area, crosshair and brush colour.
formatValue(value: number) => string—Value formatter for the axis, tooltip and summary.
formatTime(time: number) => string—Time formatter for the tooltip, sliders and summary (UTC dates by default).
onRangeChange(range: [number, number]) => void—Called with [start, end] in epoch ms when the window settles.

04Features

  • 01Overview brush: drag the window, resize with handles, click to jump
  • 02Wheel zoom around the cursor and drag-to-pan on the main chart
  • 03Smooth exponential glide of both axes towards the new domain
  • 04Value axis refits to the visible points (or keeps zero)
  • 05Crosshair tooltip on hover, tap or keyboard
  • 06Imperative drawing from refs — no React render per frame or pointer move
  • 07UTC-aligned time ticks from hours to years; ResizeObserver-driven width
motion · tweeninput · draginput · pointerinput · hoverinput · keyboardinput · focus

05Accessibility

  • Both brush handles are role="slider" with min, max, current value and a formatted date as aria-valuetext; arrows move one data step, Shift / Page keys a tenth of the range, Home / End to the limits.
  • The main chart is focusable: left / right move a crosshair point by point (panning the window when needed), Home / End jump to the window edges, + / − zoom, Escape hides it; each point is read out in a polite live region.
  • A screen-reader summary describes the selected range: dates, point count, first and last values, average, lowest and highest.
  • Visible focus rings on the main chart and on each brush handle.
  • With prefers-reduced-motion the domain changes instantly.

06Limitations

  • Wheel events over the main chart are captured to zoom (Shift + wheel or horizontal scroll pans).
  • One series per chart; times are formatted in UTC unless you pass formatTime.