Skip to content
microcharts
ReferenceDecisionShiftHistogram

ShiftHistogram

ShiftHistogram mirrors before and after distributions on shared bins and states the median shift between them.

encodes
mirrored bin heights around a center + median shift
precision
medium — shape read; the shift label is the takeaway
nodes
2 per bin + 4

ShiftHistogram mirrors two histograms around a center line: before upward (muted), after downward (accent), on shared bin edges. Bar heights are per-side proportions (each side's counts over its own n) on one shared scale, so a bigger sample on one side cannot fake a shift. The mirror carries identity, not valence — up is "before", not "good". The median shift is the precise takeaway, and the medians are never smoothed or trimmed.

ShiftHistogram
interactive · 5.91 kB · static · 3.28 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

Use it for a "the fix, proven" read in a KPI card, before/after distributions in an experiments table, and showing that a change is more than a mean move. For a single distribution use HistogramStrip; for two labelled arms use ABStrips.

Sizing

Variants

overlay + no shift
locale

The median shift label and accessible summary follow format/locale. Under de-DE the before median above reads "12.400" rather than the English "12,400". mode="overlay" draws the after bins above the axis as an outline over the before fill instead of mirroring them below it; the bins stay shared either way.

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: "Median 116 ms; no after sample." It never reports a fabricated shift. With both sides empty nothing is plottable and the accessible name says "No data."

Four homes

Accessibility

Side identity rides on position (up is before, down is after), so it survives grayscale and forced-colors. The accessible name states the median shift: "Median fell from 116 ms to 92 ms." It appends "On N / M samples" when the sample 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