{"spec_id":"curve-power-duration","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\ncurve-power-duration: Mean-Maximal Power Duration Curve\nLibrary: plotly 6.8.0 | Python 3.13.13\nQuality: 89/100 | Created: 2026-06-13\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\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\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# Imprint palette — first series always #009E73\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Data — synthetic well-trained cyclist\nnp.random.seed(42)\n\nCP = 280  # Critical Power in watts (aerobic asymptote)\nW_PRIME = 22000  # Anaerobic work capacity in joules\nP_1SEC = 1100  # Neuromuscular peak power at 1 s\n\n# Log-spaced test durations: 1 s to 5 hours (50 points)\ndurations = np.logspace(0, np.log10(18000), 50)\n\n# CP model: P(t) = CP + W′/t  (valid mainly for 2–60 min)\nmodel_power = CP + W_PRIME / durations\n\n# Empirical curve bounded by neuromuscular capacity at short durations\nneuro_cap = P_1SEC * (durations**-0.15)\nempirical_raw = np.minimum(model_power, neuro_cap)\nempirical_raw += np.random.normal(0, 8, len(durations))\n# Enforce monotonically non-increasing; keep power above CP floor\nempirical_power = np.minimum.accumulate(np.maximum(empirical_raw, CP + 1))\n\n# Smooth model display line\nmodel_t = np.logspace(0, np.log10(18000), 300)\nmodel_display = CP + W_PRIME / model_t\n\n# Reference durations: data values for interpolated empirical power\nreferences = {\"5 s\": 5, \"1 min\": 60, \"5 min\": 300, \"20 min\": 1200}\n\n# Log10 range for the x-axis (1 s → 18000 s)\nLOG_MIN = 0.0\nLOG_MAX = float(np.log10(18000))  # ≈ 4.255\n\n# Paper x-coordinate constants (accounts for l=80, r=40 margins on width=800)\n_LEFT_FRAC = 80 / 800\n_DATA_FRAC = 1.0 - _LEFT_FRAC - 40 / 800\n\n# Title — 49 chars < 67-char baseline, no scaling needed\ntitle = \"curve-power-duration · python · plotly · anyplot.ai\"\ntitle_fontsize = round(16 * min(1.0, 67 / len(title)))\n\n# X-axis ticks: 7 well-spaced values, no overlap\ntick_vals = [1, 10, 60, 300, 1200, 3600, 18000]\ntick_text = [\"1s\", \"10s\", \"1min\", \"5min\", \"20min\", \"1h\", \"5h\"]\n\n# Plot\nfig = go.Figure()\n\n# Subtle aerobic zone fill: area between the empirical curve and the CP floor\nfig.add_trace(\n    go.Scatter(\n        x=np.concatenate([durations, durations[::-1]]),\n        y=np.concatenate([empirical_power, np.full(len(durations), CP)]),\n        fill=\"toself\",\n        fillcolor=\"rgba(0,158,115,0.07)\",\n        line={\"width\": 0},\n        showlegend=False,\n        hoverinfo=\"skip\",\n    )\n)\n\n# Empirical mean-maximal power curve (primary series — Imprint position 1)\nfig.add_trace(\n    go.Scatter(\n        x=durations,\n        y=empirical_power,\n        mode=\"lines+markers\",\n        name=\"Mean-Maximal Power\",\n        line={\"color\": IMPRINT_PALETTE[0], \"width\": 3},\n        marker={\"size\": 10, \"color\": IMPRINT_PALETTE[0], \"opacity\": 0.85, \"line\": {\"color\": PAGE_BG, \"width\": 1.5}},\n    )\n)\n\n# CP model fit — dashed (Imprint position 2)\nfig.add_trace(\n    go.Scatter(\n        x=model_t,\n        y=model_display,\n        mode=\"lines\",\n        name=f\"CP Model  (CP = {CP} W, W′ = {W_PRIME // 1000} kJ)\",\n        line={\"color\": IMPRINT_PALETTE[1], \"width\": 2.5, \"dash\": \"dash\"},\n    )\n)\n\n# CP asymptote horizontal dotted line\nfig.add_hline(y=CP, line={\"color\": INK_MUTED, \"width\": 1.5, \"dash\": \"dot\"})\n\n# CP label near the right where the curve flattens (paper coords for robustness)\nfig.add_annotation(\n    x=0.86,\n    y=CP + 25,\n    xref=\"paper\",\n    yref=\"y\",\n    text=f\"CP = {CP} W\",\n    showarrow=False,\n    font={\"size\": 10, \"color\": INK_MUTED},\n    bgcolor=ELEVATED_BG,\n    borderpad=3,\n    xanchor=\"left\",\n)\n\n# Reference vertical dotted lines\nfor dur in references.values():\n    fig.add_vline(x=dur, line={\"color\": INK_MUTED, \"width\": 1.2, \"dash\": \"dot\"})\n\n# Reference annotations using paper x-coordinates to avoid log-axis positioning issues\nfor label, dur in references.items():\n    ref_power = int(np.interp(dur, durations, empirical_power))\n    x_paper = _LEFT_FRAC + (np.log10(dur) - LOG_MIN) / (LOG_MAX - LOG_MIN) * _DATA_FRAC\n    fig.add_annotation(\n        x=x_paper,\n        y=0.97,\n        xref=\"paper\",\n        yref=\"paper\",\n        text=f\"<b>{label}</b><br>{ref_power} W\",\n        showarrow=False,\n        font={\"size\": 11, \"color\": INK},\n        bgcolor=ELEVATED_BG,\n        bordercolor=INK_SOFT,\n        borderwidth=1,\n        borderpad=3,\n        xanchor=\"center\",\n        yanchor=\"top\",\n    )\n\n# Layout\nfig.update_layout(\n    autosize=False,\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    title={\"text\": title, \"font\": {\"size\": title_fontsize, \"color\": INK}, \"x\": 0.5, \"xanchor\": \"center\"},\n    xaxis={\n        \"type\": \"log\",\n        \"range\": [LOG_MIN, LOG_MAX],\n        \"title\": {\"text\": \"Duration\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickvals\": tick_vals,\n        \"ticktext\": tick_text,\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"tickangle\": 0,\n        \"gridcolor\": GRID,\n        \"showgrid\": True,\n        \"showline\": False,\n        \"zeroline\": False,\n    },\n    yaxis={\n        \"range\": [220, 1250],\n        \"title\": {\"text\": \"Power (W)\", \"font\": {\"size\": 12, \"color\": INK}},\n        \"tickfont\": {\"size\": 10, \"color\": INK_SOFT},\n        \"gridcolor\": GRID,\n        \"showgrid\": True,\n        \"showline\": False,\n        \"zeroline\": False,\n    },\n    legend={\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n        \"font\": {\"size\": 10, \"color\": INK_SOFT},\n        \"x\": 0.98,\n        \"xanchor\": \"right\",\n        \"y\": 0.50,\n        \"yanchor\": \"top\",\n    },\n    margin={\"l\": 80, \"r\": 40, \"t\": 80, \"b\": 60},\n)\n\n# Save — landscape 3200 × 1800 (width=800, height=450, scale=4)\nfig.write_image(f\"plot-{THEME}.png\", width=800, height=450, scale=4)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}