{"spec_id":"line-load-duration","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nline-load-duration: Load Duration Curve for Energy Systems\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-06-10\n\"\"\"\n\nimport os\n\nimport matplotlib.patheffects as pe\nimport matplotlib.pyplot as plt\nimport matplotlib.ticker as ticker\nimport numpy as np\nfrom matplotlib.patches import Patch\n\n\n# Theme\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 assignments for load regions\nCOLOR_PEAK = \"#AE3030\"  # Imprint matte red — peak / critical load\nCOLOR_INTER = \"#DDCC77\"  # Imprint amber anchor — intermediate / caution\nCOLOR_BASE = \"#4467A3\"  # Imprint blue — base load\n\n# Data\nnp.random.seed(42)\nhours = 8760\nbase_load = 400\npeak_load = 1200\n\nhourly_load = np.concatenate(\n    [\n        np.random.normal(1100, 60, int(hours * 0.05)),\n        np.random.normal(900, 80, int(hours * 0.15)),\n        np.random.normal(750, 70, int(hours * 0.30)),\n        np.random.normal(600, 50, int(hours * 0.30)),\n        np.random.normal(480, 30, int(hours * 0.20)),\n    ]\n)\nhourly_load = np.clip(hourly_load, base_load, peak_load)\nextra = hours - len(hourly_load)\nif extra > 0:\n    hourly_load = np.concatenate([hourly_load, np.random.normal(500, 40, extra)])\nhourly_load = hourly_load[:hours]\nload_mw = np.sort(hourly_load)[::-1]\nhour = np.arange(hours)\n\npeak_threshold = 950\nintermediate_threshold = 600\n\npeak_end = np.searchsorted(-load_mw, -peak_threshold)\nbase_start = np.searchsorted(-load_mw, -intermediate_threshold)\ntotal_energy_gwh = np.trapezoid(load_mw, hour) / 1000\n\n# Plot\ntitle = \"line-load-duration · python · matplotlib · anyplot.ai\"\ntitle_n = len(title)\ntitle_fontsize = max(8, round(12 * 67 / title_n)) if title_n > 67 else 12\n\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Region fills using Imprint semantic palette — alpha boosted in dark theme for visual impact\nfill_alpha = 0.18 if THEME == \"light\" else 0.32\nfill_alpha_inter = 0.22 if THEME == \"light\" else 0.35\nax.fill_between(\n    hour[: peak_end + 1], load_mw[: peak_end + 1], base_load - 30, color=COLOR_PEAK, alpha=fill_alpha, zorder=2\n)\nax.fill_between(\n    hour[peak_end : base_start + 1],\n    load_mw[peak_end : base_start + 1],\n    base_load - 30,\n    color=COLOR_INTER,\n    alpha=fill_alpha_inter,\n    zorder=2,\n)\nax.fill_between(hour[base_start:], load_mw[base_start:], base_load - 30, color=COLOR_BASE, alpha=fill_alpha, zorder=2)\n\n# Main load duration curve\nax.plot(hour, load_mw, color=INK, linewidth=2.5, zorder=5)\n\n# Capacity tier dashed lines — staggered x-positions to prevent label crowding\ntier_props = [\n    (peak_threshold, COLOR_PEAK, \"Peak Capacity\", 0.62),\n    (intermediate_threshold, COLOR_INTER, \"Intermediate Capacity\", 0.52),\n    (base_load, COLOR_BASE, \"Base Capacity\", 0.42),\n]\nfor y_val, color, label, x_frac in tier_props:\n    ax.axhline(y=y_val, color=color, linestyle=\"--\", linewidth=0.9, alpha=0.6, zorder=3)\n    ax.text(\n        hours * x_frac,\n        y_val + 12,\n        f\"{label}  {y_val:,} MW\",\n        fontsize=8,\n        color=color,\n        fontweight=\"semibold\",\n        path_effects=[pe.withStroke(linewidth=2.5, foreground=PAGE_BG)],\n        zorder=6,\n    )\n\n# Region labels\nregion_labels = [\n    (peak_end * 0.45, peak_threshold + 65, \"PEAK\\nLOAD\", COLOR_PEAK),\n    (\n        (peak_end + base_start) / 2,\n        (peak_threshold + intermediate_threshold) / 2 + 10,\n        \"INTERMEDIATE\\nLOAD\",\n        COLOR_INTER,\n    ),\n    ((base_start + hours) / 2 - 600, (intermediate_threshold + base_load) / 2 - 30, \"BASE\\nLOAD\", COLOR_BASE),\n]\nfor x, y, text, color in region_labels:\n    ax.text(\n        x,\n        y,\n        text,\n        fontsize=8,\n        fontweight=\"bold\",\n        color=color,\n        ha=\"center\",\n        va=\"center\",\n        alpha=0.8,\n        linespacing=0.85,\n        path_effects=[pe.withStroke(linewidth=2.5, foreground=PAGE_BG)],\n        zorder=6,\n    )\n\n# Total energy annotation\nax.annotate(\n    f\"Total Energy\\n{total_energy_gwh:,.0f} GWh/year\",\n    xy=(hours * 0.45, load_mw[int(hours * 0.45)]),\n    xytext=(hours * 0.73, peak_threshold + 55),\n    fontsize=8,\n    fontweight=\"bold\",\n    color=INK,\n    ha=\"center\",\n    linespacing=1.3,\n    bbox={\n        \"boxstyle\": \"round,pad=0.4\",\n        \"facecolor\": ELEVATED_BG,\n        \"edgecolor\": INK_SOFT,\n        \"linewidth\": 0.8,\n        \"alpha\": 0.92,\n    },\n    arrowprops={\"arrowstyle\": \"->\", \"color\": INK_MUTED, \"connectionstyle\": \"arc3,rad=0.2\", \"linewidth\": 0.8},\n    zorder=7,\n)\n\n# Peak demand callout\nax.annotate(\n    f\"Peak: {load_mw[0]:,.0f} MW\",\n    xy=(0, load_mw[0]),\n    xytext=(hours * 0.11, load_mw[0] + 18),\n    fontsize=8,\n    fontweight=\"semibold\",\n    color=COLOR_PEAK,\n    arrowprops={\"arrowstyle\": \"->\", \"color\": COLOR_PEAK, \"linewidth\": 0.7},\n    path_effects=[pe.withStroke(linewidth=2, foreground=PAGE_BG)],\n    zorder=7,\n)\n\n# Style\nax.set_xlabel(\"Hours of Year (ranked by load)\", fontsize=10, color=INK, labelpad=6)\nax.set_ylabel(\"Power Demand (MW)\", fontsize=10, color=INK, labelpad=6)\nax.set_title(title, fontsize=title_fontsize, fontweight=\"medium\", color=INK, pad=10)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT)\nax.set_xlim(0, hours)\nax.set_ylim(base_load - 30, peak_load + 70)\n\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_color(INK_SOFT)\n\nax.xaxis.set_major_formatter(ticker.FuncFormatter(lambda x, _: f\"{x / 1000:.0f}k\" if x >= 1000 else f\"{x:.0f}\"))\nax.yaxis.set_major_formatter(ticker.FuncFormatter(lambda y, _: f\"{y:,.0f}\"))\nax.yaxis.grid(True, alpha=0.12, linewidth=0.6, color=INK)\n\n# Legend\nlegend_elements = [\n    Patch(facecolor=COLOR_PEAK, alpha=0.4, label=\"Peak Load\"),\n    Patch(facecolor=COLOR_INTER, alpha=0.4, label=\"Intermediate Load\"),\n    Patch(facecolor=COLOR_BASE, alpha=0.4, label=\"Base Load\"),\n]\nleg = ax.legend(\n    handles=legend_elements, fontsize=8, loc=\"upper right\", framealpha=0.92, edgecolor=INK_SOFT, fancybox=True\n)\nleg.get_frame().set_facecolor(ELEVATED_BG)\nplt.setp(leg.get_texts(), color=INK_SOFT)\n\nfig.subplots_adjust(left=0.10, right=0.97, top=0.93, bottom=0.13)\nfig.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}