{"spec_id":"curve-power-duration","library":"echarts","language":"javascript","code":"// anyplot.ai\n// curve-power-duration: Mean-Maximal Power Duration Curve\n// Library: echarts 5.5.1 | JavaScript 22.22.3\n// Quality: 88/100 | Created: 2026-06-13\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// Synthetic well-trained cyclist: CP = 280 W, W' = 20 000 J\nconst CP = 280;\nconst W_PRIME = 20000;\n\n// 45 log-spaced durations: 1 s → 18 000 s (5 h)\nconst N = 45;\nconst durations = Array.from({ length: N }, (_, i) =>\n  Math.exp((Math.log(18000) / (N - 1)) * i)\n);\n\n// Model curve: P(t) = CP + W'/t\nconst modelData = durations.map(d => [d, CP + W_PRIME / d]);\n\n// Empirical mean-maximal curve — above model at short durations (neuromuscular),\n// converges toward CP at long durations. Fixed boosts for reproducibility.\nconst boostPct = [\n  0.21, 0.20, 0.19, 0.18, 0.17, 0.17, 0.16, 0.15, 0.14, 0.14,\n  0.13, 0.12, 0.11, 0.11, 0.10, 0.09, 0.09, 0.08, 0.07, 0.07,\n  0.06, 0.06, 0.05, 0.05, 0.04, 0.04, 0.04, 0.03, 0.03, 0.03,\n  0.02, 0.02, 0.02, 0.02, 0.02, 0.01, 0.01, 0.01, 0.01, 0.01,\n  0.01, 0.01, 0.01, 0.01, 0.01\n];\nlet lastP = Infinity;\nconst empiricalData = durations.map((d, i) => {\n  let p = Math.round((CP + W_PRIME / d) * (1 + boostPct[i]));\n  p = Math.min(p, lastP);\n  lastP = p;\n  return [d, p];\n});\n\n// Reference duration vertical markers\nconst refMarkers = [\n  { xAxis: 5,    name: '5 s' },\n  { xAxis: 60,   name: '1 min' },\n  { xAxis: 300,  name: '5 min' },\n  { xAxis: 1200, name: '20 min' },\n];\n\nconst chart = echarts.init(document.getElementById('container'));\n\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: 'transparent',\n\n  title: {\n    text: 'curve-power-duration · javascript · echarts · anyplot.ai',\n    left: 'center',\n    top: 24,\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: 'bold' },\n  },\n\n  legend: {\n    top: 72,\n    right: 90,\n    orient: 'vertical',\n    textStyle: { color: t.inkSoft, fontSize: 14 },\n    itemGap: 16,\n    backgroundColor: t.elevatedBg,\n    borderRadius: 4,\n    padding: [10, 14],\n  },\n\n  grid: { left: 110, right: 260, top: 90, bottom: 80 },\n\n  xAxis: {\n    type: 'log',\n    min: 1,\n    max: 18000,\n    name: 'Duration',\n    nameLocation: 'middle',\n    nameGap: 54,\n    nameTextStyle: { color: t.inkSoft, fontSize: 16 },\n    axisLabel: {\n      color: t.inkSoft,\n      fontSize: 13,\n      formatter: function (val) {\n        const v = Math.round(val);\n        if (v < 60) return v + 's';\n        if (v < 3600) return Math.round(v / 60) + 'min';\n        return (v / 3600).toFixed(1).replace('.0', '') + 'h';\n      },\n    },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n\n  yAxis: {\n    type: 'value',\n    min: 200,\n    max: 1200,\n    interval: 100,\n    name: 'Power (W)',\n    nameLocation: 'middle',\n    nameGap: 68,\n    nameTextStyle: { color: t.inkSoft, fontSize: 16 },\n    axisLabel: {\n      color: t.inkSoft,\n      fontSize: 13,\n      formatter: '{value} W',\n    },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n\n  series: [\n    {\n      name: 'Mean-Maximal Power',\n      type: 'line',\n      data: empiricalData,\n      smooth: 0.3,\n      showSymbol: false,\n      lineStyle: { width: 3.5, color: t.palette[0] },\n      itemStyle: { color: t.palette[0] },\n      markLine: {\n        silent: true,\n        symbol: ['none', 'none'],\n        label: {\n          position: 'insideEndTop',\n          color: t.inkSoft,\n          fontSize: 14,\n          formatter: '{b}',\n          padding: [4, 6],\n        },\n        lineStyle: { type: 'dashed', color: t.inkSoft, opacity: 0.45, width: 1.5 },\n        data: refMarkers,\n      },\n    },\n    {\n      name: 'Critical Power Model',\n      type: 'line',\n      data: modelData,\n      smooth: false,\n      showSymbol: false,\n      lineStyle: { width: 2.5, type: 'dashed', color: t.palette[1] },\n      itemStyle: { color: t.palette[1] },\n    },\n    {\n      name: 'CP Asymptote (280 W)',\n      type: 'line',\n      data: [[1, CP], [18000, CP]],\n      showSymbol: false,\n      lineStyle: { width: 2.0, type: 'dotted', color: t.palette[4], opacity: 0.85 },\n      itemStyle: { color: t.palette[4] },\n    },\n  ],\n});\n"}