Skip to contents

A calendar heatmap lays out one rounded square per day on a grid where each column is a week and each row is a day of the week, so a whole year fits in a single compact band. Each square is colored by the value recorded on that day using a sequential palette, which makes weekly rhythms, seasonal patterns, streaks and gaps easy to spot. The layout was popularised by GitHub's contribution graph and is the signature chart of the Frappe Charts library.

Month labels are drawn above the grid, weekday labels on its left, and a "Less ... More" legend underneath. When the data span several years, one panel is stacked per year. Days without data (and, when all values are non-negative, days with a value of zero) are drawn in emptyCol. Hovering over a day shows a tooltip with its value and date, and hovering over a legend square highlights the days that fall in that color bucket.

Usage

calendar_heatmap(
  data,
  date,
  value = NULL,
  aggregate = c("sum", "mean", "max", "min", "count"),
  weekStart = c("sunday", "monday"),
  colors = c("#c0ddf9", "#73b3f3", "#3886e1", "#17459e"),
  emptyCol = "#ebedf0",
  cellRadius = 2,
  cellGap = 3,
  valueLabel = NULL,
  valueFormat = ",.2~f",
  dateFormat = "%a %b %-d, %Y",
  showYearLabel = TRUE,
  showMonthLabels = TRUE,
  showWeekdayLabels = TRUE,
  labelFontSize = 10,
  legend = TRUE,
  legendLabels = c("Less", "More"),
  hover = TRUE,
  animate = TRUE,
  title = NULL,
  titleFontSize = 16,
  font = "Verdana, Geneva, Tahoma, sans-serif",
  bgcol = "white",
  axisCol = "#555555",
  width = NULL,
  height = NULL
)

Arguments

data

The data frame containing the variables to consider.

date

The name of the column holding the dates. It can be of class Date, POSIXct or a character vector in "YYYY-MM-DD" format.

value

Optional. The name of the numeric column holding the value of each day. When NULL (default), the number of rows per day is used, which is handy for event logs (one row per event).

aggregate

How to combine several rows that share the same date. One of "sum" (default), "mean", "max", "min" or "count". Ignored (always "count") when value is NULL.

weekStart

The first day of the week, i.e. the top row of the grid. Either "sunday" (default) or "monday".

colors

A vector of colors, from low to high, used to fill days that have a value. Values are split into length(colors) equal-width buckets. Defaults to a Frappe-like blue ramp c("#c0ddf9", "#73b3f3", "#3886e1", "#17459e").

emptyCol

Fill color of days without data (or with a value of zero when all values are non-negative). Defaults to "#ebedf0".

cellRadius

Corner radius of the day squares in pixels. Defaults to 2.

cellGap

Gap between day squares in pixels. Defaults to 3.

valueLabel

Optional. The unit shown after the value in the tooltip and year totals (e.g. "commits"). Defaults to the name of the value column, or "events" when value is NULL.

valueFormat

A d3-format specifier used to display values (e.g. ",.0f", "$,.2s"). Defaults to ",.2~f".

dateFormat

A d3-time-format specifier used to display dates in the tooltip. Defaults to "%a %b %-d, %Y" (e.g. "Wed Jan 7, 2026").

showYearLabel

Whether to print the year and its total above each panel. Defaults to TRUE.

showMonthLabels

Whether to print month labels above the grid. Defaults to TRUE.

showWeekdayLabels

Whether to print weekday labels (Mon, Wed, Fri) on the left of the grid. Defaults to TRUE.

labelFontSize

Font size of the month, weekday, year and legend labels. Defaults to 10.

legend

Whether to display the "Less ... More" legend. Defaults to TRUE.

legendLabels

A character vector of length 2 giving the text drawn on each side of the legend. Defaults to c("Less", "More").

hover

Whether to highlight the hovered day and show a tooltip. Defaults to TRUE.

animate

Whether the day squares fade in with a staggered animation on first render. Defaults to TRUE.

title

Optional. Title of the chart.

titleFontSize

Font size of the chart title. Defaults to 16.

font

The font family used for all text. Defaults to "Verdana, Geneva, Tahoma, sans-serif".

bgcol

Background color of the SVG. Defaults to "white".

axisCol

Color of the title and of the month, weekday, year and legend labels. Defaults to "#555555".

width

Optional. The width of the SVG output.

height

Optional. The height of the SVG output. Defaults to a height that fits the number of years displayed.

Value

An SVG calendar heatmap.

Examples

# One year of simulated daily activity, busier on weekdays
set.seed(2026)
days <- seq(as.Date("2026-01-01"), as.Date("2026-12-31"), by = "day")
weekday <- !(format(days, "%u") %in% c("6", "7"))
activity <- data.frame(
  day     = days,
  commits = rpois(length(days), lambda = ifelse(weekday, 6, 1.5)) *
            rbinom(length(days), 1, 0.8)
)

calendar_heatmap(
  data       = activity,
  date       = "day",
  value      = "commits",
  title      = "Commits in 2026"
)
# Several years stacked, weeks starting on Monday, GitHub-like greens set.seed(1) days <- seq(as.Date("2021-01-01"), as.Date("2023-12-31"), by = "day") season <- 10 + 8 * sin(2 * pi * as.numeric(format(days, "%j")) / 365) rainfall <- data.frame(day = days, rain = pmax(0, rnorm(length(days), season, 6))) calendar_heatmap( data = rainfall, date = "day", value = "rain", weekStart = "monday", colors = c("#9be9a8", "#40c463", "#30a14e", "#216e39"), valueLabel = "mm of rain", valueFormat = ",.1f", title = "Daily rainfall, 2021-2023" )
# Event log: one row per event, counted per day set.seed(7) events <- data.frame( when = as.Date("2025-01-01") + sample(0:364, 900, replace = TRUE) ) calendar_heatmap( data = events, date = "when", valueLabel = "orders", colors = c("#fde0c5", "#facba6", "#f59e72", "#e8603c", "#c0392b"), emptyCol = "#f2f2f2", cellRadius = 3, title = "Orders per day" )