{"spec_id":"line-loss-training","library":"echarts","language":"javascript","code":"// anyplot.ai\n// line-loss-training: Training Loss Curve\n// Library: echarts 6.1.0 | JavaScript 22.23.2\n// Quality: 94/100 | Created: 2026-09-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic LCG) ------------------------------------\nfunction makeLcg(seed) {\n  let state = seed;\n  return () => {\n    state = (state * 1664525 + 1013904223) % 4294967296;\n    return state / 4294967296;\n  };\n}\nconst rand = makeLcg(42);\n\nconst epochCount = 60;\nconst epochs = Array.from({ length: epochCount }, (_, i) => i + 1);\n\nconst trainLoss = [];\nconst valLoss = [];\nlet minValLossEpoch = 1;\nlet minValLoss = Infinity;\nfor (let i = 0; i < epochCount; i++) {\n  const epoch = i + 1;\n  // Training loss: smooth exponential decay\n  const train = 0.15 + 2.1 * Math.exp(-epoch / 12) + (rand() - 0.5) * 0.015;\n  // Validation loss: tracks training loss early on, then plateaus and\n  // creeps back up past ~epoch 30 to show overfitting\n  const overfitOnset = 28;\n  const overfitTerm = epoch > overfitOnset ? 0.0022 * (epoch - overfitOnset) ** 1.3 : 0;\n  const val = 0.22 + 2.0 * Math.exp(-epoch / 13) + overfitTerm + (rand() - 0.5) * 0.02;\n\n  trainLoss.push(Number(train.toFixed(4)));\n  valLoss.push(Number(val.toFixed(4)));\n\n  if (val < minValLoss) {\n    minValLoss = val;\n    minValLossEpoch = epoch;\n  }\n}\n\nconst stopIdx = epochs.indexOf(minValLossEpoch);\nconst gapBase = epochs.map((e, i) => [e, i >= stopIdx ? trainLoss[i] : null]);\nconst gapFill = epochs.map((e, i) =>\n  i >= stopIdx ? [e, Number((valLoss[i] - trainLoss[i]).toFixed(4))] : [e, null],\n);\n\n// --- Init --------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option --------------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  color: [t.palette[0], t.palette[1]],\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"line-loss-training · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },\n  },\n  legend: {\n    data: [\"Training loss\", \"Validation loss\"],\n    top: 60,\n    textStyle: { color: t.inkSoft, fontSize: 15 },\n    itemWidth: 24,\n    itemHeight: 3,\n  },\n  grid: { left: 90, right: 60, top: 130, bottom: 80 },\n  xAxis: {\n    type: \"value\",\n    name: \"Epoch\",\n    nameLocation: \"middle\",\n    nameGap: 40,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    min: 1,\n    max: epochCount,\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { show: false },\n    splitLine: { show: false },\n  },\n  yAxis: {\n    type: \"value\",\n    name: \"Cross-Entropy Loss\",\n    nameLocation: \"middle\",\n    nameGap: 60,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series: [\n    {\n      name: \"__gap_base\",\n      type: \"line\",\n      data: gapBase,\n      stack: \"gap\",\n      showSymbol: false,\n      silent: true,\n      tooltip: { show: false },\n      lineStyle: { opacity: 0 },\n      areaStyle: { opacity: 0 },\n      z: 1,\n    },\n    {\n      name: \"__gap_fill\",\n      type: \"line\",\n      data: gapFill,\n      stack: \"gap\",\n      showSymbol: false,\n      silent: true,\n      tooltip: { show: false },\n      lineStyle: { opacity: 0 },\n      areaStyle: { color: t.palette[1], opacity: 0.14 },\n      z: 1,\n    },\n    {\n      name: \"Training loss\",\n      type: \"line\",\n      data: epochs.map((e, i) => [e, trainLoss[i]]),\n      showSymbol: false,\n      lineStyle: { width: 3.5, color: t.palette[0] },\n    },\n    {\n      name: \"Validation loss\",\n      type: \"line\",\n      data: epochs.map((e, i) => [e, valLoss[i]]),\n      showSymbol: false,\n      lineStyle: { width: 3.5, color: t.palette[1] },\n    },\n    {\n      name: \"Optimal stopping point\",\n      type: \"scatter\",\n      data: [[minValLossEpoch, minValLoss]],\n      symbolSize: 16,\n      itemStyle: {\n        color: \"transparent\",\n        borderColor: t.ink,\n        borderWidth: 2.5,\n      },\n      z: 10,\n      tooltip: { show: false },\n      markLine: {\n        symbol: \"none\",\n        silent: true,\n        lineStyle: { color: t.ink, type: \"dashed\", width: 1.5, opacity: 0.5 },\n        label: {\n          formatter: `Min val loss · epoch ${minValLossEpoch}`,\n          color: t.inkSoft,\n          fontSize: 13,\n          position: \"insideEndTop\",\n        },\n        data: [{ xAxis: minValLossEpoch }],\n      },\n    },\n  ],\n});\n"}