{"spec_id":"histogram-capability","library":"muix","language":"javascript","code":"// anyplot.ai\n// histogram-capability: Process Capability Plot with Specification Limits\n// Library: muix 7.29.1 | JavaScript 22.22.3\n// Quality: 92/100 | Created: 2026-06-20\n//# anyplot-orientation: landscape\n// anyplot.ai\n// histogram-capability: Process Capability Plot with Specification Limits\n// Library: MUI X Charts | React | Node 22\n// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.\n// Quality: pending | Created: 2026-06-20\n\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { LinePlot } from \"@mui/x-charts/LineChart\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { ChartsYAxis } from \"@mui/x-charts/ChartsYAxis\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport { ChartsGrid } from \"@mui/x-charts/ChartsGrid\";\nimport { 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\n// Deterministic LCG + Box-Muller — no seeded Math.random in the browser\nlet seed = 42;\nfunction lcg() {\n  seed = (Math.imul(1664525, seed) + 1013904223) >>> 0;\n  return seed / 0x100000000;\n}\nfunction randn() {\n  const u1 = Math.max(lcg(), 1e-10);\n  const u2 = lcg();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\n// Specification limits for CNC shaft diameter (mm)\nconst LSL = 9.95;\nconst USL = 10.05;\nconst TARGET = 10.00;\n\n// Generate 200 shaft diameter measurements — mean slightly off-target to show Cp vs Cpk difference\nconst N = 200;\nconst measurements = Array.from({ length: N }, () => 10.005 + randn() * 0.015);\n\n// Sample statistics\nconst sampleMean = measurements.reduce((s, v) => s + v, 0) / N;\nconst sampleSigma = Math.sqrt(\n  measurements.reduce((s, v) => s + (v - sampleMean) ** 2, 0) / (N - 1)\n);\n\n// Process capability indices\nconst Cp = (USL - LSL) / (6 * sampleSigma);\nconst Cpk = Math.min(\n  (USL - sampleMean) / (3 * sampleSigma),\n  (sampleMean - LSL) / (3 * sampleSigma)\n);\n\n// 15 histogram bins from 9.925 to 10.075 (width=0.010)\n// Tightened X_MAX to 10.075 to eliminate ~30% empty space beyond USL\nconst X_MIN = 9.925;\nconst X_MAX = 10.075;\nconst BIN_WIDTH = 0.01;\nconst N_BINS = 15;\nconst binCenters = Array.from(\n  { length: N_BINS },\n  (_, i) => parseFloat((X_MIN + (i + 0.5) * BIN_WIDTH).toFixed(3))\n);\n\n// Count measurements into bins\nconst histCounts = new Array(N_BINS).fill(0);\nmeasurements.forEach((v) => {\n  const idx = Math.floor((v - X_MIN) / BIN_WIDTH);\n  if (idx >= 0 && idx < N_BINS) histCounts[idx]++;\n});\n\n// Y-axis ceiling — round up to nearest 5\nconst Y_MAX = Math.ceil((Math.max(...histCounts) + 2) / 5) * 5;\n\n// Normal distribution PDF scaled to histogram area (so curve and bars share the same y-scale)\nconst normalValues = binCenters.map((x) => {\n  const z = (x - sampleMean) / sampleSigma;\n  return (Math.exp(-0.5 * z * z) / (sampleSigma * Math.sqrt(2 * Math.PI))) * N * BIN_WIDTH;\n});\n\n// Imprint palette colors\nconst BRAND = t.palette[0]; // #009E73 — histogram bars (first categorical series)\nconst BLUE  = t.palette[2]; // #4467A3 — normal fit curve\nconst RED   = t.palette[4]; // #AE3030 — LSL / USL violation limits\n\n// Title sizing (scale down for longer-than-67-char title)\nconst TITLE = \"histogram-capability · javascript · muix · anyplot.ai\";\nconst titleSize = Math.max(11, Math.round(22 * 67 / TITLE.length));\n\n// Capability status label + colour\nconst capStatus =\n  Cpk >= 1.33 ? \"Fully Capable\" :\n  Cpk >= 1.00 ? \"Marginally Capable\" : \"Not Capable\";\nconst statusColor = Cpk >= 1.33 ? BRAND : Cpk >= 1.00 ? t.amber : RED;\n\nconst FONT = \"system-ui, -apple-system, sans-serif\";\n\n// ---- Custom histogram bars drawn as SVG rects (uses drawing-area coordinate map) ----\nfunction HistogramBars() {\n  const { left, top, width, height } = useDrawingArea();\n  const binPx = (BIN_WIDTH / (X_MAX - X_MIN)) * width;\n  const toX = (v) => left + ((v - X_MIN) / (X_MAX - X_MIN)) * width;\n  const toY = (c) => top + (1 - c / Y_MAX) * height;\n  return (\n    <g>\n      {histCounts.map((c, i) => {\n        const bx = toX(X_MIN + i * BIN_WIDTH);\n        const by = toY(c);\n        const bh = top + height - by;\n        return (\n          <rect\n            key={i}\n            x={bx + 0.5}\n            y={by}\n            width={Math.max(0, binPx - 1)}\n            height={Math.max(0, bh)}\n            fill={BRAND}\n            fillOpacity={0.82}\n          />\n        );\n      })}\n    </g>\n  );\n}\n\n// ---- Annotation panel in the right margin: capability indices + series legend ----\nfunction AnnotationPanel() {\n  const { left, top, width } = useDrawingArea();\n  const px = left + width + 24;\n  const py = top + 20;\n  const lh = 24;\n\n  return (\n    <g fontFamily={FONT}>\n      {/* Elevated background separates the panel from the chart area */}\n      <rect\n        x={px - 12}\n        y={py - 12}\n        width={176}\n        height={232}\n        fill={t.elevatedBg}\n        rx={5}\n        ry={5}\n      />\n      {/* Section header — small-caps label above the metric values */}\n      <text x={px} y={py + 4} fontSize={11} fontWeight=\"800\" fill={t.inkSoft} letterSpacing={2}>\n        CAPABILITY\n      </text>\n      {/* Cp and Cpk values — larger, monospace, prominently readable */}\n      <text x={px} y={py + lh + 2} fontSize={16} fill={t.ink} fontFamily=\"monospace\" fontWeight=\"500\">\n        {`Cp  = ${Cp.toFixed(3)}`}\n      </text>\n      <text x={px} y={py + 2 * lh + 2} fontSize={16} fill={t.ink} fontFamily=\"monospace\" fontWeight=\"500\">\n        {`Cpk = ${Cpk.toFixed(3)}`}\n      </text>\n      {/* Divider */}\n      <line\n        x1={px} y1={py + 2 * lh + 14}\n        x2={px + 152} y2={py + 2 * lh + 14}\n        stroke={t.inkSoft} strokeWidth={0.8} strokeOpacity={0.4}\n      />\n      {/* Status */}\n      <text x={px} y={py + 3 * lh + 14} fontSize={13} fontWeight=\"600\" fill={statusColor}>\n        {capStatus}\n      </text>\n      {/* Legend — histogram bars */}\n      <rect x={px} y={py + 4 * lh + 24} width={20} height={13} fill={BRAND} fillOpacity={0.82} />\n      <text x={px + 27} y={py + 4 * lh + 35} fontSize={13} fill={t.ink}>Measurements</text>\n      {/* Legend — normal fit line */}\n      <line\n        x1={px} y1={py + 5 * lh + 26} x2={px + 20} y2={py + 5 * lh + 26}\n        stroke={BLUE} strokeWidth={2.5}\n      />\n      <text x={px + 27} y={py + 5 * lh + 31} fontSize={13} fill={t.ink}>Normal fit</text>\n      {/* Legend — LSL/USL lines */}\n      <line\n        x1={px} y1={py + 6 * lh + 28} x2={px + 20} y2={py + 6 * lh + 28}\n        stroke={RED} strokeWidth={2} strokeDasharray=\"5 3\"\n      />\n      <text x={px + 27} y={py + 6 * lh + 33} fontSize={13} fill={t.ink}>LSL / USL</text>\n      {/* Legend — Target line */}\n      <line\n        x1={px} y1={py + 7 * lh + 30} x2={px + 20} y2={py + 7 * lh + 30}\n        stroke={BRAND} strokeWidth={2} strokeDasharray=\"8 4\"\n      />\n      <text x={px + 27} y={py + 7 * lh + 35} fontSize={13} fill={t.ink}>Target</text>\n    </g>\n  );\n}\n\n// ---- Main component ----\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n  const TITLE_H = 56;\n\n  return (\n    <Box\n      sx={{\n        width,\n        height,\n        bgcolor: t.pageBg,\n        display: \"flex\",\n        flexDirection: \"column\",\n        overflow: \"hidden\",\n      }}\n    >\n      {/* Chart title */}\n      <Typography\n        sx={{\n          fontSize: titleSize,\n          fontWeight: 500,\n          color: t.ink,\n          pt: \"16px\",\n          px: \"40px\",\n          pb: 0,\n          lineHeight: 1.2,\n          fontFamily: FONT,\n        }}\n      >\n        {TITLE}\n      </Typography>\n\n      {/* Chart body */}\n      <ChartContainer\n        width={width}\n        height={height - TITLE_H}\n        series={[\n          {\n            type: \"line\",\n            id: \"normal\",\n            data: normalValues,\n            label: \"Normal fit\",\n            color: BLUE,\n            showMark: false,\n            curve: \"catmullRom\",\n          },\n        ]}\n        xAxis={[\n          {\n            data: binCenters,\n            scaleType: \"linear\",\n            min: X_MIN,\n            max: X_MAX,\n            // Every 4th bin tick: 9.930, 9.970, 10.010, 10.050 — better readability\n            tickInterval: binCenters.filter((_, i) => i % 4 === 0),\n            valueFormatter: (v) => v.toFixed(3),\n          },\n        ]}\n        yAxis={[{ min: 0, max: Y_MAX }]}\n        margin={{ top: 28, right: 196, bottom: 80, left: 82 }}\n        skipAnimation\n      >\n        <ChartsGrid\n          horizontal\n          sx={{ \"& line\": { stroke: t.grid, strokeWidth: 0.8 } }}\n        />\n\n        {/* Histogram bars (behind the normal-fit line) */}\n        <HistogramBars />\n\n        {/* Normal distribution fit line */}\n        <LinePlot skipAnimation />\n\n        {/* Specification limit lines */}\n        <ChartsReferenceLine\n          x={LSL}\n          label=\"LSL\"\n          labelAlign=\"start\"\n          lineStyle={{ stroke: RED, strokeDasharray: \"6 3\", strokeWidth: 2.5 }}\n          labelStyle={{ fill: RED, fontSize: 14, fontWeight: \"bold\", fontFamily: FONT }}\n        />\n        <ChartsReferenceLine\n          x={USL}\n          label=\"USL\"\n          labelAlign=\"start\"\n          lineStyle={{ stroke: RED, strokeDasharray: \"6 3\", strokeWidth: 2.5 }}\n          labelStyle={{ fill: RED, fontSize: 14, fontWeight: \"bold\", fontFamily: FONT }}\n        />\n        <ChartsReferenceLine\n          x={TARGET}\n          label=\"Target\"\n          labelAlign=\"start\"\n          lineStyle={{ stroke: BRAND, strokeDasharray: \"8 4\", strokeWidth: 2 }}\n          labelStyle={{ fill: BRAND, fontSize: 14, fontWeight: \"bold\", fontFamily: FONT }}\n        />\n\n        {/* Axes */}\n        <ChartsXAxis\n          label=\"Shaft Diameter (mm)\"\n          tickLabelStyle={{ fontSize: 14, fill: t.inkSoft, fontFamily: FONT }}\n          labelStyle={{ fontSize: 15, fill: t.ink, fontFamily: FONT }}\n        />\n        <ChartsYAxis\n          label=\"Count\"\n          tickLabelStyle={{ fontSize: 13, fill: t.inkSoft, fontFamily: FONT }}\n          labelStyle={{ fontSize: 15, fill: t.ink, fontFamily: FONT }}\n        />\n\n        {/* Capability indices + legend in right margin */}\n        <AnnotationPanel />\n      </ChartContainer>\n    </Box>\n  );\n}\n"}