{"spec_id":"area-stacked","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\narea-stacked: Stacked Area Chart\nLibrary: plotnine 0.15.8 | Python 3.13.15\nQuality: 91/100 | Updated: 2026-08-17\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom mizani.formatters import comma_format\nfrom plotnine import (\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_area,\n    geom_line,\n    geom_text,\n    ggplot,\n    labs,\n    position_stack,\n    scale_fill_manual,\n    scale_x_date,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\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\"\n\n# Data: website traffic sources over 24 months, in thousands of visitors\nnp.random.seed(42)\n\ndates = pd.date_range(start=\"2023-01-01\", periods=24, freq=\"MS\")\n\n# Generate realistic traffic data with trends\nbase_direct = 15000 + np.cumsum(np.random.randn(24) * 500)\nbase_organic = 25000 + np.cumsum(np.random.randn(24) * 800) + np.arange(24) * 300\nbase_referral = 10000 + np.cumsum(np.random.randn(24) * 400)\nbase_social = 8000 + np.cumsum(np.random.randn(24) * 600) + np.arange(24) * 200\n\n# Ensure all values are positive, then rescale to thousands for compact axis labels\ndirect = np.maximum(base_direct, 5000) / 1000\norganic = np.maximum(base_organic, 10000) / 1000\nreferral = np.maximum(base_referral, 3000) / 1000\nsocial = np.maximum(base_social, 2000) / 1000\n\n# Create long-format DataFrame for stacking\ndf = pd.DataFrame(\n    {\n        \"Date\": np.tile(dates, 4),\n        \"Visitors\": np.concatenate([organic, direct, referral, social]),\n        \"Source\": ([\"Organic Search\"] * 24 + [\"Direct\"] * 24 + [\"Referral\"] * 24 + [\"Social Media\"] * 24),\n    }\n)\n\n# Order categories by average size (largest at bottom for easier reading)\nsource_order = [\"Organic Search\", \"Direct\", \"Referral\", \"Social Media\"]\ndf[\"Source\"] = pd.Categorical(df[\"Source\"], categories=source_order, ordered=True)\n\n# Running total across sources, overlaid as a dashed trend line so the\n# cumulative-traffic story the spec calls out isn't left implicit in the stack\ntotals = df.groupby(\"Date\", observed=True)[\"Visitors\"].sum().reset_index()\nlast_total = totals.iloc[[-1]].copy()\nlast_total[\"label\"] = \"Total\"\n\n# Imprint palette\ncolors = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Theme\nanyplot_theme = theme(\n    figure_size=(8, 4.5),\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_grid_major_x=element_blank(),\n    panel_grid_major_y=element_line(color=INK, size=0.3, alpha=0.12),\n    panel_grid_minor=element_blank(),\n    plot_title=element_text(size=12, weight=\"bold\", color=INK),\n    axis_title=element_text(size=10, color=INK),\n    axis_text=element_text(size=8, color=INK_SOFT),\n    axis_text_x=element_text(angle=45, hjust=1, margin={\"t\": 6, \"unit\": \"pt\"}),\n    legend_background=element_rect(fill=ELEVATED_BG, color=None),\n    legend_text=element_text(size=8, color=INK_SOFT),\n    legend_title=element_text(size=9, color=INK),\n)\n\n# Create stacked area chart: a subtle ink outline on each band's upper edge\n# (outline_type) plus a dashed total-traffic overlay for the cumulative read.\n# position_stack(reverse=True) puts the first factor level (Organic Search,\n# the largest series) at the bottom of the stack, matching source_order and\n# the spec's \"largest at bottom\" size ordering.\nplot = (\n    ggplot(df, aes(x=\"Date\", y=\"Visitors\", fill=\"Source\"))\n    + geom_area(alpha=0.85, outline_type=\"upper\", color=INK_SOFT, size=0.35, position=position_stack(reverse=True))\n    + geom_line(\n        totals, aes(x=\"Date\", y=\"Visitors\"), color=INK, linetype=\"dashed\", size=0.8, alpha=0.8, inherit_aes=False\n    )\n    + geom_text(\n        last_total,\n        aes(x=\"Date\", y=\"Visitors\", label=\"label\"),\n        color=INK,\n        size=7,\n        ha=\"left\",\n        nudge_x=10,\n        inherit_aes=False,\n    )\n    + scale_fill_manual(values=colors)\n    + scale_x_date(date_labels=\"%b %Y\", date_breaks=\"3 months\", expand=(0.02, 12, 0.02, 40))\n    + scale_y_continuous(labels=comma_format(), expand=(0, 0, 0.08, 0))\n    + labs(\n        title=\"area-stacked · python · plotnine · anyplot.ai\",\n        x=\"Month\",\n        y=\"Monthly Visitors (thousands)\",\n        fill=\"Traffic Source\",\n    )\n    + theme_minimal()\n    + anyplot_theme\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}