# ShiftHistogram (/docs/charts/shift-histogram)

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.

```tsx
<ShiftHistogram
  data={{
    before: Array.from({ length: 100 }, (_, i) => 120 + (i % 40) - 20),
    after: Array.from({ length: 100 }, (_, i) => 96 + (i % 40) - 20),
  }}
  format={{ style: "unit", unit: "millisecond", unitDisplay: "short", maximumFractionDigits: 0 }}
  title="The fix"
  width={260}
  height={30}
/>
```

## Install

```tsx
import { ShiftHistogram } from "@microcharts/react/shift-histogram";

<ShiftHistogram data={{ before, after }} title="The fix" />
```

Setup (package + stylesheet): [Quickstart](/docs/quickstart#set-up-with-an-ai-agent) or paste [`/agent-setup.md`](/agent-setup.md) into your agent.


## 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).


## Variants

```tsx
const before = Array.from({ length: 100 }, (_, i) => 120 + (i % 40) - 20);
const after = Array.from({ length: 100 }, (_, i) => 96 + (i % 40) - 20);

<ShiftHistogram data={{ before, after }} mode="overlay" />
<ShiftHistogram data={{ before, after: before }} />
```

```tsx
<ShiftHistogram
  data={{ before: [12000, 12400, 12800], after: [9600, 10000, 10400] }}
  locale="de-DE"
/>
```

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

```tsx
// the after sample hasn't landed yet — before renders alone
const before = Array.from({ length: 100 }, (_, i) => 120 + (i % 40) - 20);

<ShiftHistogram
  data={{ before, after: [] }}
  format={(n) => Math.round(n) + " ms"}
  title="Awaiting after sample"
/>
```

```tsx
<ShiftHistogram data={{ before: [], after: [] }} title="No data yet" />
```

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."**


## 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](/docs/accessibility#one-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 | Description |
| --- | --- | --- |
| `data` (required) | `{ 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` | (interactive) Opt-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](/docs/quickstart#the-shared-grammar).
