{"spec_id":"line-load-duration","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nline-load-duration: Load Duration Curve for Energy Systems\nLibrary: plotly 6.8.0 | Python 3.13.13\nQuality: 86/100 | Updated: 2026-06-10\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\n\n\n# Theme tokens (Imprint palette — see prompts/default-style-guide.md)\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\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint palette — semantic assignment: green=base (always-on), blue=intermediate, red=peak\nCOLOR_BASE = \"#009E73\"  # Imprint pos 1 — stable, always-on base load\nCOLOR_INTERMEDIATE = \"#4467A3\"  # Imprint pos 3 — intermediate cycling load\nCOLOR_PEAK = \"#AE3030\"  # Imprint pos 5 — high-cost peak demand spikes\n\n# Data — synthetic annual hourly load profile for a mid-sized utility\nnp.random.seed(42)\nhours = np.arange(8760)\n\nhour_of_day = hours % 24\nday_of_year = hours // 24\n\nbase_load = 400\nseasonal = 200 * np.sin(2 * np.pi * (day_of_year - 30) / 365)\ndaily_cycle = 250 * np.sin(2 * np.pi * (hour_of_day - 6) / 24) + 150 * np.sin(4 * np.pi * (hour_of_day - 6) / 24)\npeak_factor = np.where(\n    (day_of_year > 150) & (day_of_year < 250) & (hour_of_day > 12) & (hour_of_day < 18),\n    np.random.uniform(100, 300, 8760),\n    0,\n)\nnoise = np.random.normal(0, 30, 8760)\n\nload_raw = base_load + seasonal + daily_cycle + peak_factor + noise + 400\nload_mw = np.sort(load_raw)[::-1]\nload_mw = np.clip(load_mw, 350, 1250)\n\n# Capacity tier thresholds\nbase_capacity = 550\nintermediate_capacity = 900\npeak_capacity = 1150\n\n# Hour indices where load crosses each threshold\npeak_hours = np.searchsorted(-load_mw, -peak_capacity)\nintermediate_hours = np.searchsorted(-load_mw, -intermediate_capacity)\n\n# Total energy (area under curve) in GWh\ntotal_energy_gwh = np.trapezoid(load_mw) / 1000\n\n# Plot\nfig = go.Figure()\n\n# Base load region (rightmost — always-on generation)\nfig.add_trace(\n    go.Scatter(\n        x=np.concatenate([hours, hours[::-1]]),\n        y=np.concatenate([np.minimum(load_mw, base_capacity), np.zeros(8760)]),\n        fill=\"toself\",\n        fillcolor=\"rgba(0,158,115,0.22)\",\n        line={\"width\": 0},\n        name=\"Base Load\",\n        hoverinfo=\"skip\",\n    )\n)\n\n# Intermediate load region (between base and intermediate capacity)\nintermediate_top = np.clip(load_mw, base_capacity, intermediate_capacity)\nfig.add_trace(\n    go.Scatter(\n        x=np.concatenate([hours, hours[::-1]]),\n        y=np.concatenate([intermediate_top, np.full(8760, base_capacity)]),\n        fill=\"toself\",\n        fillcolor=\"rgba(68,103,163,0.30)\",\n        line={\"width\": 0},\n        name=\"Intermediate Load\",\n        hoverinfo=\"skip\",\n    )\n)\n\n# Peak load region (leftmost — brief high-demand spikes)\npeak_top = np.maximum(load_mw, intermediate_capacity)\nfig.add_trace(\n    go.Scatter(\n        x=np.concatenate([hours, hours[::-1]]),\n        y=np.concatenate([peak_top, np.full(8760, intermediate_capacity)]),\n        fill=\"toself\",\n        fillcolor=\"rgba(174,48,48,0.32)\",\n        line={\"width\": 0},\n        name=\"Peak Load\",\n        hoverinfo=\"skip\",\n    )\n)\n\n# Main load duration curve\nfig.add_trace(\n    go.Scatter(\n        x=hours,\n        y=load_mw,\n        mode=\"lines\",\n        line={\"color\": INK, \"width\": 2.5},\n        name=\"Load Duration Curve\",\n        hovertemplate=\"<b>Hour %{x:,}</b><br>Load: %{y:.0f} MW<extra></extra>\",\n    )\n)\n\n# Horizontal dashed capacity tier lines — annotations on right to avoid y-axis crowding\nfor capacity, label, color in [\n    (peak_capacity, f\"Peak Capacity ({peak_capacity} MW)\", COLOR_PEAK),\n    (intermediate_capacity, f\"Intermediate ({intermediate_capacity} MW)\", COLOR_INTERMEDIATE),\n    (base_capacity, f\"Base Capacity ({base_capacity} MW)\", COLOR_BASE),\n]:\n    fig.add_hline(\n        y=capacity,\n        line_dash=\"dash\",\n        line_color=color,\n        line_width=1.5,\n        annotation_text=label,\n        annotation_position=\"top right\",\n        annotation_font={\"size\": 12, \"color\": color},\n    )\n\n# Region labels placed within each zone\nfig.add_annotation(\n    x=peak_hours // 2,\n    y=(peak_capacity + intermediate_capacity) // 2 + 40,\n    text=\"<b>Peak</b>\",\n    showarrow=False,\n    font={\"size\": 14, \"color\": COLOR_PEAK},\n)\n\nfig.add_annotation(\n    x=(peak_hours + intermediate_hours) // 2,\n    y=(intermediate_capacity + base_capacity) // 2 + 30,\n    text=\"<b>Intermediate</b>\",\n    showarrow=False,\n    font={\"size\": 14, \"color\": COLOR_INTERMEDIATE},\n)\n\nfig.add_annotation(\n    x=6500, y=base_capacity // 2 + 30, text=\"<b>Base Load</b>\", showarrow=False, font={\"size\": 14, \"color\": COLOR_BASE}\n)\n\n# Total energy annotation (mid-left area, clear of legend and capacity lines)\nfig.add_annotation(\n    x=3800,\n    y=680,\n    text=f\"Total Energy: {total_energy_gwh:,.0f} GWh/year\",\n    showarrow=False,\n    font={\"size\": 12, \"color\": INK_SOFT},\n    bordercolor=INK_SOFT,\n    borderwidth=1,\n    borderpad=5,\n    bgcolor=ELEVATED_BG,\n)\n\n# Layout\ntitle = \"line-load-duration · python · plotly · anyplot.ai\"\nfig.update_layout(\n    autosize=False,\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    title={\"text\": title, \"font\": {\"size\": 16, \"color\": INK}, \"x\": 0.5, \"xanchor\": \"center\"},\n    xaxis={\n        \"title\": {\"text\": \"Hours (ranked by load, descending)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"showgrid\": False,\n        \"range\": [0, 8760],\n        \"tickvals\": [0, 2000, 4000, 6000, 8000, 8760],\n        \"ticktext\": [\"0\", \"2,000\", \"4,000\", \"6,000\", \"8,000\", \"8,760\"],\n        \"linecolor\": INK_SOFT,\n        \"zerolinecolor\": INK_SOFT,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Power Demand (MW)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"showgrid\": True,\n        \"gridwidth\": 1,\n        \"gridcolor\": GRID,\n        \"linecolor\": INK_SOFT,\n        \"zerolinecolor\": INK_SOFT,\n        \"range\": [0, 1400],\n    },\n    legend={\n        \"x\": 0.75,\n        \"y\": 0.15,\n        \"font\": {\"size\": 10, \"color\": INK_SOFT},\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n    },\n    margin={\"l\": 80, \"r\": 60, \"t\": 80, \"b\": 60},\n    hovermode=\"x\",\n)\n\n# Save\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}