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.json

Engine: ·

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 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 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.

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).

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.