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