# PercentileTrace (/docs/charts/percentile-trace)

PercentileTrace traces one entity's **percentile rank** over time on the **full 0–100 scale**. Because the axis _is_
rank, the population is constant by definition: the middle-half (**p25–75**) and near-full (**p5–95**) bands are fixed
fields, exact by construction rather than estimated from the traced series, and the only line on the chart is the entity
itself. The default axis is never truncated, which would hide how much headroom or floor is left; `domain` zooms it when
you are reading a run that lives at the top of the ladder.

```tsx
import { PercentileTrace } from "@microcharts/react/percentile-trace";

<PercentileTrace
  data={[40, 46, 52, 58, 63, 68, 72, 76, 79, 81]}
  unit="week"
  title="Standing"
/>
```

## Install

```tsx
import { PercentileTrace } from "@microcharts/react/percentile-trace";

<PercentileTrace data={ranks} title="Standing" />
```

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

## Try it

```tsx
import { PercentileTrace } from "@microcharts/react/percentile-trace/interactive";

<PercentileTrace
  data={ranks}
/>
```

## When to use it

Use it for one player's or product's rank drifting over time, for whether a standing has crossed into the top or bottom
of the pack, and for a percentile KPI where the population context matters. For a raw value over time use Sparkline; for
one absolute number against a target use Bullet or Delta.

## Sizing

**without population bands**

```tsx
<PercentileTrace data={ranks} showBands={false} />
```

**a slipping standing (down is good)**

```tsx
<PercentileTrace data={ranks} positive="down" />
```

## Variants

```tsx
<PercentileTrace data={[40, 46, 52, 58, 63, 68, 72, 76, 79, 81]} />
<PercentileTrace data={[40, 46, 52, 58, 63, 68, 72, 76, 79, 81]} showBands={false} />
```

The endpoint dot carries **valence**. By default a rising standing is good, so it turns positive; set `positive="down"`
when slipping _down_ the pack is the win, as with a support ticket's backlog rank. The line already encodes direction,
so color is a redundant cue and never the only signal.

```tsx
<PercentileTrace
  data={[78, 72, 64, 55, 47, 40, 34, 29, 26, 24]}
  positive="down"
/>
```

## Edge cases

```tsx
<PercentileTrace data={[63]} unit="week" />
```

```tsx
// ranks are 0–100 — values outside are clamped (and dev-warn once)
<PercentileTrace data={[102, 88, 96]} unit="week" />
```

```tsx
<PercentileTrace
  data={[40, 55, 68, 81.5]}
  format={{ maximumFractionDigits: 1 }}
  locale="de-DE"
  label="last"
/>
```

A single reading is a lone endpoint with no line to draw. It still reports its standing and holds the accessible-name
contract. Ranks outside 0–100 are clamped to the axis (a value of `102` reads as `p100`) and warn once in development.
With a `locale`, the percentile label and every announced number follow that locale's own formatting: `de-DE` renders
`p81,5` with a comma.

## Four homes

**In a sentence**

```tsx
<p>
  Standing over the season{" "}
  <span className="mc-inline">
    <PercentileTrace data={ranks} summary={false} />
  </span>{" "}
  — climbed from 40th to 81st percentile.
</p>
```

**In a table cell**

```tsx
<td>
  <PercentileTrace data={ranks} />
</td>
```

**In a KPI card**

```tsx
<div className="kpi">
  <span className="figure">81st</span>
  <span className="unit">percentile now</span>
  <PercentileTrace data={ranks} />
</div>
```

**In a tab header**

```tsx
<button className="tab">
  Team A <PercentileTrace data={ranks} />
</button>
```

## Accessibility

The accessible name states the current percentile, the change from the first reading, and how the standing moved
relative to the middle half — **"p81 now, up 41 points from the first reading; moved above the middle half."** The
interactive entry steps the readings and announces each one's percentile.

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, press `Escape`, or
press outside the chart. On touch, a tap pins and a drag scrubs.

## Props

| Prop | Type | Description |
| --- | --- | --- |
| `data` (required) | `number[]` | Percentile ranks 0–100, one per reading; out-of-range values are clamped. |
| `domain` | `[number, number]` | Rank extent (default [0, 100] — the full ladder). |
| `showBands` | `boolean` | Draw the fixed p25–75 and p5–95 population fields (default true). |
| `positive` | `"up" \| "down"` | Which direction is good — colors the endpoint dot (default up). |
| `label` | `"last" \| "none"` | Final percentile in a right gutter. |
| `unit` | `string` | (interactive) Reading noun for the interactive announcement (default 'step'). |
| `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).
