{"spec_id":"curve-power-duration","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\ncurve-power-duration: Mean-Maximal Power Duration Curve\nLibrary: matplotlib 3.11.0 | Python 3.13.13\nQuality: 89/100 | Created: 2026-06-13\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport matplotlib.ticker as ticker\nimport numpy as np\n\n\n# Theme tokens — Imprint palette chrome mapping\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\nBRAND = \"#009E73\"  # Imprint position 1 — empirical MMP (always first series)\nMODEL_CLR = \"#4467A3\"  # Imprint position 3 — CP model (semantic: blue = theoretical fit)\n\n# Data — synthetic well-trained cyclist: CP = 280 W, W' = 20 kJ\nnp.random.seed(42)\nCP = 280  # W — critical power aerobic asymptote\nW_PRIME = 20000  # J — anaerobic work capacity\nP_MAX = 1100  # W — neuromuscular peak power (1-s sprint)\n\n# 60 log-spaced durations: 1 s → 18 000 s (5 h)\ndurations = np.logspace(0, np.log10(18000), 60)\n\n# Empirical mean-maximal power: bounded by neuromuscular ceiling, monotonically non-increasing\nraw = np.minimum(P_MAX, CP + W_PRIME / durations) + np.random.normal(3, 9, 60)\nempirical = raw.copy()\nfor i in range(1, len(empirical)):\n    empirical[i] = min(empirical[i], empirical[i - 1])\n\n# Smooth CP model line (from 60 s — physiologically applicable range)\ndur_model = np.logspace(np.log10(60), np.log10(18000), 400)\nmodel_line = CP + W_PRIME / dur_model\n\n# Plot canvas (3200 × 1800 px — no bbox_inches, figsize × dpi sets canvas exactly)\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\n# Primary series — empirical MMP (Imprint brand green, solid)\nax.plot(durations, empirical, color=BRAND, linewidth=2.8, label=\"Mean-Maximal Power\", zorder=3)\n\n# CP model fit (Imprint blue, dashed)\nax.plot(\n    dur_model, model_line, color=MODEL_CLR, linewidth=2.5, linestyle=\"--\", label=\"CP Model  (P = CP + W′/t)\", zorder=2\n)\n\n# CP horizontal asymptote — annotated directly, not in legend\nax.axhline(y=CP, color=INK_MUTED, linewidth=1.3, linestyle=\":\", alpha=0.8, zorder=1)\nax.text(17000, CP + 12, f\"CP = {CP} W\", color=INK_MUTED, fontsize=8, ha=\"right\", va=\"bottom\")\n\n# Reference duration vertical markers\nref_marks = {\"5 s\\nsprint\": 5, \"1 min\": 60, \"5 min\": 300, \"20 min\\n(FTP)\": 1200}\nfor label, dur in ref_marks.items():\n    ax.axvline(x=dur, color=INK_SOFT, linewidth=0.8, linestyle=\":\", alpha=0.35, zorder=1)\n    ax.text(dur, 1200, label, color=INK_SOFT, fontsize=8, ha=\"center\", va=\"top\", linespacing=1.25)\n\n# X-axis — log scale with human-readable tick labels\nax.set_xscale(\"log\")\ntick_secs = [1, 5, 30, 60, 300, 1200, 3600, 10800, 18000]\ntick_labels = [\"1s\", \"5s\", \"30s\", \"1min\", \"5min\", \"20min\", \"1h\", \"3h\", \"5h\"]\nax.set_xticks(tick_secs)\nax.set_xticklabels(tick_labels)\nax.xaxis.set_minor_locator(ticker.NullLocator())\nax.set_xlim(0.9, 20000)\nax.set_ylim(150, 1270)\n\n# Style\ntitle = \"curve-power-duration · python · matplotlib · anyplot.ai\"\nax.set_title(title, fontsize=12, fontweight=\"medium\", color=INK, pad=10)\nax.set_xlabel(\"Effort Duration\", fontsize=10, color=INK, labelpad=6)\nax.set_ylabel(\"Power Output (W)\", fontsize=10, color=INK, labelpad=6)\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT, labelcolor=INK_SOFT)\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nfor spine in (\"left\", \"bottom\"):\n    ax.spines[spine].set_color(INK_SOFT)\nax.yaxis.grid(True, alpha=0.12, linewidth=0.8, color=INK)\n\n# Legend — lower left has ample space (curve is above 1000 W there)\nleg = ax.legend(fontsize=8, loc=\"lower left\")\nif leg:\n    leg.get_frame().set_facecolor(ELEVATED_BG)\n    leg.get_frame().set_edgecolor(INK_SOFT)\n    plt.setp(leg.get_texts(), color=INK_SOFT)\n\nfig.subplots_adjust(left=0.09, right=0.97, top=0.92, bottom=0.13)\n\n# Save (bbox_inches omitted — figsize=(8,4.5) × dpi=400 → exactly 3200×1800)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\nplt.close()\n"}