HeatmapFree
A matrix or calendar heatmap with a stepped or diverging scale and empty outlines for missing cells. Plain SVG: no chart engine.
npx shadcn@latest add https://beautifulcharts.dev/r/heatmap.jsonEngine: ·
Code
"use client";
import { Heatmap } from "@/components/beautiful-charts/themes/instrument/heatmap";
// One row per day, as "YYYY-MM-DD". Days with no row are drawn as empty outlines, never as zero.
const days = Array.from({ length: 120 }, (_, i) => ({
day: new Date(Date.UTC(2026, 5, 1) + i * 86_400_000).toISOString().slice(0, 10),
deploys: (i * 7) % 5,
}));
export function DeployCalendar() {
return (
<Heatmap
data={days}
layout="calendar"
dateKey="day"
valueKey="deploys"
config={{ deploys: { label: "Deploys", color: "var(--chart-2)" } }}
title="Deploys per day"
onSelectedChange={(selection) => console.log(selection?.datum.day)}
/>
);
}
Props
valueKey*
keyof TDatum & string
The field holding each cell's value. Its entry in
configsets the colour and label.layout
"matrix" | "calendar"
"matrix" (default) places cells by
xKeyandyKey; "calendar" places them bydateKey, one per day.xKey
keyof TDatum & string
Matrix: the field for columns.
yKey
keyof TDatum & string
Matrix: the field for rows.
xOrder
string[]
Matrix: column order. Defaults to first appearance in the data.
yOrder
string[]
Matrix: row order. Defaults to first appearance in the data.
dateKey
keyof TDatum & string
Calendar: the field holding each day, as "YYYY-MM-DD" (read in UTC).
from
string
Calendar: the first day shown. Defaults to the earliest day in the data.
to
string
Calendar: the last day shown. Defaults to the latest day in the data.
weekStart
number
Calendar: first day of the week (0 = Sunday). Defaults to the locale's.
scale
"sequential" | "diverging"
"sequential" (default) ramps one colour; "diverging" uses two colours either side of
midpoint.midpoint
number
Diverging: the neutral value. Default 0.
domain
[number, number]
Fix the scale's range. Values outside it take the end colour and are flagged. Defaults to a nice range around the data.
steps
number
Colour steps, 2–9 (default 5), shown in the legend as ranges. 0 draws a continuous ramp.
showValues
boolean
Write each value in its cell when it fits.
formatX
(value: string) => string
Formats column labels (matrix), in the axis, tooltip and table.
formatY
(value: string) => string
Formats row labels (matrix), in the axis, tooltip and table.
cellGap
number
Space between cells in px (default 2).
cellRadius
number
Cell corner radius in px (default 3).
| Prop | Type | Description |
|---|---|---|
| valueKey* | keyof TDatum & string | The field holding each cell's value. Its entry in config sets the colour and label. |
| layout | "matrix" | "calendar" | "matrix" (default) places cells by xKey and yKey; "calendar" places them by dateKey, one per day. |
| xKey | keyof TDatum & string | Matrix: the field for columns. |
| yKey | keyof TDatum & string | Matrix: the field for rows. |
| xOrder | string[] | Matrix: column order. Defaults to first appearance in the data. |
| yOrder | string[] | Matrix: row order. Defaults to first appearance in the data. |
| dateKey | keyof TDatum & string | Calendar: the field holding each day, as "YYYY-MM-DD" (read in UTC). |
| from | string | Calendar: the first day shown. Defaults to the earliest day in the data. |
| to | string | Calendar: the last day shown. Defaults to the latest day in the data. |
| weekStart | number | Calendar: first day of the week (0 = Sunday). Defaults to the locale's. |
| scale | "sequential" | "diverging" | "sequential" (default) ramps one colour; "diverging" uses two colours either side of midpoint. |
| midpoint | number | Diverging: the neutral value. Default 0. |
| domain | [number, number] | Fix the scale's range. Values outside it take the end colour and are flagged. Defaults to a nice range around the data. |
| steps | number | Colour steps, 2–9 (default 5), shown in the legend as ranges. 0 draws a continuous ramp. |
| showValues | boolean | Write each value in its cell when it fits. |
| formatX | (value: string) => string | Formats column labels (matrix), in the axis, tooltip and table. |
| formatY | (value: string) => string | Formats row labels (matrix), in the axis, tooltip and table. |
| cellGap | number | Space between cells in px (default 2). |
| cellRadius | number | Cell corner radius in px (default 3). |
Props every chart shares (29): data, config, header, states, legend, tooltip, formatting, motion and selection
data*
TDatum[]
Your rows, as they are. Missing values (null, undefined, NaN) stay missing and are never drawn as zero.
locale
string
BCP 47 locale. Defaults to "en-US" so server and client render the same text.
timeZone
string
IANA time zone for dates. Defaults to "UTC" so server and client agree.
config
ChartConfig
Per-series label, colour, icon, format and style, keyed by series key. shadcn ChartConfig works as it is.
eyebrow
ReactNode
Small label above the title.
title
ReactNode
Card title. A string also names the chart for screen readers.
description
ReactNode
A line under the title.
takeaway
ReactNode | ((summary: string) => ReactNode) | false
The sentence under the title. Computed from the data by default; pass text, a function of the computed sentence, or false to hide it.
ariaDescription
string
Accessible summary for screen readers. Computed from the data by default.
motion
MotionPreference
"full" (default), "subtle" or "off". Reduced-motion settings always win.
motionOptions
MotionOptions
When the entrance plays.
sound
boolean
Sound cues on hover, scrub and select. Plays only if the visitor has also turned sound on for the page.
density
Density
"comfortable" (default) or "compact" for dashboards.
tooltip
TooltipOptions<TDatum> | false
Tooltip position, indicator, cursor and custom content, or false to turn it off.
valueFormatter
(value: number) => string
Values formatted for display. Series formatters in config win.
loading
boolean
Show the loading state. Over existing data, the old marks stay dimmed instead of vanishing.
error
string | null
An error message. Shows the error state in place of the plot.
emptyState
ReactNode
Shown instead of the plot when there is no data.
loadingState
ReactNode
Shown while
loadingis true and there is no data yet.errorState
ReactNode
Shown when
erroris set.selected
number | null
Controlled and uncontrolled interaction state. Point selection (
selected) pins one row by its index indata. Series selection (selectedSeries) picks one series and dims the others. They are separate: picking a row doesn't pick a series.defaultSelected
number | null
Initial selected row when uncontrolled.
activeIndex
number | null
Controlled hover or focus index, for syncing several charts on one cursor.
palette
"theme" | "shadcn"
Series colours: "theme" (default) uses the theme's designed palette; "shadcn" uses your --chart-1..5. --bc-series-N always wins. You can also opt a whole section in with the class bc-use-shadcn-colors.
table
boolean
Show the "Show data table" disclosure. On by default.
stats
ChartStat[]
A row of figures under the title, for dashboard cards and blocks.
footer
ReactNode
A line or action at the bottom of the card, e.g. a source note or a link.
className
string
Classes on the card, for spacing and layout.
height
number
Height of the plot area in px. The frame sizes around it.
| Prop | Type | Description |
|---|---|---|
| data* | TDatum[] | Your rows, as they are. Missing values (null, undefined, NaN) stay missing and are never drawn as zero. |
| locale | string | BCP 47 locale. Defaults to "en-US" so server and client render the same text. |
| timeZone | string | IANA time zone for dates. Defaults to "UTC" so server and client agree. |
| config | ChartConfig | Per-series label, colour, icon, format and style, keyed by series key. shadcn ChartConfig works as it is. |
| eyebrow | ReactNode | Small label above the title. |
| title | ReactNode | Card title. A string also names the chart for screen readers. |
| description | ReactNode | A line under the title. |
| takeaway | ReactNode | ((summary: string) => ReactNode) | false | The sentence under the title. Computed from the data by default; pass text, a function of the computed sentence, or false to hide it. |
| ariaDescription | string | Accessible summary for screen readers. Computed from the data by default. |
| motion | MotionPreference | "full" (default), "subtle" or "off". Reduced-motion settings always win. |
| motionOptions | MotionOptions | When the entrance plays. |
| sound | boolean | Sound cues on hover, scrub and select. Plays only if the visitor has also turned sound on for the page. |
| density | Density | "comfortable" (default) or "compact" for dashboards. |
| tooltip | TooltipOptions<TDatum> | false | Tooltip position, indicator, cursor and custom content, or false to turn it off. |
| valueFormatter | (value: number) => string | Values formatted for display. Series formatters in config win. |
| loading | boolean | Show the loading state. Over existing data, the old marks stay dimmed instead of vanishing. |
| error | string | null | An error message. Shows the error state in place of the plot. |
| emptyState | ReactNode | Shown instead of the plot when there is no data. |
| loadingState | ReactNode | Shown while loading is true and there is no data yet. |
| errorState | ReactNode | Shown when error is set. |
| selected | number | null | Controlled and uncontrolled interaction state. Point selection (selected) pins one row by its index in data. Series selection (selectedSeries) picks one series and dims the others. They are separate: picking a row doesn't pick a series. |
| defaultSelected | number | null | Initial selected row when uncontrolled. |
| activeIndex | number | null | Controlled hover or focus index, for syncing several charts on one cursor. |
| palette | "theme" | "shadcn" | Series colours: "theme" (default) uses the theme's designed palette; "shadcn" uses your --chart-1..5. --bc-series-N always wins. You can also opt a whole section in with the class bc-use-shadcn-colors. |
| table | boolean | Show the "Show data table" disclosure. On by default. |
| stats | ChartStat[] | A row of figures under the title, for dashboard cards and blocks. |
| footer | ReactNode | A line or action at the bottom of the card, e.g. a source note or a link. |
| className | string | Classes on the card, for spacing and layout. |
| height | number | Height of the plot area in px. The frame sizes around it. |
Events (3)
onSelectedChange
(selection: Selection<TDatum> | null) => void
Called when a row is selected or cleared, by pointer, keyboard or the data table.
onActiveChange
(index: number | null) => void
Called as the hovered or focused row changes, or null when it leaves.
onReady
(chart: EChartsType) => void
Called once with the ECharts instance, e.g. to add event listeners.
| Prop | Type | Description |
|---|---|---|
| onSelectedChange | (selection: Selection<TDatum> | null) => void | Called when a row is selected or cleared, by pointer, keyboard or the data table. |
| onActiveChange | (index: number | null) => void | Called as the hovered or focused row changes, or null when it leaves. |
| onReady | (chart: EChartsType) => void | Called once with the ECharts instance, e.g. to add event listeners. |
Apache ECharts build only (2)
renderer
EChartsRenderer
"canvas" (default, best for large grids) or "svg".
option
OptionOverride
Deep-merged over the generated option, or
(generated) => option. Cells are one custom series (id "cells", one item per cell).
| Prop | Type | Description |
|---|---|---|
| renderer | EChartsRenderer | "canvas" (default, best for large grids) or "svg". |
| option | OptionOverride | Deep-merged over the generated option, or (generated) => option. Cells are one custom series (id "cells", one item per cell). |
Accessibility, keyboard and motion
- Pointer
- Hover a cell to show its row, column and value; click pins it. Arrow keys move through the grid.
- Keyboard
- Tab lands once on the grid. Left and Right move between columns, Up and Down between rows, Home and End jump to the ends of a row, Enter or Space pins, Escape clears.
- Screen readers
- Every move is announced as a sentence, not coordinates. "Show data table" opens the numbers as a real table, and missing values stay empty, never zero.
- Motion
- Cells fade in across the grid, so the pattern builds. With reduced motion everything appears settled at once, and nothing depends on the motion to be read.