Brush chart

A dense time series with an overview strip: drag a window to zoom, resize it by its handles, and read events in place.

datachartnew
Preview
Brush chart preview

Daily active users, Jul 1, 2026 to Aug 29, 2026. Showing Jul 21, 2026 to Aug 15, 2026.

Daily active users, Jul 21, 2026 to Aug 15, 2026
DateDaily active usersEvent
Jul 21, 20261,217 usersv2 launch. The redesign shipped on this day.
Jul 22, 20261,184 users
Jul 23, 20261,157 users
Jul 24, 20261,137 users
Jul 25, 20261,124 users
Jul 26, 20261,119 users
Jul 27, 20261,122 users
Jul 28, 20261,134 users
Jul 29, 20261,154 users
Jul 30, 20261,183 users
Jul 31, 20261,218 users
Aug 1, 20261,260 users
Aug 2, 20261,307 users
Aug 3, 20261,358 users
Aug 4, 20261,412 users
Aug 5, 20261,466 users
Aug 6, 20261,519 users
Aug 7, 20261,571 users
Aug 8, 20261,618 users
Aug 9, 20261,661 users
Aug 10, 20261,697 users
Aug 11, 20261,727 users
Aug 12, 20261,748 users
Aug 13, 20261,761 users
Aug 14, 20261,765 users
Aug 15, 20261,761 users

When to use

  • Product analytics with a year or more of daily data.
  • Incident reviews where events need to be read against a metric.
  • Any chart where people ask to zoom into a stretch without losing context.

When not to use

  • Use line-chart for a few weeks of data or several series.
  • Use sparkline for a small inline trend without interaction.
  • Avoid it for data that dips below zero or needs a log scale.

shadcn CLI

Also installs (1)

  • manicat-foundation
Manual install(+)
registry.jsonjson
{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "brush-chart",
  "type": "registry:ui",
  "title": "Brush chart",
  "description": "A dense time series with an overview strip: drag a window to zoom, resize it by its handles, and read events in place.",
  "categories": [
    "data"
  ],
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "https://manicat.viraza.net/r/manicat-foundation.json"
  ],
  "files": [
    {
      "path": "registry/components/brush-chart/brush-chart.tsx",
      "type": "registry:component",
      "target": "@components/manicat/brush-chart/brush-chart.tsx"
    },
    {
      "path": "registry/components/brush-chart/brush-chart.module.css",
      "type": "registry:component",
      "target": "@components/manicat/brush-chart/brush-chart.module.css"
    }
  ],
  "docs": "Docs and live preview: https://uiarc.dev/components/brush-chart",
  "meta": {
    "tier": "free",
    "kind": "component",
    "docs": "https://manicat.viraza.net/components/brush-chart",
    "markdown": "https://uiarc.dev/components/brush-chart/markdown",
    "tags": [
      "data",
      "chart",
      "new"
    ]
  }
}

Usage

Install the component, then copy this into your app. Change the content and the props to fit your screen.

example.tsxtsx
"use client";

import { BrushChart } from "@/components/manicat/brush-chart/brush-chart";
import type { BrushChartDatum } from "@/components/manicat/brush-chart/brush-chart";

const DAY = 86_400_000;

function range(from: Date, days: number): Date[] {
  return Array.from({ length: days }, (_, index) => new Date(from.getTime() + index * DAY));
}

const brushData: BrushChartDatum[] = range(new Date(Date.UTC(2026, 6, 1, 12)), 60).map((date, index) => ({
  date: date.getTime(),
  value: Math.max(400, 1200 + Math.round(260 * Math.sin(index / 5.5) + index * 7)),
}));

export function BrushChartExample() {
  return (
    <BrushChart
      data={brushData}
      label="Daily active users"
      unit="users"
      annotations={[{ date: brushData[20].date, label: "v2 launch", description: "The redesign shipped on this day." }]}
      defaultRange={[brushData[20].date as number, brushData[45].date as number]}
    />
  );
}

Examples

Real screens rather than isolated props. Switch to Code to copy a whole example into your project.

Zoomable timeline Drag the brush to focus on a range; an annotation marks the launch day.

Zoomable timeline Drag the brush to focus on a range; an annotation marks the launch day. preview

Daily active users, Jul 1, 2026 to Aug 29, 2026. Showing Jul 21, 2026 to Aug 15, 2026.

Daily active users, Jul 21, 2026 to Aug 15, 2026
DateDaily active usersEvent
Jul 21, 20261,217 usersv2 launch. The redesign shipped on this day.
Jul 22, 20261,184 users
Jul 23, 20261,157 users
Jul 24, 20261,137 users
Jul 25, 20261,124 users
Jul 26, 20261,119 users
Jul 27, 20261,122 users
Jul 28, 20261,134 users
Jul 29, 20261,154 users
Jul 30, 20261,183 users
Jul 31, 20261,218 users
Aug 1, 20261,260 users
Aug 2, 20261,307 users
Aug 3, 20261,358 users
Aug 4, 20261,412 users
Aug 5, 20261,466 users
Aug 6, 20261,519 users
Aug 7, 20261,571 users
Aug 8, 20261,618 users
Aug 9, 20261,661 users
Aug 10, 20261,697 users
Aug 11, 20261,727 users
Aug 12, 20261,748 users
Aug 13, 20261,761 users
Aug 14, 20261,765 users
Aug 15, 20261,761 users

Keyboard interactions

KeysAction
ArrowLeft / ArrowRight (plot)Moves the crosshair between points in view.
ArrowLeft / ArrowRight (window)Pans by a tenth of the window; Shift or PageUp and PageDown pan by half.
+ / - (window)Zooms in or out around the window's centre.
ArrowLeft / ArrowRight (handles)Moves that edge by one point; Shift moves by ten.
Home / EndMoves the window or edge to the start or end.
Escape / 0Resets the window to the whole series.

Accessibility

  • The window and both handles are sliders with dates as aria-valuetext.
  • The plot is a focusable group; each reading is announced with its date, value, and any event on that day.
  • A visually hidden table lists every point in the window with its event.
  • Event markers pair the second series hue with a text label or tooltip; they never rely on color alone.

Motion

  • The line draws in from the left once, the first time it is seen.
  • Dragging tracks the pointer 1:1; presets, resets, and controlled changes glide the window on a spring.
  • The value axis springs to the tallest point in view, and the raw line crossfades with the smoothed trend as density changes.
  • Reduced motion places the window directly and keeps the crosshair without travel.

Responsive behavior

  • Both charts fill their container; date ticks pick days, weeks, months, or years to fit the width.
  • Handles have a 28 by 44px hit area for touch, and the strip keeps vertical page scroll free.
  • Event labels are placed left to right and skip themselves when they would collide.

Performance

  • Past two points per pixel, each pixel column keeps only its low and high, so spikes survive and paths stay small.
  • The overview path is built once per width; the rolling average is computed once per data change.
  • Window moves re-render one component; a few thousand points stay smooth.

Notes for AI

  • Choose it for long daily or hourly series where people need both the whole history and a close look.
  • Control range to sync presets (30D, 90D, 1Y) or other charts with the window.
  • Use annotations for launches, incidents, and pricing changes; keep labels to a word or two.
  • The value axis starts at zero, so it suits counts and totals rather than prices that hover far from zero.