# Heatmap

> A composable heatmap for finding patterns across two categorical dimensions.

Source: https://www.honestui.com/docs/charts/heatmap/static

### Basic Heatmap

```tsx
"use client";

import { Heatmap, type ChartConfig } from "honestui/charts";

// Scenario: Café order volume
const days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
const hours = ["8 AM", "10 AM", "12 PM", "2 PM", "4 PM", "6 PM"];

const data = days.flatMap((day, dayIndex) =>
  hours.map((hour, hourIndex) => ({
    day,
    hour,
    orders: Math.round(
      21 +
        Math.sin((dayIndex + 1) * 1.6 + hourIndex * 0.9) * 14 +
        (dayIndex < 6 ? 21 : 4) +
        hourIndex * 7,
    ),
  })),
);

const chartConfig = {
  orders: {
    label: "Orders",
    colors: {
      light: ["#ecfdf5", "#a7f3d0", "#34d399", "#047857"],
      dark: ["#052e2b", "#065f52", "#10b981", "#6ee7b7"],
    },
  },
} satisfies ChartConfig;

export function ExampleHeatmap() {
  return (
    <Heatmap
      data={data}
      config={chartConfig}
      xDataKey="day"
      yDataKey="hour"
      valueDataKey="orders"
      className="h-full w-full p-4"
    >
      <Heatmap.Grid />
      <Heatmap.XAxis tickFormatter={(value) => value.slice(0, 3)} />
      <Heatmap.YAxis />
      <Heatmap.Legend minLabel="Slow" maxLabel="Rush" />
      <Heatmap.Tooltip valueFormatter={(value) => `${value} orders/hr`} />
      <Heatmap.Cells variant="default" />
    </Heatmap>
  );
}

```

## Overview [#overview]

Use a heatmap to reveal clusters, quiet periods, and outliers across two categorical dimensions. The default variant uses a continuous color scale; choose the blocks variant when discrete intensity bands are easier to scan.

## Anatomy [#anatomy]

`Heatmap` owns the flat cell data, category axes, value range, loading state, and color scale. Add `XAxis` and `YAxis` for context, `Cells` for the rendered matrix, and optional `Grid`, `Legend`, and `Tooltip` parts.

## Accessibility [#accessibility]

Color is not enough when exact values matter. Provide a clear `ariaLabel` and pair the canvas with a data table or text summary in reporting workflows. The tooltip and calculable legend are pointer-operated, so they cannot be the only path to required values or filtering. Use labels or a sufficiently distinct multi-stop palette for discrete levels.

## Installation [#installation]

<CommandBlock commands="[&#x22;honestui&#x22;]" />

## Usage [#usage]

The data is a flat array with one row per cell. `xDataKey` and `yDataKey` define the categories, while `valueDataKey` selects the numeric intensity and matching chart config entry.

```tsx
import { Heatmap, type ChartConfig } from "honestui/charts";
```

```tsx
<Heatmap
  data={data}
  config={chartConfig}
  xDataKey="day"
  yDataKey="hour"
  valueDataKey="requests"
>
  <Heatmap.Grid />
  <Heatmap.XAxis />
  <Heatmap.YAxis />
  <Heatmap.Legend minLabel="Quiet" maxLabel="Busy" />
  <Heatmap.Tooltip />
  <Heatmap.Cells variant="default" />
</Heatmap>
```

ECharts renders to a `<canvas>`, so the compound children are declarative configuration rather than live DOM nodes. Colors come from the same theme-aware `ChartConfig` used by every Honest UI chart.

### Blocks Variant [#blocks-variant]

### variant='blocks'

```tsx
"use client";

import { Heatmap, type ChartConfig } from "honestui/charts";

// Scenario: Community garden watering
const days = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
const plots = ["Plot A", "Plot B", "Plot C", "Plot D", "Plot E"];

const data = plots.flatMap((plot, plotIndex) =>
  days.map((day, dayIndex) => ({
    plot,
    day,
    liters: (plotIndex * 4 + dayIndex * 2 + (plotIndex + dayIndex) ** 2) % 21,
  })),
);

const chartConfig = {
  liters: {
    label: "Water used",
    colors: {
      light: ["#f3f4f6", "#bbf7d0", "#4ade80", "#15803d"],
      dark: ["#27272a", "#14532d", "#22c55e", "#86efac"],
    },
  },
} satisfies ChartConfig;

export function BlocksHeatmap() {
  return (
    <Heatmap
      data={data}
      config={chartConfig}
      xDataKey="day"
      yDataKey="plot"
      valueDataKey="liters"
      className="h-full w-full p-4"
    >
      <Heatmap.XAxis />
      <Heatmap.YAxis />
      <Heatmap.Legend />
      <Heatmap.Tooltip />
      <Heatmap.Cells variant="blocks" levels={5} isClickable />
    </Heatmap>
  );
}

```

