Dumbbell
Dumbbell links a before dot and an after dot per row on one shared scale.
- encodes
- two dot positions + connecting span
- precision
- high
- nodes
- ≤ 3 per row (rows ≤ 5)
- collection
- Core
Dumbbell draws each row as a hollow dot at from, a filled dot at to, and a connector between them: a band that moved
62,000 → 84,000 reads without a legend. Direction is shape-coded, so it never relies on color alone, and the pair
renders the same whichever way the values run. With positive the connector takes the valence token by direction. Drop
positive for ranges such as min→max or a confidence span, which have no valence to color.
Install
import { Dumbbell } from "@microcharts/react/dumbbell";<Dumbbell data={[{ from: 62000, to: 84000 }]} title="Band move" />Try it
- hover · rove · click…
Rove rows with ↑/↓; ←/→ inspect the from/to ends of the active row.
import { Dumbbell } from "@microcharts/react/dumbbell/interactive";// bands — real values under “Sample data” below<Dumbbell data={bands}/>Sample data
const bands = [ { label: "Paris", from: 52, to: 61 }, { label: "Berlin", from: 48, to: 68 }, { label: "Oslo", from: 66, to: 60 }, { label: "Rome", from: 44, to: 50 },];When to use it
Use it for salary bands, before/after per table row, and ranges. When you have many categories and the crossings matter, use Slope; when the path between the two ends matters, use Sparkline.
Sizing
Variants
Edge cases
When a row's from equals its to, the connector is dropped and one filled dot is drawn, rather than a hollow and a
filled dot stacked on the same coordinate. The summary says "No change at 55." for a single row.
Four homes
Berlin's band moved from €48k to €68k after the review — up 42%.
| Paris | +9k | |
| Berlin | +20k | |
| Oslo | -6k | |
| Rome | +6k |
Preview uses the public chart API only. When an interactive twin exists, it swaps in with the same props and no entrance motion - code samples stay on the static import. Placement recipes: Composition, Sizing.
Accessibility
A single row reads "From 40 to 60, up 50%.", a degenerate pair reads "No change at 55.", and a multi-row chart leads with the largest change ("2 rows. Largest change Paris, up 17%."). The interactive entry roves rows with ↑/↓ (←/→ do the same) and announces each row's own pair: "From 52 to 61, up 17%."
The interactive entry follows the shared interaction contract:
arrow keys rove between units on both axes, Home and End jump to the ends, and a click, tap, Enter or
Space selects a unit — pinning its readout so it survives blur, until you select it again, press Escape, or
press outside the chart. On touch, a tap pins and a drag scrubs.
Props
| Prop | Type | Notes |
|---|---|---|
| data* | { label?; from; to }[] | Start/end pairs. |
| positive | "up" | "down" | Direction valence for CHANGES; drop it for ranges (no valence). |
| label | "value" | "none" | From/to values outside the dots (drop when the span is tight). |
| highlight | number | string | Accent one row. |
| animate | boolean | interactiveOpt-in entrance motion when the chart mounts client-side — add import "@microcharts/react/motion" once. Inert on the server, on hydrated server HTML, and under prefers-reduced-motion. |
Plus the shared grammar — data, domain, color, title, summary, format — and the layout props (width, height, className, style) that every chart accepts. Interactive entries also share animate and live, and — wherever a chart has more than one navigable unit — onActive, onSelect, selectedIndex and defaultSelectedIndex; and — wherever the chart shows a hover value — readout. See the shared grammar.