The same plot in 14 other libraries — Python: Altair, Bokeh, lets-plot, Matplotlib, Plotly, plotnine, Pygal, Seaborn; Julia: Makie.jl; JavaScript: Chart.js, D3.js, Apache ECharts, Highcharts, MUI X Charts. Compare all 15 side by side: Basic Calendar Heatmap in Python, R, Julia and JavaScript.
A calendar heatmap visualizes time-series data on a calendar grid, where each day is represented as a cell and color intensity indicates the value magnitude. The layout follows a calendar structure with days as cells, weekdays as rows, weeks as columns, and months as labeled sections along the top. This visualization excels at revealing daily patterns, seasonal trends, and temporal anomalies over extended time periods.

#' anyplot.ai
#' heatmap-calendar: Basic Calendar Heatmap
#' Library: ggplot2 3.5.1 | R 4.4.1
#' Quality: 93/100 | Created: 2026-07-23
library(ggplot2)
library(dplyr)
library(tibble)
library(ragg)
set.seed(42)
# --- Theme tokens ------------------------------------------------------------
THEME <- Sys.getenv("ANYPLOT_THEME", "light")
PAGE_BG <- if (THEME == "light") "#FAF8F1" else "#1A1A17"
ELEVATED_BG <- if (THEME == "light") "#FFFDF6" else "#242420"
INK <- if (THEME == "light") "#1A1A17" else "#F0EFE8"
INK_SOFT <- if (THEME == "light") "#4A4A44" else "#B8B7B0"
INK_MUTED <- if (THEME == "light") "#6B6A63" else "#A8A79F"
IMPRINT_PALETTE <- c("#009E73", "#C475FD", "#4467A3", "#BD8233",
"#AE3030", "#2ABCCD", "#954477", "#99B314")
# --- Data: one year of daily commit activity, one mini-calendar per month ---
dates <- seq(as.Date("2025-01-01"), as.Date("2025-12-31"), by = "day")
n <- length(dates)
weekday_num <- as.integer(format(dates, "%u")) # 1 = Mon ... 7 = Sun
seasonal_wave <- 1 + 0.5 * sin(2 * pi * seq_len(n) / n) # busier toward year-end
base_lambda <- ifelse(weekday_num %in% 6:7, 1.2, 6.0) * seasonal_wave
commits <- rpois(n, lambda = base_lambda)
# A handful of days have no recorded activity (tracker offline)
missing_idx <- sample.int(n, size = round(0.03 * n))
commits[missing_idx] <- NA_integer_
day_labels <- c("1" = "Mon", "2" = "Tue", "3" = "Wed", "4" = "Thu",
"5" = "Fri", "6" = "Sat", "7" = "Sun")
df <- tibble(
date = dates,
month_label = factor(format(dates, "%b"), levels = month.abb),
day_of_month = as.integer(format(dates, "%d")),
day_label = factor(day_labels[as.character(weekday_num)],
levels = rev(c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"))),
weekday_num = weekday_num,
commits = commits
)
# Week-of-month row index (1-indexed), aligned so day 1 lands on its weekday
month_offsets <- df %>%
filter(day_of_month == 1) %>%
transmute(month_label, offset = weekday_num - 1)
df <- df %>%
left_join(month_offsets, by = "month_label") %>%
mutate(week_in_month = (day_of_month - 1 + offset) %/% 7 + 1)
# --- Peak-activity callout: busiest month + single-day high ------------
month_totals <- df %>%
group_by(month_label) %>%
summarise(total = sum(commits, na.rm = TRUE), .groups = "drop")
peak_month_row <- month_totals %>% slice_max(total, n = 1, with_ties = FALSE)
peak_month <- peak_month_row$month_label
peak_panel <- tibble(month_label = peak_month)
peak_day <- df %>%
filter(!is.na(commits)) %>%
slice_max(commits, n = 1, with_ties = FALSE)
subtitle_str <- sprintf(
"Busiest month: %s (%d commits) · single-day high: %d on %s",
peak_month, peak_month_row$total, peak_day$commits, format(peak_day$date, "%b %d")
)
# --- Plot ---------------------------------------------------------------
title_str <- "heatmap-calendar · r · ggplot2 · anyplot.ai"
p <- ggplot(df, aes(x = week_in_month, y = day_label, fill = commits)) +
geom_rect(
data = peak_panel, aes(xmin = -Inf, xmax = Inf, ymin = -Inf, ymax = Inf),
inherit.aes = FALSE, fill = IMPRINT_PALETTE[1], alpha = 0.10
) +
geom_tile(color = PAGE_BG, linewidth = 0.9, width = 0.85, height = 0.8) +
geom_tile(
data = peak_day, aes(x = week_in_month, y = day_label),
inherit.aes = FALSE, fill = NA, color = INK, linewidth = 1.1,
width = 0.85, height = 0.8
) +
scale_fill_gradient(
low = IMPRINT_PALETTE[1], high = IMPRINT_PALETTE[3],
na.value = INK_MUTED, name = "Commits (count)",
guide = guide_colorbar(barwidth = 14, barheight = 0.6, ticks = FALSE)
) +
scale_x_continuous(breaks = NULL) +
facet_wrap(~month_label, ncol = 4) +
labs(title = title_str, subtitle = subtitle_str, x = NULL, y = NULL) +
theme_minimal(base_size = 8) +
theme(
aspect.ratio = 7 / 6,
plot.background = element_rect(fill = PAGE_BG, color = PAGE_BG),
panel.background = element_rect(fill = PAGE_BG, color = NA),
panel.grid = element_blank(),
panel.spacing = unit(1.1, "lines"),
axis.ticks = element_blank(),
axis.text.y = element_text(color = INK_SOFT, size = 8),
strip.background = element_rect(fill = ELEVATED_BG, color = NA),
strip.text = element_text(color = INK, size = 9, face = "bold"),
plot.title = element_text(color = INK, size = 13, face = "bold", hjust = 0),
plot.subtitle = element_text(color = INK_SOFT, size = 8.5, hjust = 0,
margin = margin(t = 2, b = 8)),
legend.position = "bottom",
legend.background = element_rect(fill = PAGE_BG, color = NA),
legend.text = element_text(color = INK_SOFT, size = 8),
legend.title = element_text(color = INK, size = 10),
plot.margin = margin(t = 12, r = 16, b = 10, l = 10)
)
# --- Save --------------------------------------------------------------
ggsave(
filename = sprintf("plot-%s.png", THEME),
plot = p,
device = ragg::agg_png,
width = 6,
height = 6,
units = "in",
dpi = 400
)
Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/heatmap-calendar/ggplot2/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.
{
"spec_id": "heatmap-calendar",
"language": "r",
"library": "ggplot2",
"page": "https://anyplot.ai/heatmap-calendar/r/ggplot2",
"hub": "https://anyplot.ai/heatmap-calendar",
"code_json": "https://api.anyplot.ai/specs/heatmap-calendar/ggplot2/code",
"spec_json": "https://api.anyplot.ai/specs/heatmap-calendar",
"render_light_png": "https://storage.googleapis.com/anyplot-images/plots/heatmap-calendar/r/ggplot2/plot-light.png",
"render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/heatmap-calendar/r/ggplot2/plot-dark.png",
"quality_score": 93.0,
"license": "MIT",
"guide": "https://anyplot.ai/llms.txt"
}Part of Basic Calendar Heatmap on anyplot.ai.