{"spec_id":"line-pca-variance-cumulative","library":"muix","language":"javascript","code":"// anyplot.ai\n// line-pca-variance-cumulative: Cumulative Explained Variance for PCA Component Selection\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-08-26\nimport { LineChart } from \"@mui/x-charts/LineChart\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { Box, Typography } from \"@mui/material\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// A 16-sensor vibration/temperature/pressure array on a rotating pump feeds a\n// predictive-maintenance model. PCA on the standardized sensor readings finds\n// a handful of latent modes (imbalance, misalignment, bearing wear, thermal\n// drift, ...) that explain most of the signal — the classic \"how many\n// components do we keep\" question. Eigenvalues decay roughly geometrically,\n// which is typical for correlated sensor arrays and produces the elbow shape\n// the cumulative curve is meant to reveal.\nconst rawEigenvalues = [\n  28, 17, 13, 9.5, 7, 5.5, 4.2, 3.2, 2.4, 1.9, 1.5, 1.2, 0.95, 0.75, 0.6, 0.5,\n];\nconst totalVariance = rawEigenvalues.reduce((sum, v) => sum + v, 0);\nconst varianceRatio = rawEigenvalues.map((v) => v / totalVariance);\n\nconst cumulativeVariance = [];\nvarianceRatio.reduce((sum, v, i) => {\n  const next = sum + v;\n  cumulativeVariance[i] = next;\n  return next;\n}, 0);\n\nconst componentNumbers = rawEigenvalues.map((_, i) => i + 1);\n\n// First component count whose cumulative curve reaches each guidance threshold.\nconst componentsFor = (threshold) => cumulativeVariance.findIndex((v) => v >= threshold) + 1;\nconst ninetyPctComponents = componentsFor(0.9);\nconst ninetyFivePctComponents = componentsFor(0.95);\n\nconst TITLE = \"line-pca-variance-cumulative · 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 padding = { top: 28, right: 48, bottom: 16, left: 16 };\n  const titleBlockHeight = 56;\n  // MUI X's y-axis `label` prop offsets itself from a hardcoded tick-width\n  // guess rather than the tick labels' real measured width, so it collides\n  // with the \"100%\"/\"90%\" tick text. A hand-rotated label in its own flex\n  // column sidesteps that and gives predictable, collision-free spacing.\n  const yLabelWidth = 32;\n  const chartWidth = size.width - padding.left - padding.right - yLabelWidth;\n  const chartHeight = size.height - padding.top - padding.bottom - titleBlockHeight;\n\n  return (\n    <Box\n      sx={{\n        width: size.width,\n        height: size.height,\n        boxSizing: \"border-box\",\n        padding: `${padding.top}px ${padding.right}px ${padding.bottom}px ${padding.left}px`,\n        display: \"flex\",\n        flexDirection: \"column\",\n      }}\n    >\n      <Typography sx={{ fontSize: 22, fontWeight: 600, color: \"text.primary\", mb: \"20px\", lineHeight: 1 }}>\n        {TITLE}\n      </Typography>\n      <Box sx={{ display: \"flex\", flexDirection: \"row\", height: chartHeight }}>\n        <Box sx={{ width: yLabelWidth, display: \"flex\", alignItems: \"center\", justifyContent: \"center\" }}>\n          <Typography\n            sx={{\n              fontSize: 16,\n              color: \"text.secondary\",\n              whiteSpace: \"nowrap\",\n              transform: \"rotate(-90deg)\",\n            }}\n          >\n            Explained Variance\n          </Typography>\n        </Box>\n        <LineChart\n          width={chartWidth}\n          height={chartHeight}\n          skipAnimation\n          series={[\n            {\n              id: \"cumulative\",\n              label: \"Cumulative variance explained\",\n              data: cumulativeVariance,\n              curve: \"monotoneX\",\n              showMark: true,\n              color: t.palette[0],\n              valueFormatter: (v) => `${(v * 100).toFixed(1)}%`,\n            },\n            {\n              id: \"individual\",\n              label: \"Variance from this component\",\n              data: varianceRatio,\n              curve: \"monotoneX\",\n              showMark: true,\n              color: t.palette[2],\n              valueFormatter: (v) => `${(v * 100).toFixed(1)}%`,\n            },\n          ]}\n          xAxis={[\n            {\n              data: componentNumbers,\n              scaleType: \"point\",\n              label: \"Number of Principal Components\",\n              valueFormatter: (v) => `${v}`,\n              tickLabelStyle: { fontSize: 14 },\n              labelStyle: { fontSize: 16 },\n            },\n          ]}\n          yAxis={[\n            {\n              min: 0,\n              max: 1,\n              valueFormatter: (v) => `${Math.round(v * 100)}%`,\n              tickLabelStyle: { fontSize: 14 },\n            },\n          ]}\n          grid={{ horizontal: true }}\n          slotProps={{\n            legend: {\n              direction: \"row\",\n              labelStyle: { fontSize: 14 },\n              itemMarkWidth: 18,\n              itemMarkHeight: 10,\n              markGap: 8,\n            },\n          }}\n          sx={{\n            \"& .MuiLineElement-series-cumulative\": { strokeWidth: 3 },\n            \"& .MuiLineElement-series-individual\": { strokeWidth: 2, strokeDasharray: \"6 4\" },\n            \"& .MuiMarkElement-series-cumulative\": { r: 5 },\n            \"& .MuiMarkElement-series-individual\": { r: 4 },\n            \"& .MuiChartsGrid-line\": { strokeDasharray: \"4 3\" },\n          }}\n        >\n          <ChartsReferenceLine\n            y={0.9}\n            label={`90% → ${ninetyPctComponents} components`}\n            labelAlign=\"start\"\n            lineStyle={{ stroke: t.amber, strokeWidth: 1.5, strokeDasharray: \"8 4\" }}\n            labelStyle={{ fontSize: 13, fill: t.inkSoft }}\n          />\n          <ChartsReferenceLine\n            y={0.95}\n            label={`95% → ${ninetyFivePctComponents} components`}\n            labelAlign=\"start\"\n            lineStyle={{ stroke: t.amber, strokeWidth: 1.5, strokeDasharray: \"8 4\" }}\n            labelStyle={{ fontSize: 13, fill: t.inkSoft }}\n          />\n        </LineChart>\n      </Box>\n    </Box>\n  );\n}\n"}