# DotPlot (/docs/charts/dot-plot)

DotPlot places each named value as a dot on one shared scale, which is the minimum ink per comparison. Reach for it over
bars when the scale doesn't start at zero: position stays readable where a truncated bar length would overstate the
difference. Without `stem` the domain fits the data and position is the read; with `stem` the domain is forced through
zero and the read becomes magnitude.

```tsx
import { DotPlot } from "@microcharts/react/dot-plot";

<DotPlot data={[
  { label: "Ada", value: 96 },
  { label: "Kim", value: 41 },
  { label: "Sam", value: 88 },
]} title="Review scores" />
```

## Install

```tsx
import { DotPlot } from "@microcharts/react/dot-plot";

<DotPlot data={team} title="Review scores" />
```

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 { DotPlot } from "@microcharts/react/dot-plot/interactive";

<DotPlot
  data={team}
/>
```

## When to use it

Use it for KPI leaderboards, named comparisons in cards, and rows where a truncated bar would overstate the difference.
Avoid it above 7 rows; for a time series use Sparkline.

## Sizing

**KPI leaderboard**

```tsx
<DotPlot data={team} label="value" highlight="Ada"
  width={140} height={56} />
```

**magnitude read (stems)**

```tsx
// stems force a zero-anchored domain — position becomes magnitude
<DotPlot data={team} stem />
```

## Variants

```tsx
// stems anchor the read to zero — position becomes magnitude
<DotPlot data={team} stem />
```

```tsx
<DotPlot data={team} label="value" highlight="Ada" />
```

## Edge cases

```tsx
<DotPlot data={[]} />
```

```tsx
<DotPlot data={[{ label: "Only", value: 42 }]} />
```

```tsx
// a missing value draws no dot (and no label) but the row still holds its
// slot, so the rows around it don't drift together
<DotPlot data={[
  { label: "Ada", value: 96 },
  { label: "Kim", value: null },
  { label: "Sam", value: 88 },
]} />
```

An empty series has nothing to compare, so no dots draw and the accessible name reads **"No data."** A single category
still renders normally, one dot and one label, with the summary's "category"/"categories" wording matching the count
(**"1 category. Highest Only 42, lowest Only 42."**). A `null` value draws no dot and no label for its row, but the row
still occupies its slot in the vertical rhythm, so the real values around it keep the spacing they'd have in a fully
populated set. It is also excluded from the category count in the summary.

Two dots that would land on the same spot on adjacent rows de-overlap by half a unit. Labels truncate by character count
and drop entirely once rows get dense. Both are arithmetic on the layout; nothing is measured from the rendered text.

## Four homes

**In a sentence**

```tsx
<p>
  This cycle's review scores{" "}
  <span className="mc-inline">
    <DotPlot data={team} width={76} height={34} summary={false} />
  </span>{" "}
  spread from Kim's 41
  to Ada's 96 — a 55-point range.
</p>
```

**In a table cell**

```tsx
<td>
  <DotPlot data={pod.scores} width={70} height={24} />
</td>
```

**In a KPI card**

```tsx
<div className="kpi">
  <span className="figure">96</span>
  <span className="unit">Ada, of 5 reviewed</span>
  <DotPlot data={team} label="value" highlight="Ada" width={110} height={46} />
</div>
```

**In a tab header**

```tsx
<button className="tab">
  Engineering <DotPlot data={pod.scores.slice(0, 3)} width={44} height={20} summary={false} />
</button>
```

## Accessibility

The accessible name carries the count and the extremes: **"3 categories. Highest Ada 96, lowest Kim 41."** The
interactive entry roves rows and announces each one with its rank (**"Ada: 96 — 1st of 3."**).

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) | `{ label; value }[]` | Named values. |
| `stem` | `boolean` | Hairline from zero — flips to a magnitude read (zero-anchored domain forced). |
| `highlight` | `number \| string` | Accent one category. |
| `label` | `"value" \| "none"` | Value text beside each dot (drops out under 8-unit rows). |
| `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).
