Skip to content
WANCRAFT

G.179Data & Feedback

Scatter Plot

An SVG scatter plot with size and colour encodings, nearest-point tooltips, lasso or box selection, legend toggles and keyboard travel.

NewReactSVGTailwindadvanced

Overview

Scatter Plot maps x and y to position, an optional size to point area (radius ∝ √size) and an optional category to colour, on axes with nice 1-2-5 ticks. Hover, tap or focus brings up the nearest point's tooltip — found with a linear search in pixel space and only re-rendered when the nearest point changes. Drag a lasso or a rectangle to select a subset (Shift adds to it): the rest dims, a count appears, and a Clear selection button resets it. Legend buttons hide and show categories, the wheel can zoom around the cursor, and points grow in with a short stagger. The plot is a single focusable group: arrow keys jump to the nearest point in that direction, Space toggles it in the selection, and every change is announced; a visually hidden table carries all values. 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/scatter-plot.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/scatter-plot.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/scatter-plot.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 { ScatterPlot, type ScatterDatum } from "@/components/ui/scatter-plot";

export function ScatterPlotExample() {
  const data: ScatterDatum[] = [
    { label: "Northwind", category: "Studio", x: 34, y: 920, size: 8 },
    { label: "Fable & Co", category: "Studio", x: 41, y: 1010, size: 14 },
    { label: "Larch", category: "Agency", x: 72, y: 1240, size: 46 },
    { label: "Meridian", category: "Agency", x: 81, y: 1130, size: 32 },
    { label: "R. Okafor", category: "Freelance", x: 19, y: 540, size: 1 },
    { label: "J. Lind", category: "Freelance", x: 27, y: 610, size: 2 },
  ];

  return (
    <ScatterPlot
      label="Projects per year against day rate"
      xLabel="Projects / yr"
      yLabel="Day rate"
      sizeLabel="Team"
      data={data}
      onSelectionChange={(points) => console.log(points.length)}
    />
  );
}

03Props

PropTypeDefaultDescription
data{ x; y; size?; category?; label?; id? }[]—Points (required). size maps to area, category to colour and the legend.
labelstring—Accessible name (required); also the data table caption.
xLabel / yLabel / sizeLabelstring"x" / "y" / "Size"Axis and measure names used on the axes, tooltip, read-outs and table.
heightnumber380Plot height in px; the width follows the container (ResizeObserver).
pointSizenumber12Largest point radius in px; points without a size use about half.
opacitynumber0.75Point fill opacity (0.1–1).
brushMode"lasso" | "rect" | "none""lasso"Drag gesture for selecting points (mouse and pen; Shift adds to the selection).
zoomablebooleanfalseWheel zooms around the cursor; +/− zoom around the active point; 0 resets.
showGridbooleantrueGrid lines at the tick positions.
showLegendbooleantrueCategory legend with show/hide toggles (drawn when there is more than one category).
accentstring"#FF7448"Colour of the first category; others use a categorical palette.
formatX / formatY / formatSize(value: number) => string—Formatters for ticks, tooltip, announcements and table.
onSelectionChange(selected: ScatterDatum[]) => void—Called with the selected points whenever the selection changes.

04Features

  • 01Position, area (√-scaled size) and colour (category) encodings with nice 1-2-5 axis ticks
  • 02Nearest-point tooltip on hover, tap and keyboard focus (linear search; re-render only on change)
  • 03Lasso or rectangle brushing drawn imperatively, Shift to extend, with a Clear selection button
  • 04Legend buttons toggle categories; optional wheel and keyboard zoom with a Reset zoom button
  • 05Staggered point entrance via CSS keyframes; responsive width via ResizeObserver
motion · staggermotion · tweeninput · hoverinput · pointerinput · draginput · clickinput · keyboardinput · focus

05Accessibility

  • The plot is one focusable group (aria-roledescription "scatter plot") with instructions in aria-describedby.
  • Arrow keys move to the nearest visible point in that direction, Home / End jump to the first / last along x, Space or Enter toggles the point in the selection, Escape clears the selection; with zoom on, + / − zoom and 0 resets.
  • The active point, selection changes and legend toggles are announced in a polite live region.
  • Legend items are buttons with aria-pressed; Clear selection and Reset zoom are real buttons.
  • A visually hidden table lists every point with its category and values.
  • With prefers-reduced-motion the entrance stagger is skipped.

06Limitations

  • Brushing needs a mouse or pen; on touch a drag scrolls the page and a tap shows the nearest point.
  • Nearest-point search is linear, which is comfortable up to a few thousand points.