Skip to content
microcharts
ReferenceDecisionShiftHistogram

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.

ShiftHistogram
interactive · 5.71 kB · static · 3.17 kB

Install

Import & usereact/shift-histogram
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);
Needs package + stylesheetSet up with AI

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

overlay + no shift
locale

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

one side empty
both empty

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

PropTypeNotes
data*{ before: number[]; after: number[] }The two samples — raw observations, shared bin edges are derived.
binsnumberShared 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.
animatebooleaninteractiveOpt-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