ShiftHistogram
React shift histogram chart — before/after distributions on mirrored shared bins. Tiny accessible SVG microchart; RSC-safe static or /interactive.
ShiftHistogram answers "did the fix actually change the distribution?". It mirrors two histograms around a center line — before upward (muted), after downward (accent) — on shared bin edges, with the median shift as the precise takeaway. Bar heights are per-side proportions on one shared scale, so a bigger sample on one side can't fake a shift. The mirror carries identity, not valence (up isn't "good") — that's the whole point of the symmetry.
Install
import { ShiftHistogram } from "@microcharts/react/shift-histogram";// before, after — real values under “Sample data” below<ShiftHistogram data={{ before, after }} title="The fix" />Sample data
// latency (ms) before/after a fix — the whole distribution moved leftconst before = Array.from({ length: 100 }, (_, i) => 120 + (i % 40) - 20);const after = Array.from({ length: 100 }, (_, i) => 96 + (i % 40) - 20);Try it
When to use it
- Good for — a "the fix, proven" read in a KPI card, before/after distributions in an experiments table, showing a change is real (not just a mean move).
- Avoid for — a single distribution (HistogramStrip) or two labelled arms (ABStrips).
Sizing
Variants
The median shift label and accessible summary follow format/locale — the before median above reads "12.400" under
de-DE rather than the English "12,400".
Edge cases
With one side empty the chart still renders the populated side's bins alone, and the summary names the gap directly — "Median 116 ms; no after sample." — never a fabricated shift. With both sides empty nothing is plottable and the accessible name says "No data."
Four homes
Why this default
Mirror, because before/after symmetry makes the shift a visible displacement. The only normalization allowed is per-side proportions — each side's counts over its own n — on one shared height scale, so unequal sample sizes never fake a shift, and the rule is stated. The bins are shared across both sides always, and the medians are never smoothed or trimmed. Side identity rides on position (up vs before, down vs after) so it survives grayscale and forced-colors; the summary names the direction and, when the sample sizes differ, carries both.
Accessibility
The accessible name states the median shift plainly — "Median fell from 116 ms to 92 ms." — appending "On N / M samples" when the sizes differ. The interactive entry steps the bins and announces each bin's before/after proportions; M jumps to the two median bins.
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 or press Escape.
On touch, a tap pins and a drag scrubs.
Props
| Prop | Type | Notes |
|---|---|---|
| data* | { before: number[]; after: number[] } | The two samples — raw observations, shared bin edges are derived. |
| bins | number | Shared bin count (default auto, Sturges capped at 12). |
| mode | "mirror" | "overlay" | Mirror (default) or after-as-outline over before fill. |
| seriesLabels | [string, string] | Side identities for the summary (default ['before', 'after']). |
| label | "shift" | "none" | Signed median shift in a right gutter. |
| 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.
Related charts
ABStrips
React A/B strips chart — two graded quantile strips on one scale so A/B overlap is visible at a glance. Tiny accessible SVG microchart; RSC-safe static or /interactive.
ParetoStrip
React pareto strip chart — sorted causes with a cumulative reference line. Tiny accessible SVG microchart; RSC-safe static or /interactive.