<!-- Protocore Design System — Heatmap -->
# Heatmap

- **Category:** Charts (`charts`)
- **Slug:** `charts/heatmap`
- **Status:** stable
- **Platforms:** web
- **Import:** `import { Heatmap } from "@protocore/pds/charts";`
- **Docs:** https://pds.protocore.io/components/charts/heatmap

> A hand-rolled grid heatmap: sharp cells whose fill opacity encodes value share of the domain max, monochrome by default.

## Import path

`Heatmap` ships from the **`@protocore/pds/charts`** subpath. Unlike the recharts wrappers it is hand-rolled tokened markup — **no recharts dependency** — so it renders anywhere, but it is grouped with the charts family for its ramp conventions.

```tsx
import { Heatmap } from "@protocore/pds/charts";
import { ChartContainer } from "@protocore/pds";
```

## When to use it

Use a **Heatmap** for a *dense two-way matrix* — activity by weekday × hour, errors by service × region, a calendar of daily counts. Intensity encodes magnitude by opacity, keeping the palette monochrome; switch `color` to a signal hue only when the whole grid measures one charged thing (errors, risk).

For a single ranked list of values use a [BarsList](/charts/bars-list); for a part-to-whole split use a [PercentageBarChart](/charts/percentage-bar-chart).

## Props

| Prop | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `data` | `HeatmapCell[]` | yes | — | The cells to plot. `HeatmapCell` = `{ x: string; y: string; value: number }` — `x`/`y` must match `xLabels`/`yLabels`; missing pairs render empty. |
| `xLabels` | `string[]` | yes | — | Column labels, left → right. |
| `yLabels` | `string[]` | yes | — | Row labels, top → bottom. |
| `color` | `string` | no | `var(--pds-chart-1)` | The hue whose opacity encodes intensity. |
| `cellSize` | `number` | no | `20` | Cell edge length in px. |
| `minOpacity` | `number` | no | `0.08` | Minimum fill opacity for a non-zero cell (the floor). |
| `max` | `number` | no | `largest value in data` | Domain max; a cell's intensity is `value / max`. |
| `formatValue` | `(value: number, cell: HeatmapCell) => string` | no | `String(value)` | Format a cell's value for its title/tooltip. |
| `label` | `string` | no | `auto-composed` | Accessible summary (role=img aria-label). Composed from grid dimensions when omitted. |

## Examples

### Basics

Pass `data` as `{ x, y, value }` cells plus the `xLabels`/`yLabels` axes. Each cell's opacity is its value's share of the domain max over a single hue — the ink `chart-1` by default — so the grid reads in grayscale. Missing pairs render as a faint ghost cell.

```tsx
import { ChartContainer } from "@protocore/pds";
import { Heatmap } from "@protocore/pds/charts";

const days = ["Mon", "Tue", "Wed", "Thu", "Fri"];
const hours = ["09", "12", "15", "18", "21"];

const data = days.flatMap((x, di) =>
  hours.map((y, hi) => ({ x, y, value: Math.round(20 + 60 * Math.sin((di + hi) / 2) ** 2) })),
);

export default function Demo() {
  return (
    <ChartContainer label="Requests by day × hour" height={200}>
      <Heatmap data={data} xLabels={days} yLabels={hours} cellSize={22} />
    </ChartContainer>
  );
}
```

## Do & don't

**Do**

- Let opacity carry magnitude — keep to one hue per grid.
- Give short `xLabels`/`yLabels` so the axis headers stay legible.
- Set `max` explicitly to compare two heatmaps on the same scale.
- Provide a `label` (or rely on the composed one) for assistive tech.

**Don't**

- Don't colour cells by category — this is a sequential intensity, not a ramp.
- Don't round the cells or add shadows; keep the geometry sharp.
- Don't pack in so many columns that cells shrink below readability.
- Don't import it from `@protocore/pds`; it lives on the `/charts` subpath.

## Accessibility

**Notes**

- The grid exposes `role="img"` with a composed aria-label (grid dimensions + max); override via `label`.
- Intensity encodes value by opacity independently of hue, so the matrix stays readable in grayscale and for colour-vision deficiencies.
- Each cell carries a `title` with its coordinates and value for pointer inspection.

## Related

`bars-list`, `percentage-bar-chart`, `chart-container`

---

© Protocore. All rights reserved. Use of the Protocore Design System requires prior written authorization from Protocore (contact@protocore.io). These machine-readable materials must not be ingested into ML-training datasets or derivative design systems. See https://pds.protocore.io/legal/
