Heatmap Chart

Documentation Index

Fetch the complete documentation index at: /llms.txt. Use this file to discover all available pages before exploring further.

A GitHub-style calendar heatmap — one cell per day, colored by intensity — powered by Recharts

Basic Chart

Installation

npx shadcn@latest add @evilcharts/recharts-heatmap-chart

Usage

The heatmap is the GitHub contribution graph shape: a calendar year laid out as week columns, one square per day, with color intensity standing in for the value. <EvilHeatmapChart> is the container, and every part hangs off it as a compound member — <EvilHeatmapChart.Cell>, <EvilHeatmapChart.MonthLabel>, <EvilHeatmapChart.DayLabel>, <EvilHeatmapChart.Tooltip>, and <EvilHeatmapChart.Legend> — so a single import gives you the whole chart. The cells are intrinsic to the data, so <Cell> always renders and just configures the grid; the labels, tooltip, and legend follow presence semantics — omit one and it does not render.

import { EvilHeatmapChart, type ChartConfig, type HeatmapDatum } from "@/components/evilcharts/charts/recharts-heatmap-chart";
const data: HeatmapDatum[] = [
  { date: "2025-01-06", value: 3 },
  { date: "2025-01-07", value: 11 },
  { date: "2025-01-09", value: 1 },
];
 
const chartConfig = {
  contributions: {
    label: "Contributions",
    colors: {
      light: ["#9be9a8", "#40c463", "#30a14e", "#216e39"],
      dark: ["#0e4429", "#006d32", "#26a641", "#39d353"],
    },
  },
} satisfies ChartConfig;
 
<EvilHeatmapChart className="h-48" data={data} config={chartConfig}>
  <EvilHeatmapChart.Cell dataKey="contributions" />
  <EvilHeatmapChart.MonthLabel />
  <EvilHeatmapChart.DayLabel />
  <EvilHeatmapChart.Tooltip />
  <EvilHeatmapChart.Legend />
</EvilHeatmapChart>

Under the hood the grid is a Recharts ScatterChart: every day is a point on two numeric axes measured in cells — week columns along the top, weekday rows down the side — so the cells stay square at any container size and the axes double as the month and weekday labels. Each point is drawn as a rounded SVG rect carrying that day's ramp color.

config is the same contract as every EvilCharts chart — a key maps to a label and a per-theme colors array. See Chart Config for the full shape. Here the array does double duty: its length is the number of intensity levels, and each entry is that level's color. Colors resolve from CSS variables at runtime, so dark mode just works.

Interactive Selection

Set isClickable on <Cell> to make days selectable. The selected day keeps full strength and wears a ring while the rest soften. Handle selection events with the root's onSelectionChange callback:

isClickable
<EvilHeatmapChart
  data={data}
  config={chartConfig}
  onSelectionChange={(selection) => {
    if (selection) {
      console.log("Selected:", selection.date, "Value:", selection.value);
    } else {
      console.log("Deselected");
    }
  }}
>
  <EvilHeatmapChart.Cell dataKey="contributions" isClickable />
  <EvilHeatmapChart.Tooltip />
</EvilHeatmapChart>

Loading State

isLoading='true'

Examples

Examples of the heatmap in different configurations. Customize the root range, weekStart, and scale, the <Cell> radius and gap, and more.

Calendar Year

range={2025} scale='linear'

Monday Start

weekStart='monday'

Rounded Cells

radius={6} gap={5}

Explicit Thresholds

thresholds={[3, 10]}

API Reference

A root container plus a small set of composible parts. Render the root, then compose the parts you need as children.

EvilHeatmapChart

The root container. It owns the date window, the value → level mapping, shared selection state, and the loading skeleton. Everything visual is composed as children.

PropTypeDefaultDescription
data*HeatmapDatum[]–

One entry per day that has a value. HeatmapDatum is { date: string; value: number }, where date is an ISO calendar date (YYYY-MM-DD). Days absent from the array render as empty cells; duplicate dates are summed.

config*ChartConfig–

Defines the series. The key named by <Cell dataKey> supplies a label and a per-theme colors array that becomes the intensity ramp — lightest first, strongest last. Same contract as every EvilCharts chart — see Chart Config.

