{"spec_id":"area-stacked","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\narea-stacked: Stacked Area Chart\nLibrary: letsplot 4.11.0 | 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 lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_area,\n    geom_text,\n    geom_vline,\n    ggplot,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_fill_manual,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n)\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\n\n# Theme tokens (see prompts/default-style-guide.md)\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nRULE = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint palette (first series always #009E73)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\nAMBER = \"#DDCC77\"  # semantic anchor for the campaign-launch event marker\n\n# Data: monthly website visits by acquisition channel, Jan 2023 - Aug 2024\nnp.random.seed(42)\nn_months = 20\nmonths = pd.date_range(\"2023-01\", periods=n_months, freq=\"ME\")\nt = np.arange(n_months)\n\n# Organic search: steady, largest channel with mild seasonality\norganic = 95 + 8 * np.sin(np.linspace(0, 3 * np.pi, n_months)) + np.cumsum(np.random.randn(n_months) * 2)\norganic = np.maximum(organic, 60)\n\n# Direct: stable, slowly tapering as other channels grow\ndirect = 55 - 0.4 * t + np.cumsum(np.random.randn(n_months) * 1.2)\ndirect = np.maximum(direct, 30)\n\n# Social media: a paid campaign launches at month 13, sharply accelerating growth\ncampaign_start = 13\nsocial_pre = 18 + 0.4 * t\nsocial_post = 18 + 0.4 * campaign_start + (t - campaign_start) * 4.5\nsocial = np.where(t < campaign_start, social_pre, social_post) + np.cumsum(np.random.randn(n_months) * 1.5)\nsocial = np.maximum(social, 12)\n\n# Referral: small, flat channel\nreferral = 14 + np.cumsum(np.random.randn(n_months) * 0.8)\nreferral = np.maximum(referral, 6)\n\n# Create long-format dataframe for lets-plot\ndf = pd.DataFrame(\n    {\n        \"MonthNum\": np.tile(t, 4),\n        \"Visits\": np.concatenate([organic, direct, social, referral]),\n        \"Channel\": [\"Organic Search\"] * n_months\n        + [\"Direct\"] * n_months\n        + [\"Social Media\"] * n_months\n        + [\"Referral\"] * n_months,\n    }\n)\n\n# Reorder channels for stacking (largest at bottom)\nchannel_order = [\"Organic Search\", \"Direct\", \"Social Media\", \"Referral\"]\ndf[\"Channel\"] = pd.Categorical(df[\"Channel\"], categories=channel_order, ordered=True)\n\n# Annotate the story: where the paid social campaign kicks off growth\nstack_top_at_launch = (\n    organic[campaign_start] + direct[campaign_start] + social[campaign_start] + referral[campaign_start]\n)\ncallout_y = stack_top_at_launch + 18\n\n# Richer tooltip: bolded channel title plus formatted visits (lets-plot-distinctive\n# interactive feature, beyond a generic ggplot2-style port)\narea_tooltips = layer_tooltips().title(\"@Channel\").format(\"@Visits\", \".0f\").line(\"Visits|@Visits k\")\n\n# Create stacked area chart\nplot = (\n    ggplot(df, aes(x=\"MonthNum\", y=\"Visits\", fill=\"Channel\"))\n    + geom_area(alpha=0.85, position=\"stack\", size=0.5, color=PAGE_BG, tooltips=area_tooltips)\n    + geom_vline(xintercept=campaign_start, linetype=\"dashed\", color=AMBER, size=0.8, alpha=0.9)\n    + geom_text(\n        x=campaign_start, y=callout_y, label=\"Paid social campaign launch\", size=4.1, color=INK, hjust=0, nudge_x=0.4\n    )\n    + scale_fill_manual(values=IMPRINT)\n    + scale_x_continuous(name=\"Month\", breaks=[0, 6, 12, 19], labels=[\"Jan 2023\", \"Jul 2023\", \"Jan 2024\", \"Aug 2024\"])\n    + scale_y_continuous(name=\"Website Visits (Thousands)\", format=\",d\")\n    + labs(title=\"area-stacked · python · letsplot · anyplot.ai\", fill=\"Acquisition Channel\")\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_border=element_blank(),\n        panel_grid_major_x=element_blank(),\n        panel_grid_major_y=element_line(color=RULE, size=0.5),\n        panel_grid_minor=element_blank(),\n        plot_title=element_text(size=16, face=\"bold\", color=INK),\n        axis_title=element_text(size=12, color=INK),\n        axis_text=element_text(size=10, color=INK_SOFT),\n        axis_line_x=element_line(color=INK_SOFT),\n        axis_line_y=element_line(color=INK_SOFT),\n        legend_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        legend_title=element_text(size=12, color=INK),\n        legend_text=element_text(size=10, color=INK_SOFT),\n        legend_position=\"right\",\n    )\n    + ggsize(800, 450)\n)\n\n# Save as PNG (scale 4x for 3200x1800 px)\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\n\n# Save interactive HTML version\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}