M
MetricUI
Charts

HeatMap

Two-dimensional matrix with color intensity. Day×hour usage, correlation, activity grids.

import { HeatMap } from "metricui";

Use HeatMap when you need to show patterns across two categorical axes — day/hour activity grids, server response time matrices, or correlation tables. For single-axis trends over time, use AreaChart or BarChart instead.

Basic Example

Weekly Activity
<HeatMap
  data={[
    { id: "Mon", data: [{ x: "9am", y: 12 }, { x: "10am", y: 45 }, { x: "11am", y: 78 }] },
    { id: "Tue", data: [{ x: "9am", y: 23 }, { x: "10am", y: 56 }, { x: "11am", y: 92 }] },
    { id: "Wed", data: [{ x: "9am", y: 18 }, { x: "10am", y: 62 }, { x: "11am", y: 85 }] },
  ]}
  title="Weekly Activity"
/>

Labels & Formatting

Enable enableLabels to show formatted values inside each cell. Combine with format for number formatting and borderRadius for rounded cells.

User Activity

Sessions by day and hour

<HeatMap
  data={activityData}
  title="User Activity"
  subtitle="Sessions by day and hour"
  format="number"
  enableLabels
  borderRadius={6}
  height={320}
/>

Diverging Color Scale

Switch to colorScale="diverging" for data with a meaningful midpoint (like correlations from -1 to 1). Combine with forceSquare for symmetric matrices.

Metric Correlations
<HeatMap
  data={correlationData}
  title="Metric Correlations"
  colorScale="diverging"
  enableLabels
  forceSquare
/>

Simple Data Shorthand

Use simpleData for a flat object format that gets converted to the series format automatically.

// Instead of the full series format, use simpleData:
<HeatMap
  simpleData={{
    Mon: { "9am": 12, "10am": 45, "11am": 78 },
    Tue: { "9am": 23, "10am": 56, "11am": 92 },
  }}
  title="Weekly Activity"
/>

Data States

Every component handles loading, empty, and error states.

Loading

Error

Failed to load data

// Loading state
<HeatMap data={[]} title="Activity" loading />

// Error state
<HeatMap data={[]} title="Activity" error={{ message: "Failed to load" }} />

Props

PropTypeDescription
data*
{ id: string; data: { x: string; y: number | null }[] }[]

Array of series. Each series is a row, each datum is a column cell.

index
string

Column key for row IDs. Used with the unified flat-row data format. If omitted with categories, auto-inferred as the first string column.

categories
Category[]

Columns to use as cell columns. Accepts plain strings or CategoryConfig objects ({ key, label?, format?, color?, axis? }). If omitted with index, auto-inferred as all number columns.

simpleData
Record<string, Record<string, number | null>>

Simple 2D object format. Converted to series internally. data takes precedence.

title
string

Card title.

subtitle
string

Card subtitle.

description
string | React.ReactNode

Popover description.

footnote
string

Footnote below the chart.

action
React.ReactNode

Action slot in the header.

format
FormatOption

Format for cell values and tooltips.

height
number

Chart height in px.

colorScale
"sequential" | "diverging"

Color scale type.

colors
string[]

Custom color stops. Overrides colorScale.

emptyColor
string

Color for null/missing cells.

borderRadius
number

Cell corner radius.

enableLabels
boolean

Show values inside cells.

forceSquare
boolean

Force cells to be square.

cellPadding
number

Inner padding between cells (0-1).

hoverTarget
"cell" | "row" | "column" | "rowColumn"

What to highlight on hover. 'cell' = single cell, 'row' = entire row, 'column' = entire column, 'rowColumn' = cross-hair highlighting both row and column. Use 'rowColumn' for comparing patterns across dimensions.

onCellClick
(cell: { serieId: string; x: string; value: number | null }) => void

Click handler for cells.

animate
boolean

Enable animation. Reads from MetricProvider.

variant
CardVariant

Card variant (supports custom strings). CSS-variable-driven via [data-variant]. Reads from MetricProvider.

dense
boolean

Dense mode. Reads from MetricProvider.

className
string

Additional class names.

classNames
{ root?: string; header?: string; chart?: string }

Sub-element class overrides.

id
string

HTML id.

data-testid
string

Test id.

loading
boolean

Show skeleton.

empty
EmptyState

Empty state.

error
ErrorState

Error state.

stale
StaleState

Stale indicator.

crossFilter
boolean | { field: string }

Enable cross-filter signal on click. true uses the row id field, { field } overrides. Emits selection via CrossFilterProvider — does NOT change chart appearance. Dev reads selection with useCrossFilter() and filters their own data.

tooltipHint
boolean | string

Show action hint at bottom of tooltip. true: auto ('Click to drill down' / 'Click to filter'). string: custom hint text. false: disable. Default: respects MetricConfig.tooltipHint (true).

drillDown
boolean | ((event: { serieId: string; x: string; y: number }) => React.ReactNode)

Enable drill-down on cell click. `true` auto-generates a summary KPI row + filtered DataTable from the chart's source data. Pass a render function for full control over the panel content. Requires DrillDown.Root wrapper. When both drillDown and crossFilter are set, drillDown wins.

drillDownMode
DrillDownMode

Presentation mode for the drill-down panel. "slide-over" (default) slides from the right, full height. "modal" renders centered and compact.

Data Shape

// Each series is a row, each datum is a column
type HeatMapSeries = {
  id: string;           // Row label (e.g. "Monday")
  data: {
    x: string;          // Column label (e.g. "9am")
    y: number | null;   // Cell value
  }[];
};

// Or use simpleData shorthand:
const simpleData = {
  Mon: { "9am": 12, "10am": 45, "11am": 78 },
  Tue: { "9am": 23, "10am": 56, "11am": 92 },
};

Notes

  • Uses forwardRef — attach a ref to the root div.
  • Uses ChartContainer for consistent card styling.
  • simpleData shorthand converts { row: { col: value } } to the series format automatically.
  • Sequential color scale uses blues scheme. Diverging uses red-yellow-green.
  • Custom colors array overrides the preset color scales.
  • crossFilter prop emits a selection signal on click — it does NOT change the chart's appearance. The dev reads the selection via useCrossFilter() and filters their own data.
  • drillDown={true} auto-generates a summary KPI row + filtered DataTable from the chart's source data. Pass a render function for custom panel content. Requires DrillDown.Root wrapper.
  • When both drillDown and crossFilter are set on the same component, drillDown wins.

Playground

Experiment with every prop interactively. Adjust the controls on the right to see the component update in real time.

Live Preview

Weekly Activity

User sessions by day and hour

Code

<HeatMap
  data={weeklyActivityData}
  title="Weekly Activity"
  subtitle="User sessions by day and hour"
/>

Props

Adjust props to see the chart update in real time

Switch between sample datasets

Cell corner radius in px

Inner padding between cells (0-1 ratio)

sequential = single hue, diverging = two-tone with midpoint