{"spec_id":"line-training-load-pmc","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// line-training-load-pmc: Training Load Performance Management Chart\n// Library: chartjs 4.4.7 | JavaScript 22.22.3\n// Quality: 86/100 | Created: 2026-06-13\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data ---------------------------------------------------------------\nconst N = 180;\n\n// Deterministic LCG for reproducible day-to-day variation\nlet lcgState = 7919;\nfunction lcg() {\n    lcgState = (lcgState * 1664525 + 1013904223) >>> 0;\n    return lcgState / 4294967295;\n}\n\n// Daily base TSS by day-of-week (Mon=0 … Sun=6): rest Mon, long Sat, rest Sun\nconst dayBase = [0, 80, 90, 70, 100, 160, 0];\n\nconst tssArr = [];\nfor (let i = 0; i < N; i++) {\n    const week = Math.floor(i / 7);\n    const dow = i % 7;\n    const cyclePhase = week % 4; // 0-2: build weeks, 3: recovery week\n    const loadFactor = cyclePhase === 3 ? 0.35 : 0.60 + cyclePhase * 0.15;\n    const taperFactor = week >= 24 ? Math.max(0.15, 1 - (week - 24) * 0.45) : 1;\n    const base = dayBase[dow];\n    if (base === 0) {\n        tssArr.push(0);\n    } else {\n        const jitter = (lcg() - 0.5) * 22;\n        tssArr.push(Math.max(0, Math.round(base * loadFactor * taperFactor + jitter)));\n    }\n}\n\n// CTL = 42-day EWMA (fitness), ATL = 7-day EWMA (fatigue), TSB = CTL − ATL\nconst ctlArr = [], atlArr = [], tsbArr = [];\nlet ctlPrev = 20, atlPrev = 25;\nfor (let i = 0; i < N; i++) {\n    tsbArr.push(Math.round((ctlPrev - atlPrev) * 10) / 10);\n    ctlPrev = ctlPrev + (tssArr[i] - ctlPrev) / 42;\n    atlPrev = atlPrev + (tssArr[i] - atlPrev) / 7;\n    ctlArr.push(Math.round(ctlPrev * 10) / 10);\n    atlArr.push(Math.round(atlPrev * 10) / 10);\n}\n\n// Date labels for x-axis\nconst startDate = new Date(\"2025-12-14\");\nconst labels = Array.from({ length: N }, (_, i) => {\n    const d = new Date(startDate);\n    d.setDate(d.getDate() + i);\n    return d.toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\" });\n});\n\n// TSB split: separate positive (fresh) and negative (fatigued) for two-tone fill\nconst tsbPos = tsbArr.map(v => v >= 0 ? v : 0);\nconst tsbNeg = tsbArr.map(v => v <= 0 ? v : 0);\n\n// --- Mount ---------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Title ---------------------------------------------------------------\nconst TITLE = \"Training Load PMC · line-training-load-pmc · javascript · chartjs · anyplot.ai\";\nconst titleSize = Math.max(15, Math.round(22 * 67 / TITLE.length));\n\n// --- Chart ---------------------------------------------------------------\nnew Chart(canvas, {\n    type: \"line\",\n    data: {\n        labels,\n        datasets: [\n            // Daily TSS bars — raw workout load, drawn as background layer\n            {\n                type: \"bar\",\n                label: \"Daily TSS\",\n                data: tssArr,\n                yAxisID: \"y\",\n                backgroundColor: t.palette[2] + \"66\",\n                borderWidth: 0,\n            },\n            // TSB positive fill — fresh / good form (blue, per spec and CVD safety)\n            {\n                type: \"line\",\n                label: \"_tsbPos\",\n                data: tsbPos,\n                yAxisID: \"y1\",\n                backgroundColor: t.palette[2] + \"4D\",\n                borderWidth: 0,\n                pointRadius: 0,\n                fill: \"origin\",\n                tension: 0.3,\n            },\n            // TSB negative fill — fatigued / overloaded (red)\n            {\n                type: \"line\",\n                label: \"_tsbNeg\",\n                data: tsbNeg,\n                yAxisID: \"y1\",\n                backgroundColor: t.palette[4] + \"4D\",\n                borderWidth: 0,\n                pointRadius: 0,\n                fill: \"origin\",\n                tension: 0.3,\n            },\n            // TSB = 0 reference line — separates fresh from fatigued zones\n            {\n                type: \"line\",\n                label: \"_zero\",\n                data: Array(N).fill(0),\n                yAxisID: \"y1\",\n                borderColor: t.inkSoft + \"80\",\n                borderWidth: 1,\n                borderDash: [6, 4],\n                pointRadius: 0,\n                fill: false,\n            },\n            // Form (TSB) outline\n            {\n                type: \"line\",\n                label: \"Form (TSB)\",\n                data: tsbArr,\n                yAxisID: \"y1\",\n                borderColor: t.inkSoft,\n                borderWidth: 2,\n                pointRadius: 0,\n                fill: false,\n                tension: 0.3,\n            },\n            // Fatigue (ATL) — 7-day EWMA, dashed to distinguish from CTL\n            {\n                type: \"line\",\n                label: \"Fatigue (ATL)\",\n                data: atlArr,\n                yAxisID: \"y\",\n                borderColor: t.palette[1],\n                borderWidth: 2.5,\n                borderDash: [8, 4],\n                pointRadius: 0,\n                fill: false,\n                tension: 0.2,\n            },\n            // Fitness (CTL) — 42-day EWMA, smoothest and thickest line\n            {\n                type: \"line\",\n                label: \"Fitness (CTL)\",\n                data: ctlArr,\n                yAxisID: \"y\",\n                borderColor: t.palette[0],\n                borderWidth: 3.5,\n                pointRadius: 0,\n                fill: false,\n                tension: 0.2,\n            },\n        ],\n    },\n    options: {\n        responsive: true,\n        maintainAspectRatio: false,\n        animation: false,\n        layout: {\n            padding: { top: 8, right: 20, bottom: 8, left: 8 },\n        },\n        plugins: {\n            title: {\n                display: true,\n                text: TITLE,\n                color: t.ink,\n                font: { size: titleSize, weight: \"bold\" },\n                padding: { top: 12, bottom: 20 },\n            },\n            legend: {\n                position: \"top\",\n                align: \"end\",\n                labels: {\n                    color: t.ink,\n                    font: { size: 14 },\n                    filter: (item) => !item.text.startsWith(\"_\"),\n                    usePointStyle: true,\n                    pointStyleWidth: 28,\n                    boxHeight: 12,\n                    padding: 18,\n                },\n            },\n        },\n        scales: {\n            x: {\n                ticks: {\n                    color: t.inkSoft,\n                    font: { size: 12 },\n                    maxTicksLimit: 13,\n                    maxRotation: 0,\n                },\n                grid: { color: t.grid },\n                border: { color: t.inkSoft },\n                title: {\n                    display: true,\n                    text: \"Date\",\n                    color: t.ink,\n                    font: { size: 14 },\n                    padding: { top: 6 },\n                },\n            },\n            y: {\n                position: \"left\",\n                min: 0,\n                ticks: {\n                    color: t.inkSoft,\n                    font: { size: 12 },\n                },\n                grid: { color: t.grid },\n                border: { color: t.inkSoft },\n                title: {\n                    display: true,\n                    text: \"CTL / ATL / TSS\",\n                    color: t.ink,\n                    font: { size: 14 },\n                },\n            },\n            y1: {\n                position: \"right\",\n                ticks: {\n                    color: t.inkSoft,\n                    font: { size: 12 },\n                },\n                grid: { drawOnChartArea: false },\n                border: { color: t.inkSoft },\n                title: {\n                    display: true,\n                    text: \"TSB (Form)\",\n                    color: t.ink,\n                    font: { size: 14 },\n                },\n            },\n        },\n    },\n    plugins: [\n        {\n            id: \"canvasBg\",\n            beforeDraw(chart) {\n                const ctx = chart.canvas.getContext(\"2d\");\n                ctx.save();\n                ctx.fillStyle = t.pageBg;\n                ctx.fillRect(0, 0, chart.width, chart.height);\n                ctx.restore();\n            },\n        },\n    ],\n});\n"}