{"spec_id":"learning-curve-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// learning-curve-basic: Model Learning Curve\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-09-05\nimport { LineChart } from \"@mui/x-charts/LineChart\";\nimport { Box, Typography } from \"@mui/material\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// A spam-filter classifier evaluated at 10 training-set sizes via 8-fold\n// cross-validation. Training accuracy drifts slightly downward as the fixed\n// model has to fit more varied examples, while validation accuracy climbs and\n// the two converge — but don't fully close — as more labeled email tightens\n// the ±1 std-dev spread across folds. That persistent, narrowing gap is the\n// classic \"more data would still help a bit\" diagnosis a learning curve exists\n// to reveal.\nconst trainSizes = [200, 400, 800, 1200, 1600, 2000, 2400, 2800, 3200, 3600];\nconst trainMean = [0.985, 0.978, 0.968, 0.96, 0.955, 0.951, 0.948, 0.946, 0.944, 0.943];\nconst trainStd = [0.012, 0.01, 0.009, 0.008, 0.007, 0.006, 0.006, 0.005, 0.005, 0.004];\nconst valMean = [0.76, 0.82, 0.868, 0.892, 0.905, 0.913, 0.919, 0.923, 0.926, 0.928];\nconst valStd = [0.055, 0.045, 0.035, 0.028, 0.023, 0.02, 0.017, 0.015, 0.014, 0.013];\n\n// ±1 std-dev confidence band via the stacked-area trick: a fully transparent\n// \"lower\" series carries the band's floor, and a \"width\" series (2×std)\n// stacked on top of it fills exactly the [mean-std, mean+std] interval.\nconst trainLower = trainMean.map((m, i) => m - trainStd[i]);\nconst trainWidth = trainStd.map((s) => 2 * s);\nconst valLower = valMean.map((m, i) => m - valStd[i]);\nconst valWidth = valStd.map((s) => 2 * s);\n\nconst TITLE = \"learning-curve-basic · javascript · muix · anyplot.ai\";\n\n// --- Chart (default-exported component — the harness mounts it) ------------\nexport default function Chart() {\n  const size = window.ANYPLOT_SIZE;\n  const titleH = 64;\n  const chartH = size.height - titleH;\n\n  return (\n    <Box\n      sx={{\n        width: size.width,\n        height: size.height,\n        boxSizing: \"border-box\",\n        display: \"flex\",\n        flexDirection: \"column\",\n      }}\n    >\n      <Box sx={{ height: titleH, display: \"flex\", alignItems: \"center\", justifyContent: \"center\" }}>\n        <Typography sx={{ color: t.ink, fontSize: 22, fontWeight: 600 }}>{TITLE}</Typography>\n      </Box>\n      <LineChart\n        width={size.width}\n        height={chartH}\n        skipAnimation\n        margin={{ top: 20, right: 40, bottom: 88, left: 108 }}\n        series={[\n          { id: \"trainLower\", data: trainLower, stack: \"trainBand\", area: true, showMark: false, color: t.palette[0] },\n          { id: \"trainBand\", data: trainWidth, stack: \"trainBand\", area: true, showMark: false, color: t.palette[0] },\n          {\n            id: \"trainMean\",\n            data: trainMean,\n            label: \"Training score\",\n            color: t.palette[0],\n            showMark: true,\n            curve: \"monotoneX\",\n            valueFormatter: (v) => `${(v * 100).toFixed(1)}%`,\n          },\n          { id: \"valLower\", data: valLower, stack: \"valBand\", area: true, showMark: false, color: t.palette[1] },\n          { id: \"valBand\", data: valWidth, stack: \"valBand\", area: true, showMark: false, color: t.palette[1] },\n          {\n            id: \"valMean\",\n            data: valMean,\n            label: \"Validation score\",\n            color: t.palette[1],\n            showMark: true,\n            curve: \"monotoneX\",\n            valueFormatter: (v) => `${(v * 100).toFixed(1)}%`,\n          },\n        ]}\n        xAxis={[\n          {\n            data: trainSizes,\n            scaleType: \"linear\",\n            label: \"Training Set Size (labeled emails)\",\n            tickLabelStyle: { fontSize: 14 },\n            labelStyle: { fontSize: 16 },\n          },\n        ]}\n        yAxis={[\n          {\n            min: 0.68,\n            max: 1.0,\n            label: \"Classification Accuracy (%)\",\n            valueFormatter: (v) => `${Math.round(v * 100)}%`,\n            // tickFontSize drives the label's reserved offset from the tick\n            // text (MUI X sizes that gap off this prop, not tickLabelStyle),\n            // so it must stay wide enough for a 4-char \"100%\" tick.\n            tickFontSize: 34,\n            tickLabelStyle: { fontSize: 14 },\n            labelStyle: { fontSize: 16 },\n          },\n        ]}\n        grid={{ horizontal: true }}\n        slotProps={{\n          legend: {\n            position: { vertical: \"bottom\", horizontal: \"middle\" },\n            direction: \"row\",\n            labelStyle: { fontSize: 14 },\n          },\n        }}\n        sx={{\n          \"& .MuiLineElement-series-trainMean\": { strokeWidth: 3 },\n          \"& .MuiLineElement-series-valMean\": { strokeWidth: 3 },\n          \"& .MuiLineElement-series-trainLower, & .MuiLineElement-series-valLower\": { stroke: \"none\" },\n          \"& .MuiLineElement-series-trainBand\": { stroke: t.palette[0], strokeWidth: 1, strokeOpacity: 0.5 },\n          \"& .MuiLineElement-series-valBand\": { stroke: t.palette[1], strokeWidth: 1, strokeOpacity: 0.5 },\n          \"& .MuiAreaElement-series-trainLower, & .MuiAreaElement-series-valLower\": { fillOpacity: 0 },\n          \"& .MuiAreaElement-series-trainBand, & .MuiAreaElement-series-valBand\": { fillOpacity: 0.18 },\n          \"& .MuiMarkElement-series-trainMean, & .MuiMarkElement-series-valMean\": {\n            r: 9,\n            stroke: t.pageBg,\n            strokeWidth: 2,\n          },\n          \"& .MuiChartsGrid-line\": { stroke: t.grid },\n        }}\n      />\n    </Box>\n  );\n}\n"}