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,POSIXctor 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") whenvalueisNULL.- 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 rampc("#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 thevaluecolumn, or"events"whenvalueisNULL.- 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.
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"
)