children*ReactNode–

The composed parts — <Cell />, <MonthLabel />, <DayLabel />, <Tooltip />, and <Legend />.

classNamestring–

Additional CSS classes for the chart container.

chartPropsScatterChartProps–

Escape hatch spread onto the underlying Recharts ScatterChart. data, children, and margin are owned by the root. See the ScatterChart documentation.

rangenumber | string | [string, string]–

The calendar window. A year (2025) shows January through December, a month string ("2025-03") shows that month, and an [start, end] pair of ISO dates shows that inclusive span. Omit it for the trailing year ending on the latest date in data.

weekStartsunday|monday"sunday"

Which day sits on the top row of each week column.

cellSizenumber–

A fixed cell size in pixels. Omit it to fit square cells to the container's width (capped by its height) and re-fit on resize.

scalequantile|linear"quantile"

How values map onto the ramp when no thresholds are given. "quantile" shares the non-zero days evenly across the levels, like GitHub, so a few huge days don't wash out the rest; "linear" divides the min→max span evenly.

thresholdsnumber[]–

Explicit ascending level boundaries. Values below the first threshold are level one; each threshold a value reaches moves it up a level. n thresholds make n + 1 levels, spread across however many colors the config provides.

defaultSelectedDatestring | nullnull

The ISO date selected on first render.

onSelectionChange(selection: { date: string; value: number } | null) => void–

Called when a day is selected or deselected. Receives the day's date and value, or null on deselect. Fires on click while <Cell /> has isClickable set.

isLoadingbooleanfalse

Shows the animated loading skeleton.

animationbooleantrue

Master switch for the intro sweep. Pass false to render the chart instantly.

animationTypenone|default"default"

"default" pops the cells in column by column, left to right, on first render; "none" disables it. The OS reduce-motion preference falls back to "none" automatically.

Cell

Configures the day cells. Always renders — it names the config key to color the grid with and shapes each cell.

PropTypeDefaultDescription
dataKey*string–

The config key whose colors array forms the intensity ramp and whose label names the value in the tooltip.

radiusnumber2

The corner radius of each cell in pixels. Set to 0 for square cells.

gapnumber3

The space between neighboring cells in pixels.

isClickablebooleanfalse

Lets days be clicked to select/deselect them. The selected day keeps full strength and wears a ring while the rest soften.

MonthLabel

Shows month names above the grid, each over the column where the month begins. Its presence enables it; omit it and none show.

PropTypeDefaultDescription
labelsstring[]["Jan", …, "Dec"]

Twelve month names, January first.

DayLabel

Shows weekday names beside the grid. Its presence enables it; omit it and none show.

PropTypeDefaultDescription
labelsstring[]["", "Mon", "", "Wed", "", "Fri", ""]

Seven day names, Sunday first, regardless of weekStart. An empty string leaves that row unlabeled — the default labels every other row, like GitHub.

Tooltip

The hover tooltip. Its presence enables it; omit it and none shows. Hovering a day shows the formatted date as the title and the series label with that day's value beneath it. Hidden automatically while loading.

PropTypeDefaultDescription
variantdefault|frosted-glass"default"

Controls the visual style of the tooltip surface.

roundnesssm|md|lg|xl"lg"

Controls the border-radius of the tooltip.

positionfixed|variable"variable"

Controls how the tooltip is anchored. "variable" follows the hovered cell (Recharts' default); "fixed" pins the tooltip to the top of the chart and only tracks the cell's X.

dateFormatter(date: string) => string–

Formats the hovered day's ISO date for the tooltip title. Defaults to "Mar 3, 2026" style English.

valueFormatter(value: number) => string(value) => value.toLocaleString()

Formats the hovered day's value.

Legend

The "Less → More" ramp rendered under the grid, one swatch per level plus the empty cell. Its presence enables it; omit it and none shows. Hidden while loading.

PropTypeDefaultDescription
alignleft|right"right"

Which side of the chart the ramp sits on.

lessLabelstring"Less"

Text before the ramp.

moreLabelstring"More"

Text after the ramp.