{"spec_id":"histogram-epidemic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nhistogram-epidemic: Epidemic Curve (Epi Curve)\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 87/100 | Updated: 2026-06-02\n\"\"\"\n\nimport os\n\nimport matplotlib.dates as mdates\nimport matplotlib.lines as mlines\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nANYPLOT_AMBER = \"#DDCC77\"\n\n# Imprint categorical palette — canonical order, first series always #009E73\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Data\nnp.random.seed(42)\ndates_range = pd.date_range(\"2024-01-15\", periods=120, freq=\"D\")\n\nconfirmed_base = np.concatenate(\n    [\n        np.linspace(2, 35, 30),\n        np.linspace(35, 80, 15),\n        np.linspace(80, 120, 10),\n        np.linspace(120, 60, 20),\n        np.linspace(60, 25, 15),\n        np.linspace(25, 45, 10),\n        np.linspace(45, 15, 20),\n    ]\n)\nconfirmed_counts = np.maximum(0, confirmed_base + np.random.normal(0, 8, 120)).astype(int)\nprobable_counts = np.maximum(0, confirmed_counts * 0.35 + np.random.normal(0, 3, 120)).astype(int)\nsuspect_counts = np.maximum(0, confirmed_counts * 0.20 + np.random.normal(0, 2, 120)).astype(int)\n\n# Long-form DataFrame for sns.histplot stacking\nrows = []\nfor i, date in enumerate(dates_range):\n    rows.extend([(date, \"Confirmed\")] * confirmed_counts[i])\n    rows.extend([(date, \"Probable\")] * probable_counts[i])\n    rows.extend([(date, \"Suspect\")] * suspect_counts[i])\ncases_df = pd.DataFrame(rows, columns=[\"onset_date\", \"case_type\"])\n\ndaily_totals = confirmed_counts + probable_counts + suspect_counts\ncumulative = np.cumsum(daily_totals)\n\n# Configure theme\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"grid.color\": INK,\n        \"grid.alpha\": 0.15,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\n# Plot — 16:9 landscape canvas (3200 × 1800 px)\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\n\n# Weekly bins (~17 bins for 120-day outbreak); spec recommends weekly for > 3 months\nbin_edges = mdates.date2num(pd.date_range(\"2024-01-14\", periods=19, freq=\"7D\"))\n\npalette = {\n    \"Confirmed\": IMPRINT_PALETTE[0],  # #009E73 green\n    \"Probable\": IMPRINT_PALETTE[1],  # #C475FD lavender\n    \"Suspect\": IMPRINT_PALETTE[2],  # #4467A3 blue\n}\n\nsns.histplot(\n    data=cases_df,\n    x=\"onset_date\",\n    hue=\"case_type\",\n    hue_order=[\"Confirmed\", \"Probable\", \"Suspect\"],\n    multiple=\"stack\",\n    palette=palette,\n    bins=bin_edges,\n    edgecolor=PAGE_BG,\n    linewidth=0.5,\n    legend=True,\n    ax=ax,\n)\n\ny_max = ax.get_ylim()[1]\n\n# Peak period — amber shading only, no text label (reduces visual competition)\nax.axvspan(pd.Timestamp(\"2024-02-25\"), pd.Timestamp(\"2024-03-25\"), alpha=0.13, color=ANYPLOT_AMBER, zorder=0)\n\n# Cumulative cases on secondary axis\nax2 = ax.twinx()\nax2.plot(dates_range, cumulative, color=INK_SOFT, linewidth=2.0, alpha=0.75, zorder=3)\nax2.set_ylabel(\"Cumulative Cases\", fontsize=10, color=INK_SOFT, labelpad=8)\nax2.tick_params(axis=\"y\", labelsize=8, colors=INK_SOFT)\nax2.spines[\"right\"].set_color(INK_SOFT)\nax2.spines[\"top\"].set_visible(False)\nax2.spines[\"left\"].set_visible(False)\nax2.spines[\"bottom\"].set_visible(False)\n\n# Intervention markers — staggered labels to avoid overlap with peak shading\nintervention_dates = [\n    (pd.Timestamp(\"2024-02-20\"), \"Travel\\nRestrictions\", 4),\n    (pd.Timestamp(\"2024-03-25\"), \"Vaccination\\nCampaign\", 4),\n]\nfor date, label, day_offset in intervention_dates:\n    ax.axvline(date, color=INK_MUTED, linewidth=1.4, linestyle=\"--\", alpha=0.85, zorder=5)\n    ax.annotate(\n        label,\n        xy=(date + pd.Timedelta(days=day_offset), y_max * 0.84),\n        fontsize=7,\n        fontweight=\"semibold\",\n        ha=\"center\",\n        va=\"top\",\n        color=INK,\n        bbox={\n            \"boxstyle\": \"round,pad=0.3\",\n            \"facecolor\": ELEVATED_BG,\n            \"edgecolor\": INK_SOFT,\n            \"linewidth\": 0.8,\n            \"alpha\": 0.92,\n        },\n    )\n\n# Style\ntitle = \"histogram-epidemic · python · seaborn · anyplot.ai\"\nax.set_title(title, fontsize=12, fontweight=\"medium\", color=INK, pad=10)\nax.set_xlabel(\"Date of Symptom Onset\", fontsize=10, color=INK)\nax.set_ylabel(\"New Cases (Weekly)\", fontsize=10, color=INK)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nax.xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=0, interval=2))\nax.xaxis.set_major_formatter(mdates.DateFormatter(\"%b %d\"))\nplt.setp(ax.get_xticklabels(), rotation=45, ha=\"right\")\n\nsns.despine(ax=ax)\nax.yaxis.grid(True, alpha=0.15, linewidth=0.8)\nax.set_axisbelow(True)\n\n# Combine legend: histogram series + cumulative line\nlegend = ax.get_legend()\nhandles = list(legend.legend_handles)\nlabels = [t.get_text() for t in legend.get_texts()]\nlegend.remove()\nhandles.append(mlines.Line2D([], [], color=INK_SOFT, linewidth=2.0, alpha=0.75))\nlabels.append(\"Cumulative Cases\")\nax.legend(\n    handles=handles, labels=labels, fontsize=8, loc=\"upper left\", framealpha=0.92, edgecolor=INK_SOFT, fancybox=False\n)\n\n# Margins: room for rotated x-tick labels at bottom, secondary axis label at right\nfig.subplots_adjust(bottom=0.20, right=0.87)\n\n# Save\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}