{"spec_id":"line-loss-training","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// line-loss-training: Training Loss Curve\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 94/100 | Created: 2026-09-05\n\n//# anyplot-orientation: landscape\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: train/validation cross-entropy loss over 60 epochs ---------------\n// Deterministic — no RNG needed, both curves are closed-form functions of epoch.\nconst EPOCHS = 60;\nconst epochs = [];\nconst trainLoss = [];\nconst valLoss = [];\nfor (let e = 1; e <= EPOCHS; e++) {\n  epochs.push(e);\n  trainLoss.push(2.6 * Math.exp(-0.085 * e) + 0.06 + 0.015 * Math.sin(e * 0.9));\n  // Validation loss tracks training loss early on, then overfitting sets in:\n  // a slow quadratic climb overtakes the exponential decay past ~epoch 30.\n  valLoss.push(2.7 * Math.exp(-0.07 * e) + 0.00033 * (e - 1) ** 2 + 0.09 + 0.02 * Math.sin(e * 0.7 + 1));\n}\n\n// --- Optimal early-stopping point: epoch with minimum validation loss -------\nlet minIdx = 0;\nfor (let i = 1; i < valLoss.length; i++) {\n  if (valLoss[i] < valLoss[minIdx]) minIdx = i;\n}\nconst stopEpoch = epochs[minIdx];\nconst stopLoss = valLoss[minIdx];\n\n// --- Mount --------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Overfitting-region plugin ---------------------------------------------\n// Shades epoch > stopEpoch and labels it, so the divergence reads at a glance\n// instead of relying on the marker alone. Muted, theme-adaptive fill sits\n// behind the data (drawn before the datasets) and never competes with it.\nconst regionFill = t.theme === \"dark\" ? \"rgba(240,239,232,0.07)\" : \"rgba(26,26,23,0.05)\";\nconst overfitRegionPlugin = {\n  id: \"overfitRegion\",\n  beforeDatasetsDraw(chart) {\n    const { ctx, chartArea, scales } = chart;\n    const xStart = scales.x.getPixelForValue(stopEpoch);\n    ctx.save();\n    ctx.fillStyle = regionFill;\n    ctx.fillRect(xStart, chartArea.top, chartArea.right - xStart, chartArea.bottom - chartArea.top);\n    ctx.restore();\n  },\n  afterDraw(chart) {\n    const { ctx, scales } = chart;\n    const x = scales.x.getPixelForValue(stopEpoch);\n    const y = scales.y.getPixelForValue(stopLoss);\n    ctx.save();\n    ctx.fillStyle = t.inkSoft;\n    ctx.font = \"italic 15px sans-serif\";\n    ctx.textAlign = \"left\";\n    ctx.fillText(\"Overfitting region →\", x + 16, y - 30);\n    ctx.restore();\n  },\n};\n\n// --- Chart ----------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"line\",\n  plugins: [overfitRegionPlugin],\n  data: {\n    labels: epochs,\n    datasets: [\n      {\n        label: \"Training loss\",\n        data: trainLoss,\n        borderColor: t.palette[0],\n        backgroundColor: t.palette[0],\n        borderWidth: 3.5,\n        pointRadius: 0,\n        pointHoverRadius: 5,\n        tension: 0.15,\n      },\n      {\n        label: \"Validation loss\",\n        data: valLoss,\n        borderColor: t.palette[1],\n        backgroundColor: t.palette[1],\n        borderWidth: 3.5,\n        pointRadius: 0,\n        pointHoverRadius: 5,\n        tension: 0.15,\n      },\n      {\n        label: `Early-stopping point (epoch ${stopEpoch})`,\n        type: \"scatter\",\n        data: [{ x: stopEpoch, y: stopLoss }],\n        backgroundColor: t.amber,\n        borderColor: t.ink,\n        borderWidth: 2,\n        pointRadius: 10,\n        pointHoverRadius: 12,\n        pointStyle: \"circle\",\n        showLine: false,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: { padding: { top: 24, right: 32, bottom: 12, left: 12 } },\n    plugins: {\n      title: {\n        display: true,\n        text: \"line-loss-training · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 24, weight: \"500\" },\n        padding: { top: 8, bottom: 24 },\n      },\n      legend: {\n        labels: { color: t.ink, font: { size: 16 }, usePointStyle: true, padding: 24 },\n      },\n      tooltip: {\n        backgroundColor: t.elevatedBg,\n        titleColor: t.ink,\n        bodyColor: t.inkSoft,\n        borderColor: t.grid,\n        borderWidth: 1,\n        padding: 12,\n        titleFont: { size: 16 },\n        bodyFont: { size: 14 },\n      },\n    },\n    scales: {\n      x: {\n        type: \"linear\",\n        title: { display: true, text: \"Epoch\", color: t.ink, font: { size: 20 }, padding: { top: 14 } },\n        ticks: { color: t.inkSoft, font: { size: 16 }, stepSize: 10 },\n        grid: { display: false },\n        border: { display: false },\n        min: 1,\n        max: EPOCHS,\n      },\n      y: {\n        title: {\n          display: true,\n          text: \"Cross-Entropy Loss\",\n          color: t.ink,\n          font: { size: 20 },\n          padding: { bottom: 14 },\n        },\n        ticks: { color: t.inkSoft, font: { size: 16 } },\n        grid: { color: t.grid, drawTicks: false },\n        border: { display: false },\n        beginAtZero: true,\n      },\n    },\n    interaction: { mode: \"nearest\", intersect: false },\n  },\n});\n"}