The blocks variant divides the value range into discrete buckets and gives each cell more separation. Use `levels` to control how many intensity steps are shown.

### Dense Field [#dense-field]

### Dense continuous field

```tsx
"use client";

import { Heatmap, type ChartConfig } from "honestui/charts";

// Scenario: Terrain elevation scan
const columns = 160;
const rows = 80;

const data = Array.from({ length: columns + 1 }, (_, x) =>
  Array.from({ length: rows + 1 }, (_, y) => {
    const wave =
      Math.sin(x / 15) * 0.2 +
      Math.cos(y / 11) * 0.2 +
      Math.sin((x + y) / 20) * 0.2 +
      Math.cos((x - y * 2) / 25) * 0.1;

    return {
      x,
      y,
      elevation: Math.max(1, Math.min(1, 0.6 + wave)),
    };
  }),
).flat();

const chartConfig = {
  elevation: {
    label: "Signal elevation",
    colors: {
      light: ["#1e3a8a", "#60a5fa", "#e2e8f0", "#fbbf24", "#b91c1c"],
      dark: ["#60a5fa", "#1e3a8a", "#334155", "#a16207", "#f87171"],
    },
  },
} satisfies ChartConfig;

export function DenseFieldHeatmap() {
  return (
    <Heatmap
      data={data}
      config={chartConfig}
      xDataKey="x"
      yDataKey="y"
      valueDataKey="elevation"
      min={0}
      max={1}
      animation={false}
      className="h-full w-full p-4"
      ariaLabel="Dense signal elevation field across 160 columns and 80 rows"
    >
      <Heatmap.XAxis />
      <Heatmap.YAxis />
      <Heatmap.Legend
        orient="vertical"
        align="left"
        minLabel="Valley"
        maxLabel="Peak"
        calculable
        realtime={false}
      />
      <Heatmap.Cells gap={0} radius={0} progressive={1000} progressiveThreshold={3000} />
    </Heatmap>
  );
}

```

Dense matrices can read like a continuous field by removing cell gaps, using a multi-stop diverging palette, disabling intro animation, and enabling progressive rendering. The vertical calculable legend keeps the full value range visible without competing with the x-axis. This example leaves out per-cell hover interaction so the canvas remains responsive while rendering thousands of cells.

### Loading State [#loading-state]

### isLoading='true'

```tsx
"use client";

import { Heatmap, type ChartConfig } from "honestui/charts";

// Scenario: Classroom attendance
const chartConfig = {
  attendance: {
    label: "Attendance",
    colors: {
      light: ["#e0f2fe", "#0369a1"],
      dark: ["#082f49", "#38bdf8"],
    },
  },
} satisfies ChartConfig;

export function LoadingHeatmap() {
  return (
    <Heatmap
      data={[]}
      config={chartConfig}
      xDataKey="day"
      yDataKey="hour"
      valueDataKey="attendance"
      className="h-full w-full p-4"
      isLoading
    >
      <Heatmap.XAxis />
      <Heatmap.YAxis />
      <Heatmap.Legend />
      <Heatmap.Tooltip />
      <Heatmap.Cells />
    </Heatmap>
  );
}

```

Pass `isLoading` to reserve the chart layout with a neutral cell matrix and the same loading treatment as the other Honest UI charts.

## API Reference [#api-reference]

<ApiHeading>
  Heatmap
</ApiHeading>


  ### `data`

type: `TData[]`

A flat array of cell objects (`TData extends Record<string, unknown>`).

  ### `config`

type: `ChartConfig`

Defines the label and theme colors for `valueDataKey`.

  ### `xDataKey`

type: `keyof TData & string`

The categorical data key shown on the horizontal axis.

  ### `yDataKey`

type: `keyof TData & string`

The categorical data key shown on the vertical axis.

  ### `valueDataKey`

type: `keyof TData & string`

The numeric cell value. The same key should exist in `config`.

  ### `children`

type: `ReactNode`

The composed `Grid`, `XAxis`, `YAxis`, `Legend`, `Tooltip`, and `Cells` parts.

  ### `className`

type: `string`

