{"spec_id":"line-loss-training","library":"muix","language":"javascript","code":"// anyplot.ai\n// line-loss-training: Training Loss Curve\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 97/100 | Created: 2026-09-05\n\nimport { LineChart } from \"@mui/x-charts/LineChart\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { useXScale, useDrawingArea } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\nconst TRAIN_GRADIENT_ID = \"lineLossTrainingTrainFill\";\nconst VAL_GRADIENT_ID = \"lineLossTrainingValFill\";\n\n// --- Data (in-memory, deterministic LCG for reproducible noise) -------------\nfunction lcg(seed) {\n  let state = seed;\n  return () => {\n    state = (state * 1664525 + 1013904223) % 4294967296;\n    return state / 4294967296;\n  };\n}\nconst rand = lcg(42);\n\nconst EPOCHS = 60;\nconst epoch = Array.from({ length: EPOCHS }, (_, i) => i + 1);\n\n// Training loss keeps decaying smoothly for the full run.\nconst trainLoss = epoch.map(\n  (e) => 2.35 * Math.exp(-e / 17) + 0.08 + (rand() - 0.5) * 0.02,\n);\n\n// Validation loss tracks training loss early on, then diverges upward past\n// epoch ~28 — the classic overfitting signature this spec is about.\nconst valLoss = epoch.map((e) => {\n  const overfitPenalty = Math.max(0, e - 28) ** 2 * 0.00085;\n  return 2.5 * Math.exp(-e / 15.5) + 0.12 + overfitPenalty + (rand() - 0.5) * 0.035;\n});\n\nlet bestEpoch = epoch[0];\nlet bestValLoss = valLoss[0];\nvalLoss.forEach((v, i) => {\n  if (v < bestValLoss) {\n    bestValLoss = v;\n    bestEpoch = epoch[i];\n  }\n});\n\n// Highlights the post-early-stop overfitting window (bestEpoch → last epoch)\n// as a soft background band. MUI X community has no band-annotation\n// primitive, so this reads the shared x-scale/drawing-area straight out of\n// the chart's own render context — the documented composition pattern for\n// marks outside the community surface.\nfunction DivergenceZone() {\n  const xScale = useXScale() as any;\n  const drawingArea = useDrawingArea();\n  if (!xScale) return null;\n  const xStart = xScale(bestEpoch);\n  const xEnd = xScale(EPOCHS);\n  return (\n    <rect\n      x={xStart}\n      y={drawingArea.top}\n      width={Math.max(0, xEnd - xStart)}\n      height={drawingArea.height}\n      fill={t.amber}\n      fillOpacity={0.08}\n    />\n  );\n}\n\n// --- Chart (default-exported component — the harness mounts it) -------------\nexport default function Chart() {\n  return (\n    <div style={{ width: \"100%\", height: \"100%\", position: \"relative\" }}>\n      {/* Title rendered in the chart's top margin space */}\n      <div\n        style={{\n          position: \"absolute\",\n          top: 14,\n          left: 0,\n          right: 0,\n          textAlign: \"center\",\n          zIndex: 1,\n          fontSize: 22,\n          fontWeight: 600,\n          letterSpacing: \"0.2px\",\n          color: t.ink,\n          pointerEvents: \"none\",\n          fontFamily: \"'Roboto', 'Helvetica', 'Arial', sans-serif\",\n        }}\n      >\n        line-loss-training · javascript · muix · anyplot.ai\n      </div>\n\n      <LineChart\n        width={window.ANYPLOT_SIZE.width}\n        height={window.ANYPLOT_SIZE.height}\n        skipAnimation\n        series={[\n          {\n            id: \"train\",\n            data: trainLoss,\n            label: \"Training loss\",\n            color: t.palette[0],\n            showMark: false,\n            curve: \"monotoneX\",\n            area: true,\n            baseline: 0,\n          },\n          {\n            id: \"val\",\n            data: valLoss,\n            label: \"Validation loss\",\n            color: t.palette[1],\n            showMark: false,\n            curve: \"monotoneX\",\n            area: true,\n            baseline: 0,\n          },\n        ]}\n        xAxis={[\n          {\n            data: epoch,\n            scaleType: \"linear\",\n            label: \"Epoch\",\n            tickMinStep: 5,\n          },\n        ]}\n        yAxis={[\n          {\n            label: \"Cross-Entropy Loss\",\n            min: 0,\n            valueFormatter: (v: number) => v.toFixed(1),\n          },\n        ]}\n        grid={{ horizontal: true }}\n        sx={{\n          \"& .MuiChartsAxis-label\": {\n            fontSize: \"16px !important\",\n            fontWeight: 500,\n          },\n          \"& .MuiChartsAxis-tickLabel\": {\n            fontSize: \"14px !important\",\n          },\n          \"& .MuiChartsLegend-label\": {\n            fontSize: \"15px !important\",\n          },\n          \"& .MuiLineElement-root\": {\n            strokeWidth: \"3px\",\n          },\n          \"& .MuiAreaElement-series-train\": {\n            fill: `url(#${TRAIN_GRADIENT_ID})`,\n          },\n          \"& .MuiAreaElement-series-val\": {\n            fill: `url(#${VAL_GRADIENT_ID})`,\n          },\n        }}\n        slotProps={{\n          legend: {\n            direction: \"row\",\n            position: { vertical: \"bottom\", horizontal: \"middle\" },\n          },\n        }}\n        margin={{ top: 70, right: 40, bottom: 90, left: 90 }}\n      >\n        {/* Subtle fades from each line down to the zero baseline — keeps\n            visual weight on the curves themselves while grounding both\n            series against the cross-entropy-loss floor. */}\n        <defs>\n          <linearGradient id={TRAIN_GRADIENT_ID} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n            <stop offset=\"0%\" stopColor={t.palette[0]} stopOpacity={0.22} />\n            <stop offset=\"100%\" stopColor={t.palette[0]} stopOpacity={0.02} />\n          </linearGradient>\n          <linearGradient id={VAL_GRADIENT_ID} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n            <stop offset=\"0%\" stopColor={t.palette[1]} stopOpacity={0.22} />\n            <stop offset=\"100%\" stopColor={t.palette[1]} stopOpacity={0.02} />\n          </linearGradient>\n        </defs>\n        <DivergenceZone />\n        <ChartsReferenceLine\n          x={bestEpoch}\n          label={`Early-stop point · epoch ${bestEpoch}`}\n          labelAlign=\"start\"\n          lineStyle={{ stroke: t.ink, strokeDasharray: \"6 6\", strokeWidth: 1.5, strokeOpacity: 0.6 }}\n          labelStyle={{ fill: t.inkSoft, fontSize: 13 }}\n        />\n      </LineChart>\n    </div>\n  );\n}\n"}