{"spec_id":"area-stacked-confidence","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\narea-stacked-confidence: Stacked Area Chart with Confidence Bands\nLibrary: letsplot 4.9.0 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-05-18\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import *\n\n\nLetsPlot.setup_html()\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# Okabe-Ito palette (first series always #009E73)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\"]\n\n# Data - Quarterly energy consumption forecast by source with uncertainty\nnp.random.seed(42)\nquarters = pd.date_range(\"2023-Q1\", periods=24, freq=\"QE\")\nn = len(quarters)\n\n# Base trends for 3 energy sources (TWh)\nsolar_base = np.linspace(50, 120, n) + np.random.normal(0, 5, n)\nwind_base = np.linspace(80, 150, n) + np.random.normal(0, 8, n)\nhydro_base = np.linspace(100, 110, n) + np.random.normal(0, 3, n)\n\n# Confidence intervals (uncertainty grows over time for forecasts)\ntime_factor = np.linspace(1, 2.5, n)\nsolar_lower = solar_base - 8 * time_factor\nsolar_upper = solar_base + 8 * time_factor\nwind_lower = wind_base - 12 * time_factor\nwind_upper = wind_base + 12 * time_factor\nhydro_lower = hydro_base - 5 * time_factor\nhydro_upper = hydro_base + 5 * time_factor\n\n# Create stacked values (cumulative)\nsolar_stack = solar_base\nwind_stack = solar_base + wind_base\nhydro_stack = solar_base + wind_base + hydro_base\n\n# Stacked confidence bounds\nsolar_lower_stack = solar_lower\nsolar_upper_stack = solar_upper\nwind_lower_stack = solar_base + wind_lower\nwind_upper_stack = solar_base + wind_upper\nhydro_lower_stack = solar_base + wind_base + hydro_lower\nhydro_upper_stack = solar_base + wind_base + hydro_upper\n\n# Convert dates to numeric for lets-plot\nx_numeric = np.arange(n)\nx_labels = [f\"{q.year}-Q{(q.month - 1) // 3 + 1}\" for q in quarters]\n\n# Central line data\ndf_lines = pd.DataFrame(\n    {\n        \"x\": np.tile(x_numeric, 3),\n        \"y\": np.concatenate([solar_stack, wind_stack, hydro_stack]),\n        \"source\": np.concatenate([[\"Solar\"] * n, [\"Wind\"] * n, [\"Hydro\"] * n]),\n    }\n)\n\n# Main areas data (for stacked area)\ndf_areas = pd.DataFrame(\n    {\n        \"x\": np.tile(x_numeric, 3),\n        \"y_min\": np.concatenate([np.zeros(n), solar_stack, wind_stack]),\n        \"y_max\": np.concatenate([solar_stack, wind_stack, hydro_stack]),\n        \"source\": np.concatenate([[\"Solar\"] * n, [\"Wind\"] * n, [\"Hydro\"] * n]),\n    }\n)\n\n# Confidence band data\ndf_conf = pd.DataFrame(\n    {\n        \"x\": np.tile(x_numeric, 3),\n        \"y_lower\": np.concatenate([solar_lower_stack, wind_lower_stack, hydro_lower_stack]),\n        \"y_upper\": np.concatenate([solar_upper_stack, wind_upper_stack, hydro_upper_stack]),\n        \"source\": np.concatenate([[\"Solar\"] * n, [\"Wind\"] * n, [\"Hydro\"] * n]),\n    }\n)\n\n# Theme-adaptive plot styling\nanyplot_theme = theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_grid_major_y=element_line(color=INK, size=0.3),\n    panel_grid_minor=element_blank(),\n    axis_title=element_text(color=INK, size=20),\n    axis_text=element_text(color=INK_SOFT, size=16),\n    axis_line=element_line(color=INK_SOFT, size=0.4),\n    plot_title=element_text(color=INK, size=24),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_text=element_text(color=INK_SOFT, size=16),\n    legend_title=element_text(color=INK, size=18),\n    legend_position=\"right\",\n)\n\n# Create plot with stacked areas and confidence ribbons\nplot = (\n    ggplot()\n    + geom_ribbon(aes(x=\"x\", ymin=\"y_lower\", ymax=\"y_upper\", fill=\"source\"), data=df_conf, alpha=0.25)\n    + geom_ribbon(aes(x=\"x\", ymin=\"y_min\", ymax=\"y_max\", fill=\"source\"), data=df_areas, alpha=0.7)\n    + geom_line(aes(x=\"x\", y=\"y\", color=\"source\"), data=df_lines, size=1.5)\n    + scale_fill_manual(values=IMPRINT, name=\"Energy Source\")\n    + scale_color_manual(values=IMPRINT, guide=\"none\")\n    + scale_x_continuous(breaks=list(range(0, n, 4)), labels=[x_labels[i] for i in range(0, n, 4)])\n    + labs(\n        title=\"area-stacked-confidence · Python · letsplot · anyplot.ai\",\n        x=\"Quarter\",\n        y=\"Energy Consumption (TWh)\",\n        caption=\"Shaded bands show 90% prediction intervals\",\n    )\n    + theme_minimal()\n    + anyplot_theme\n    + ggsize(1600, 900)\n)\n\n# Save PNG (scale 3x for 4800x2700)\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=3)\n\n# Save HTML for interactive viewing\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}