{"spec_id":"curve-bias-variance-tradeoff","library":"muix","language":"javascript","code":"// anyplot.ai\n// curve-bias-variance-tradeoff: Bias-Variance Tradeoff Curve\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 97/100 | Created: 2026-08-24\n\nimport { LineChart } from \"@mui/x-charts/LineChart\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { useXScale, useYScale, useDrawingArea } from \"@mui/x-charts/hooks\";\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\n\nconst t = window.ANYPLOT_TOKENS;\n// \"muted\" semantic anchor is not exposed on ANYPLOT_TOKENS — derive per theme\n// to match prompts/default-style-guide.md \"Theme-adaptive Chrome\" table.\nconst INK_MUTED = window.ANYPLOT_THEME === \"dark\" ? \"#A8A79F\" : \"#6B6A63\";\n\n// --- Data: theoretical error-decomposition curves, not empirical -----------\nconst N_POINTS = 80;\nconst COMPLEXITY_MIN = 1;\nconst COMPLEXITY_MAX = 20;\nconst modelComplexity = Array.from(\n  { length: N_POINTS },\n  (_, i) => COMPLEXITY_MIN + (i * (COMPLEXITY_MAX - COMPLEXITY_MIN)) / (N_POINTS - 1),\n);\n\nconst IRREDUCIBLE_ERROR = 0.15;\nconst biasSquared = modelComplexity.map((c) => 2.4 / (1 + 0.3 * c));\nconst variance = modelComplexity.map((c) => 0.005 * c * c);\nconst irreducibleError = modelComplexity.map(() => IRREDUCIBLE_ERROR);\nconst totalError = modelComplexity.map((_, i) => biasSquared[i] + variance[i] + IRREDUCIBLE_ERROR);\n\nlet optimalIndex = 0;\nfor (let i = 1; i < totalError.length; i++) {\n  if (totalError[i] < totalError[optimalIndex]) optimalIndex = i;\n}\nconst optimalComplexity = modelComplexity[optimalIndex];\nconst yMax = Math.max(...totalError) * 1.08;\n\nconst series = [\n  { id: \"total\", label: \"Total Error\", data: totalError, color: t.palette[0], curve: \"monotoneX\" as const, showMark: false },\n  { id: \"bias\", label: \"Bias²\", data: biasSquared, color: t.palette[1], curve: \"monotoneX\" as const, showMark: false },\n  { id: \"variance\", label: \"Variance\", data: variance, color: t.palette[2], curve: \"monotoneX\" as const, showMark: false },\n  // \"Irreducible\" plays the neutral/baseline semantic role (see default-style-guide.md\n  // \"Semantic anchors\") — a constant reference line, styled to read as part of the chart's ink.\n  { id: \"irreducible\", label: \"Irreducible Error\", data: irreducibleError, color: t.ink, curve: \"linear\" as const, showMark: false },\n];\n\nconst TITLE_HEIGHT = 84;\n\n// --- Overlay: underfit/overfit zones + direct end-of-line curve labels ------\nfunction Annotations() {\n  const xScale = useXScale();\n  const yScale = useYScale();\n  const { left, top, width, height } = useDrawingArea();\n\n  const xOptimal = xScale(optimalComplexity);\n  const xMin = left;\n  const xMax = left + width;\n\n  const endLabels = [\n    { id: \"total\", value: totalError[totalError.length - 1], color: t.palette[0], text: \"Total Error\" },\n    { id: \"variance\", value: variance[variance.length - 1], color: t.palette[2], text: \"Variance\" },\n    { id: \"bias\", value: biasSquared[biasSquared.length - 1], color: t.palette[1], text: \"Bias²\" },\n    { id: \"irreducible\", value: IRREDUCIBLE_ERROR, color: t.ink, text: \"Irreducible Error\" },\n  ]\n    .map((s) => ({ ...s, y: yScale(s.value) }))\n    .sort((a, b) => a.y - b.y);\n\n  const MIN_LABEL_GAP = 20;\n  for (let i = 1; i < endLabels.length; i++) {\n    if (endLabels[i].y - endLabels[i - 1].y < MIN_LABEL_GAP) {\n      endLabels[i].y = endLabels[i - 1].y + MIN_LABEL_GAP;\n    }\n  }\n\n  return (\n    <g>\n      <rect x={xMin} y={top} width={xOptimal - xMin} height={height} fill={INK_MUTED} opacity={0.07} />\n      <rect x={xOptimal} y={top} width={xMax - xOptimal} height={height} fill={t.amber} opacity={0.1} />\n      <text x={xMin + (xOptimal - xMin) / 2} y={top + 26} fill={INK_MUTED} fontSize={14} fontWeight={600} letterSpacing={1} textAnchor=\"middle\">\n        UNDERFITTING\n      </text>\n      <text x={xOptimal + (xMax - xOptimal) / 2} y={top + 26} fill={INK_MUTED} fontSize={14} fontWeight={600} letterSpacing={1} textAnchor=\"middle\">\n        OVERFITTING\n      </text>\n      {endLabels.map((s) => (\n        <text key={s.id} x={xMax + 10} y={s.y + 5} fill={s.color} fontSize={15} fontWeight={500}>\n          {s.text}\n        </text>\n      ))}\n    </g>\n  );\n}\n\n// --- Chart --------------------------------------------------------------------\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n\n  return (\n    <Box sx={{ width, height, display: \"flex\", flexDirection: \"column\", paddingTop: \"18px\" }}>\n      <Typography sx={{ color: t.ink, fontSize: 22, fontWeight: 500, textAlign: \"center\", lineHeight: 1.2 }}>\n        curve-bias-variance-tradeoff · javascript · muix · anyplot.ai\n      </Typography>\n      <Typography sx={{ color: t.inkSoft, fontSize: 14, textAlign: \"center\", marginTop: \"4px\" }}>\n        Total Error = Bias² + Variance + Irreducible Error\n      </Typography>\n      <LineChart\n        width={width}\n        height={height - TITLE_HEIGHT}\n        skipAnimation\n        series={series}\n        xAxis={[{\n          data: modelComplexity,\n          scaleType: \"linear\",\n          label: \"Model Complexity (Low → High)\",\n          min: COMPLEXITY_MIN,\n          max: COMPLEXITY_MAX,\n        }]}\n        yAxis={[{ label: \"Prediction Error\", min: 0, max: yMax }]}\n        margin={{ left: 100, right: 170, top: 34, bottom: 80 }}\n        slotProps={{ legend: { hidden: true } }}\n        sx={{\n          \"& .MuiChartsAxis-tickLabel\": { fontSize: \"14px\" },\n          \"& .MuiChartsAxis-label\": { fontSize: \"16px\" },\n          \"& .MuiLineElement-series-total\": { strokeWidth: 4 },\n          \"& .MuiLineElement-series-bias\": { strokeWidth: 2.5, strokeDasharray: \"10 6\" },\n          \"& .MuiLineElement-series-variance\": { strokeWidth: 2.5, strokeDasharray: \"3 5\" },\n          \"& .MuiLineElement-series-irreducible\": { strokeWidth: 1.5, strokeDasharray: \"2 4\", opacity: 0.6 },\n        }}\n      >\n        <ChartsReferenceLine\n          x={optimalComplexity}\n          label={`Optimal ≈ ${optimalComplexity.toFixed(1)}`}\n          labelAlign=\"end\"\n          lineStyle={{ stroke: t.ink, strokeDasharray: \"6 4\", strokeWidth: 1.5 }}\n          labelStyle={{ fill: t.ink, fontSize: 13, fontWeight: 600 }}\n        />\n        <Annotations />\n      </LineChart>\n    </Box>\n  );\n}\n"}