{"spec_id":"histogram-epidemic","library":"makie","language":"julia","code":"# anyplot.ai\n# histogram-epidemic: Epidemic Curve (Epi Curve)\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 91/100 | Created: 2026-06-02\n\nusing CairoMakie\nusing Colors\nusing Random\nusing Dates\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 ELEVATED_BG = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\nconst INK         = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT    = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nconst INK_MUTED   = THEME == \"light\" ? colorant\"#6B6A63\" : colorant\"#A8A79F\"\n\n# Imprint palette — confirmed: pos 1 (brand green), probable: pos 2 (lavender)\nconst BRAND    = colorant\"#009E73\"\nconst LAVENDER = colorant\"#C475FD\"\nconst AMBER    = colorant\"#DDCC77\"   # semantic anchor — intervention events\n\n# Data — 90-day influenza-like outbreak, Oct–Dec 2023\nn_days = 90\nstart_date = Date(2023, 10, 1)\n\nwave1 = [180.0 * exp(-0.5 * ((d - 22)^2) / 72.0) for d in 1:n_days]\nwave2 = [55.0 * exp(-0.5 * ((d - 62)^2) / 45.0) for d in 1:n_days]\nnoise = randn(n_days) .* 4.0\ntotal_cases = max.(0, round.(Int, wave1 .+ wave2 .+ noise))\n\nconfirmed  = round.(Int, total_cases .* 0.72)\nprobable   = total_cases .- confirmed\ncumulative = cumsum(total_cases)\nmax_daily  = maximum(total_cases)\n\n# Stacked barplot vectors (confirmed = stack 1 bottom, probable = stack 2 top)\nx_stacked    = vcat(1:n_days, 1:n_days)\ny_stacked    = vcat(Float64.(confirmed), Float64.(probable))\nstack_groups = vcat(fill(1, n_days), fill(2, n_days))\nbar_colors   = vcat(fill(BRAND, n_days), fill(LAVENDER, n_days))\n\n# X-axis ticks — fortnightly + endpoints\ntick_positions = [1, 15, 30, 45, 60, 75, 90]\ntick_labels    = [Dates.format(start_date + Day(d - 1), \"d u\") for d in tick_positions]\n\n# Intervention events (day, label)\ninterventions = [(25, \"School closure\"), (57, \"Vaccination campaign\")]\n\n# Figure\nfig = Figure(\n    size            = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\n# Primary axis: daily case counts (left y-axis)\nax = Axis(\n    fig[1, 1];\n    title              = \"histogram-epidemic · julia · makie · anyplot.ai\",\n    titlesize          = 20,\n    titlecolor         = INK,\n    xlabel             = \"Date of symptom onset\",\n    ylabel             = \"Daily new cases\",\n    xlabelsize         = 14,\n    ylabelsize         = 14,\n    xlabelcolor        = INK,\n    ylabelcolor        = INK,\n    xticklabelsize     = 11,\n    yticklabelsize     = 11,\n    xticklabelcolor    = INK_SOFT,\n    yticklabelcolor    = INK_SOFT,\n    xtickcolor         = INK_SOFT,\n    ytickcolor         = INK_SOFT,\n    backgroundcolor    = PAGE_BG,\n    topspinevisible    = false,\n    rightspinevisible  = false,\n    leftspinecolor     = INK_SOFT,\n    bottomspinecolor   = INK_SOFT,\n    xgridvisible       = false,\n    ygridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.15),\n    xminorgridvisible  = false,\n    yminorgridvisible  = false,\n    xticks             = (tick_positions, tick_labels),\n    xticklabelrotation = π / 6,\n    xticklabelalign    = (:right, :center),\n)\n\n# Secondary axis: cumulative cases (right y-axis)\nax2 = Axis(\n    fig[1, 1];\n    yaxisposition      = :right,\n    ylabel             = \"Cumulative cases\",\n    ylabelsize         = 14,\n    ylabelcolor        = INK_MUTED,\n    yticklabelsize     = 11,\n    yticklabelcolor    = INK_MUTED,\n    ytickcolor         = INK_MUTED,\n    rightspinecolor    = INK_MUTED,\n    backgroundcolor    = :transparent,\n    topspinevisible    = false,\n    leftspinevisible   = false,\n    bottomspinevisible = false,\n    xgridvisible       = false,\n    ygridvisible       = false,\n    xminorgridvisible  = false,\n    yminorgridvisible  = false,\n)\nhidexdecorations!(ax2)\nlinkxaxes!(ax, ax2)\n\n# Stacked bars (epi curve histogram)\nbarplot!(ax, x_stacked, y_stacked;\n    stack       = stack_groups,\n    color       = bar_colors,\n    strokewidth = 0,\n    width       = 1.0,\n)\n\n# Cumulative line on secondary axis\nlines!(ax2, 1:n_days, Float64.(cumulative);\n    color     = INK_MUTED,\n    linewidth = 2.0,\n    linestyle = :dash,\n)\n\n# Intervention events — vertical lines with labels\nfor (day, label) in interventions\n    vlines!(ax, [Float64(day)]; color = AMBER, linewidth = 1.5, linestyle = :dash)\n    text!(ax, day + 1, max_daily * 0.88;\n        text    = label,\n        fontsize = 10,\n        color   = AMBER,\n        align   = (:left, :center),\n    )\nend\n\n# Axis limits\nylims!(ax, 0, nothing)\nylims!(ax2, 0, nothing)\nxlims!(ax, 0.5, n_days + 0.5)\n\n# Legend\nlegend_entries = [\n    PolyElement(color = BRAND, strokewidth = 0),\n    PolyElement(color = LAVENDER, strokewidth = 0),\n    LineElement(color = INK_MUTED, linewidth = 2.0, linestyle = :dash),\n    LineElement(color = AMBER, linewidth = 1.5, linestyle = :dash),\n]\nlegend_labels = [\"Confirmed\", \"Probable\", \"Cumulative cases\", \"Intervention\"]\n\nLegend(\n    fig[1, 2],\n    legend_entries,\n    legend_labels;\n    framecolor      = INK_SOFT,\n    framevisible    = true,\n    backgroundcolor = ELEVATED_BG,\n    labelsize       = 11,\n    labelcolor      = INK,\n    padding         = (10, 10, 10, 10),\n)\n\ncolsize!(fig.layout, 1, Relative(0.85))\n\n# Save\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}