{"spec_id":"heatmap-calendar","library":"makie","language":"julia","code":"# anyplot.ai\n# heatmap-calendar: Basic Calendar Heatmap\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 89/100 | Created: 2026-07-24\n\nusing CairoMakie\nusing Colors\nusing Dates\nusing Random\n\nRandom.seed!(42)\n\n# --- Theme tokens -----------------------------------------------------------\nconst THEME       = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG     = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst INK         = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT    = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\n\n# Imprint sequential colormap (single-polarity) — brand green → blue.\n# Daily commit counts are single-polarity magnitude, so imprint_seq applies.\nconst IMPRINT_SEQ = cgrad([colorant\"#009E73\", colorant\"#4467A3\"])\n# Faint neutral for calendar cells outside the data range (missing days).\nconst EMPTY_CELL  = RGBAf(INK.r, INK.g, INK.b, 0.06)\n\n# --- Data -------------------------------------------------------------------\n# GitHub-style contribution graph: one year of daily commit counts.\nstart_date = Date(2025, 1, 1)\nend_date   = Date(2025, 12, 31)\nall_dates  = start_date:Day(1):end_date\n\ncommits_by_date = Dict{Date,Int}()\nfor d in all_dates\n    weekday_base = dayofweek(d) <= 5 ? 6.0 : 0.5             # weekdays busy, weekends quiet\n    yearly_trend = 10.0 * (dayofyear(d) / 365)               # activity grows over the year\n    seasonal     = 5.0 * sin(2π * (dayofyear(d) - 80) / 365) # spring/autumn peaks\n    count        = round(Int, max(0.0, weekday_base + yearly_trend + seasonal + randn() * 2.5))\n    rand() < 0.08 && (count = 0)                             # occasional rest day\n    commits_by_date[d] = count\nend\nvmax = maximum(values(commits_by_date))\n\n# Calendar grid geometry: columns = weeks, rows = weekdays (Mon..Sun).\nfirst_monday = start_date - Day(dayofweek(start_date) - 1)\nn_weeks      = div((end_date - first_monday).value, 7) + 1\n\ncell_rects  = Rect2f[]\ncell_colors = RGBAf[]\npad = 0.09\nfor week in 0:(n_weeks - 1), row in 1:7\n    day = first_monday + Day(week * 7 + (row - 1))\n    push!(cell_rects, Rect2f(week - 0.5 + pad, row - 0.5 + pad, 1 - 2pad, 1 - 2pad))\n    if start_date <= day <= end_date\n        push!(cell_colors, RGBAf(get(IMPRINT_SEQ, commits_by_date[day] / vmax)))\n    else\n        push!(cell_colors, EMPTY_CELL)\n    end\nend\n\n# Month labels centered over each month's mid-point week (shown along the top).\nmonth_positions = Float64[]\nmonth_labels    = String[]\nfor m in 1:12\n    mid = Date(2025, m, 15)\n    push!(month_positions, div((mid - first_monday).value, 7))\n    push!(month_labels, monthabbr(mid))\nend\n\n# --- Plot -------------------------------------------------------------------\nfig = Figure(\n    size            = (1600, 900),\n    fontsize        = 16,\n    backgroundcolor = PAGE_BG,\n    figure_padding  = (44, 44, 30, 30),\n)\n\n# Flexible spacer rows above (row 1) and below (row 4) the fixed calendar (row 2)\n# and legend (row 3) center the whole block, filling the 16:9 canvas evenly.\nBox(fig[1, 1]; color = :transparent, strokewidth = 0, tellheight = false)\n\nax = Axis(\n    fig[2, 1];\n    title              = \"heatmap-calendar · julia · makie · anyplot.ai\",\n    titlesize          = 26,\n    titlecolor         = INK,\n    titlegap           = 16,\n    backgroundcolor    = PAGE_BG,\n    yreversed          = true,                  # Monday at the top row\n    xaxisposition      = :top,\n    xticks             = (month_positions, month_labels),\n    yticks             = (1:7, [\"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\", \"Sun\"]),\n    xticklabelsize     = 19,\n    yticklabelsize     = 19,\n    xticklabelcolor    = INK_SOFT,\n    yticklabelcolor    = INK_SOFT,\n    xticksvisible      = false,\n    yticksvisible      = false,\n    xgridvisible       = false,\n    ygridvisible       = false,\n)\nhidespines!(ax)\n\npoly!(ax, cell_rects; color = cell_colors, strokewidth = 0)\n\nxlims!(ax, -0.7, n_weeks - 0.3)\nylims!(ax, 7.7, 0.3)\n\n# Sequential legend for value interpretation.\nColorbar(\n    fig[3, 1];\n    colormap       = IMPRINT_SEQ,\n    limits         = (0, vmax),\n    vertical       = false,\n    flipaxis       = false,\n    label          = \"Daily commits\",\n    labelcolor     = INK,\n    labelsize      = 18,\n    ticklabelcolor = INK_SOFT,\n    ticklabelsize  = 15,\n    tickcolor      = INK_SOFT,\n    width          = Relative(0.4),\n    height         = 18,\n)\n\nBox(fig[4, 1]; color = :transparent, strokewidth = 0, tellheight = false)\n\n# Fixed content-row heights (stretched, taller-than-wide cells) keep the calendar\n# and legend adjacent; the flexible spacers absorb the rest, so no empty mid-band.\nrowsize!(fig.layout, 2, Fixed(560))\nrowsize!(fig.layout, 3, Fixed(40))\nrowgap!(fig.layout, 16)\n\n# --- Save -------------------------------------------------------------------\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}