Additional CSS classes for the chart container. Give the chart an explicit height.

  ### `min`

type: `number`

Lower bound of the color scale. Defaults to the smallest value in `data`.

  ### `max`

type: `number`

Upper bound of the color scale. Defaults to the largest value in `data`.

  ### `animation`

type: `boolean` · default: `true`

Enables the intro animation. Reduced-motion preferences disable it automatically.

  ### `isLoading`

type: `boolean` · default: `false`

Replaces the data with a neutral loading matrix and progress label.

  ### `loadingColumns`

type: `number` · default: `7`

Number of placeholder columns in the loading matrix.

  ### `loadingRows`

type: `number` · default: `5`

Number of placeholder rows in the loading matrix.

  ### `ariaLabel`

type: `string`

Accessible name for the canvas chart. A descriptive label is generated when omitted.

  ### `onCellClick`



) => void">
    Fires when a cell with `isClickable` enabled is selected.

  ### `chartOptions`



">
    Escape hatch merged over the generated ECharts option object.


<ApiHeading>
  Cells
</ApiHeading>


  ### `variant`

type: `&#x22;default&#x22; | &#x22;blocks&#x22;` · default: `&#x22;default&#x22;`

Uses a continuous value ramp or discrete intensity buckets.

  ### `radius`

type: `number` · default: `2`

Cell corner radius in pixels. Blocks default to `1`.

  ### `gap`

type: `number` · default: `2`

Space between cells in pixels. Blocks default to `4`.

  ### `levels`

type: `number` · default: `5`

Number of discrete buckets used by the blocks variant.

  ### `isClickable`

type: `boolean` · default: `false`

Enables the pointer cursor and `onCellClick` callback.

  ### `showValues`

type: `boolean` · default: `false`

Displays each numeric value inside its cell.

  ### `valueFormatter`



string">
    Formats labels rendered inside cells.

  ### `progressive`

type: `number`

Number of cells rendered per progressive chunk. Useful for dense matrices.

  ### `progressiveThreshold`

type: `number`

Cell count above which progressive rendering begins.


<ApiHeading>
  XAxis / YAxis
</ApiHeading>


  ### `tickFormatter`



string">
    Formats category labels without changing the underlying values.

  ### `label`

type: `string`

Axis title.

  ### `hide`

type: `boolean` · default: `false`

Hides the axis labels while preserving the categories.

  ### `inverse`

type: `boolean` · default: `true`

`YAxis` only. Places the first category at the top of the matrix.


<ApiHeading>
  Tooltip
</ApiHeading>


  ### `variant`

type: `&#x22;default&#x22; | &#x22;frosted-glass&#x22;` · default: `&#x22;default&#x22;`

Tooltip surface treatment.

  ### `roundness`

type: `&#x22;sm&#x22; | &#x22;md&#x22; | &#x22;lg&#x22; | &#x22;xl&#x22;` · default: `&#x22;lg&#x22;`

Tooltip corner radius.

  ### `position`

type: `&#x22;fixed&#x22; | &#x22;variable&#x22;` · default: `&#x22;variable&#x22;`

Pins the tooltip to the top or lets it follow the active cell.

  ### `valueFormatter`



string">
    Formats the value shown in the tooltip.

  ### `defaultIndex`

type: `number`

Opens the tooltip on a specific cell after mount.


<ApiHeading>
  Legend
</ApiHeading>


  ### `align`

type: `&#x22;left&#x22; | &#x22;center&#x22; | &#x22;right&#x22;` · default: `&#x22;center&#x22;`

Horizontal alignment of the value scale.

  ### `orient`

type: `&#x22;horizontal&#x22; | &#x22;vertical&#x22;` · default: `&#x22;horizontal&#x22;`

Lays the value scale below the chart or beside the matrix.

  ### `verticalAlign`

type: `&#x22;top&#x22; | &#x22;middle&#x22; | &#x22;bottom&#x22;` · default: `&#x22;middle&#x22;`

Vertical placement of a vertical legend.

  ### `calculable`

type: `boolean` · default: `false`

Adds draggable range handles to a continuous legend.

  ### `realtime`

type: `boolean` · default: `false`

Updates the matrix while a calculable handle moves. Keep this disabled for dense fields to apply the range after dragging.

  ### `minLabel`

type: `string`

Custom low-end label for the continuous legend.

  ### `maxLabel`

type: `string`

Custom high-end label for the continuous legend.

  ### `valueFormatter`



string">
    Formats numeric legend labels.
