{"spec_id":"line-load-duration","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nline-load-duration: Load Duration Curve for Energy Systems\nLibrary: plotnine 0.15.5 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-06-10\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    annotate,\n    coord_cartesian,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_line,\n    geom_ribbon,\n    geom_segment,\n    ggplot,\n    guide_legend,\n    labs,\n    scale_fill_manual,\n    scale_x_continuous,\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette — semantic assignment for energy load regions\n# Base Load (always-on, sustainable): position 1 — #009E73 (brand green)\n# Intermediate (mid-merit, earth/energy): position 4 — #BD8233 (ochre)\n# Peak (urgent demand): position 5 — #AE3030 (matte red, semantic for critical)\nREGION_COLORS = {\"Base Load\": \"#009E73\", \"Intermediate\": \"#BD8233\", \"Peak\": \"#AE3030\"}\n\n# ── Data ──────────────────────────────────────────────────────────────────────\nnp.random.seed(42)\nHOURS_IN_YEAR = 8760\nBASE_LOAD = 400\nPEAK_LOAD = 1200\n\nraw_load = np.zeros(HOURS_IN_YEAR)\nfor i in range(HOURS_IN_YEAR):\n    hour_of_day = i % 24\n    day_of_year = i // 24\n    seasonal = 80 * np.sin(2 * np.pi * (day_of_year - 30) / 365)\n    daily = 120 * np.sin(np.pi * (hour_of_day - 6) / 18) if 6 <= hour_of_day <= 24 else -60\n    noise = np.random.normal(0, 30)\n    raw_load[i] = 700 + seasonal + daily + noise\n\n# Scale to the declared range so peak reaches exactly 1200 MW\nraw_load = BASE_LOAD + (raw_load - raw_load.min()) / (raw_load.max() - raw_load.min()) * (PEAK_LOAD - BASE_LOAD)\nload_sorted = np.sort(raw_load)[::-1]\n\nBASE_CAPACITY = 500\nINTER_CAPACITY = 800\n\ntotal_energy_gwh = np.trapezoid(load_sorted) / 1000\npeak_hours = int((load_sorted > INTER_CAPACITY).sum())\n\nhours = np.arange(HOURS_IN_YEAR)\n\ndf_regions = pd.concat(\n    [\n        pd.DataFrame(\n            {\"hour\": hours, \"ymin\": 0.0, \"ymax\": np.minimum(load_sorted, BASE_CAPACITY), \"region\": \"Base Load\"}\n        ),\n        pd.DataFrame(\n            {\n                \"hour\": hours,\n                \"ymin\": float(BASE_CAPACITY),\n                \"ymax\": np.clip(load_sorted, BASE_CAPACITY, INTER_CAPACITY),\n                \"region\": \"Intermediate\",\n            }\n        ),\n        pd.DataFrame(\n            {\n                \"hour\": hours,\n                \"ymin\": float(INTER_CAPACITY),\n                \"ymax\": np.where(load_sorted > INTER_CAPACITY, load_sorted, float(INTER_CAPACITY)),\n                \"region\": \"Peak\",\n            }\n        ),\n    ],\n    ignore_index=True,\n)\ndf_regions[\"region\"] = pd.Categorical(\n    df_regions[\"region\"], categories=[\"Peak\", \"Intermediate\", \"Base Load\"], ordered=True\n)\n\ndf_line = pd.DataFrame({\"hour\": hours, \"load_mw\": load_sorted})\n\ndf_segments = pd.DataFrame(\n    {\n        \"x\": [0, 0],\n        \"xend\": [HOURS_IN_YEAR, HOURS_IN_YEAR],\n        \"y\": [BASE_CAPACITY, INTER_CAPACITY],\n        \"yend\": [BASE_CAPACITY, INTER_CAPACITY],\n    }\n)\n\n# ── Plot ──────────────────────────────────────────────────────────────────────\nplot = (\n    ggplot()\n    + geom_ribbon(data=df_regions, mapping=aes(x=\"hour\", ymin=\"ymin\", ymax=\"ymax\", fill=\"region\"), alpha=0.5)\n    + geom_segment(\n        data=df_segments,\n        mapping=aes(x=\"x\", xend=\"xend\", y=\"y\", yend=\"yend\"),\n        linetype=\"dashed\",\n        color=INK_SOFT,\n        size=0.5,\n        alpha=0.8,\n    )\n    + geom_line(data=df_line, mapping=aes(x=\"hour\", y=\"load_mw\"), color=INK, size=1.0)\n    + annotate(\n        \"label\",\n        x=HOURS_IN_YEAR * 0.82,\n        y=BASE_CAPACITY,\n        label=f\"Base Capacity — {BASE_CAPACITY} MW\",\n        size=3.0,\n        ha=\"center\",\n        color=INK_SOFT,\n        fill=ELEVATED_BG,\n        alpha=0.92,\n        fontweight=\"bold\",\n        label_padding=0.3,\n    )\n    + annotate(\n        \"label\",\n        x=HOURS_IN_YEAR * 0.82,\n        y=INTER_CAPACITY,\n        label=f\"Intermediate Capacity — {INTER_CAPACITY} MW\",\n        size=3.0,\n        ha=\"center\",\n        color=INK_SOFT,\n        fill=ELEVATED_BG,\n        alpha=0.92,\n        fontweight=\"bold\",\n        label_padding=0.3,\n    )\n    + annotate(\n        \"text\",\n        x=peak_hours * 0.45,\n        y=INTER_CAPACITY + 90,\n        label=\"Peak\",\n        size=3.5,\n        ha=\"center\",\n        color=\"#AE3030\",\n        fontweight=\"bold\",\n        fontstyle=\"italic\",\n    )\n    + annotate(\n        \"text\",\n        x=HOURS_IN_YEAR * 0.35,\n        y=(BASE_CAPACITY + INTER_CAPACITY) / 2,\n        label=\"Intermediate\",\n        size=3.5,\n        ha=\"center\",\n        color=\"#BD8233\",\n        fontweight=\"bold\",\n        fontstyle=\"italic\",\n    )\n    + annotate(\n        \"text\",\n        x=HOURS_IN_YEAR * 0.55,\n        y=BASE_CAPACITY * 0.45,\n        label=\"Base Load\",\n        size=3.5,\n        ha=\"center\",\n        color=\"#009E73\",\n        fontweight=\"bold\",\n        fontstyle=\"italic\",\n    )\n    + annotate(\n        \"label\",\n        x=HOURS_IN_YEAR * 0.72,\n        y=PEAK_LOAD - 60,\n        label=f\"Total Energy: {total_energy_gwh:,.0f} GWh\",\n        size=3.0,\n        ha=\"center\",\n        color=INK,\n        fill=ELEVATED_BG,\n        alpha=0.92,\n        fontweight=\"bold\",\n        label_padding=0.4,\n    )\n    + scale_fill_manual(\n        values=REGION_COLORS, guide=guide_legend(title=\"Load Region\", override_aes={\"alpha\": 0.7}, nrow=1)\n    )\n    + scale_x_continuous(\n        breaks=[0, 2000, 4000, 6000, 8000], labels=[\"0\", \"2,000\", \"4,000\", \"6,000\", \"8,000\"], expand=(0.02, 0)\n    )\n    + scale_y_continuous(breaks=[0, 200, 400, 600, 800, 1000, 1200], expand=(0.02, 0))\n    + coord_cartesian(ylim=(0, PEAK_LOAD + 100))\n    + labs(x=\"Hours\", y=\"Load (MW)\", title=\"line-load-duration · python · plotnine · anyplot.ai\")\n    + theme_minimal()\n    + theme(\n        figure_size=(8, 4.5),\n        text=element_text(size=7, color=INK),\n        axis_title=element_text(size=10, weight=\"bold\", color=INK),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        plot_title=element_text(size=12, weight=\"bold\", color=INK),\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_grid_major_y=element_line(color=INK, size=0.3, alpha=0.15),\n        panel_background=element_rect(fill=PAGE_BG),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        legend_position=\"bottom\",\n        legend_title=element_text(size=9, weight=\"bold\", color=INK),\n        legend_text=element_text(size=8, color=INK_SOFT),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        legend_key_size=12,\n        plot_margin=0.03,\n    )\n)\n\n# ── Save ──────────────────────────────────────────────────────────────────────\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}