{"spec_id":"spc-xbar-r","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// spc-xbar-r: Statistical Process Control Chart (X-bar/R)\n// Library: chartjs 4.4.7 | JavaScript 22.22.3\n// Quality: 90/100 | Created: 2026-06-20\n\nconst t = window.ANYPLOT_TOKENS;\n\n// SPC constants for subgroup size n=5\nconst A2 = 0.577, D3 = 0, D4 = 2.114;\nconst d2 = 2.326, d3c = 0.864;\n\n// Deterministic LCG RNG (seeded, no Math.random)\nlet _seed = 42;\nfunction rand() {\n  _seed = (_seed * 1664525 + 1013904223) >>> 0;\n  return _seed / 4294967295;\n}\nfunction randNorm() {\n  const u = rand() + 1e-9;\n  return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * rand());\n}\n\n// Generate 25 subgroups of n=5 shaft diameter measurements (mm)\nconst N_SAMPLES = 25, N_PER = 5;\nconst PROC_MEAN = 50.0, PROC_STD = 0.3;\n\nconst sampleMeans = [], sampleRanges = [];\nfor (let i = 0; i < N_SAMPLES; i++) {\n  const vals = Array.from({ length: N_PER }, () => PROC_MEAN + PROC_STD * randNorm());\n  sampleMeans.push(vals.reduce((a, b) => a + b) / N_PER);\n  sampleRanges.push(Math.max(...vals) - Math.min(...vals));\n}\n\n// Inject 3 out-of-control points to demonstrate detection capability\nsampleMeans[7]  = PROC_MEAN + 1.05;\nsampleMeans[15] = PROC_MEAN - 1.0;\nsampleMeans[21] = PROC_MEAN + 1.1;\n\n// Compute process-level statistics and control limits\nconst xbarBar = sampleMeans.reduce((a, b) => a + b) / N_SAMPLES;\nconst rBar    = sampleRanges.reduce((a, b) => a + b) / N_SAMPLES;\n\n// X-bar chart limits (±3σ UCL/LCL, ±2σ warning)\nconst xucl = xbarBar + A2 * rBar;\nconst xlcl = xbarBar - A2 * rBar;\nconst xuwl = xbarBar + (2 / 3) * A2 * rBar;\nconst xlwl = xbarBar - (2 / 3) * A2 * rBar;\n\n// R chart limits (D4/D3 factors; n=5 → D3=0 so LCL=0)\nconst rucl = D4 * rBar;\nconst ruwl = rBar + 2 * (d3c / d2) * rBar;\n\nconst labels = sampleMeans.map((_, i) => String(i + 1));\nconst fmt    = v => Math.round(v * 10000) / 10000;\n\nconst OOC_CLR  = t.palette[4];  // #AE3030 matte red — error semantic\nconst XBAR_CLR = t.palette[0];  // #009E73 brand green — first series\nconst RANG_CLR = t.palette[2];  // #4467A3 blue\n\nconst xPointBg = sampleMeans.map(v  => (v  > xucl || v  < xlcl) ? OOC_CLR : XBAR_CLR);\nconst xPointR  = sampleMeans.map(v  => (v  > xucl || v  < xlcl) ? 11 : 7);\nconst rPointBg = sampleRanges.map(v => v > rucl ? OOC_CLR : RANG_CLR);\nconst rPointR  = sampleRanges.map(v => v > rucl ? 11 : 7);\n\nconst hline = val => Array(N_SAMPLES).fill(fmt(val));\n\n// Shared dataset config for horizontal reference lines\nfunction refLine(label, val, color, dash, order) {\n  return {\n    label, data: hline(val),\n    borderColor: color, borderWidth: dash ? 2 : 2.5,\n    borderDash: dash || [], pointRadius: 0, fill: false, tension: 0, order,\n  };\n}\n\n// --- Mount: flex column with title + two chart panels ---\nconst container = document.getElementById(\"container\");\ncontainer.style.cssText = `\n  display:flex; flex-direction:column;\n  background-color:${t.pageBg};\n  padding:24px 32px 18px;\n  box-sizing:border-box;\n`;\n\nconst titleEl = document.createElement(\"div\");\ntitleEl.textContent = \"spc-xbar-r · javascript · chartjs · anyplot.ai\";\ntitleEl.style.cssText = `\n  color:${t.ink}; font-family:sans-serif; font-size:22px; font-weight:600;\n  text-align:center; margin-bottom:14px; flex-shrink:0;\n`;\ncontainer.appendChild(titleEl);\n\nconst topDiv = document.createElement(\"div\");\ntopDiv.style.cssText = \"flex:1; position:relative; min-height:0; margin-bottom:10px;\";\nconst topCanvas = document.createElement(\"canvas\");\ntopDiv.appendChild(topCanvas);\ncontainer.appendChild(topDiv);\n\nconst botDiv = document.createElement(\"div\");\nbotDiv.style.cssText = \"flex:1; position:relative; min-height:0;\";\nconst botCanvas = document.createElement(\"canvas\");\nbotDiv.appendChild(botCanvas);\ncontainer.appendChild(botDiv);\n\n// Shared legend label filter — hides datasets with labels starting \"_\"\nconst legendFilter = item => !item.text.startsWith(\"_\");\n\n// Y-axis ranges with enough headroom for OOC points and limit labels\nconst xSpan = xucl - xlcl;\nconst xPad  = xSpan * 0.45;\nconst xMin  = Math.min(xlcl, ...sampleMeans) - xPad;\nconst xMax  = Math.max(xucl, ...sampleMeans) + xPad;\nconst rMax  = Math.max(rucl, ...sampleRanges) + rucl * 0.25;\n\n// X-bar chart (top panel) — x-axis ticks/title suppressed; shared x-axis lives on bottom panel\nnew Chart(topCanvas, {\n  type: \"line\",\n  data: {\n    labels,\n    datasets: [\n      refLine(\"±2σ Warning\", xuwl, t.palette[3], [4, 4], 5),\n      refLine(\"_xlwl\",       xlwl, t.palette[3], [4, 4], 5),\n      refLine(\"UCL / LCL\",  xucl, OOC_CLR,      [8, 4], 4),\n      refLine(\"_xlcl\",       xlcl, OOC_CLR,      [8, 4], 4),\n      refLine(\"Center (X̅̅)\", xbarBar, t.inkSoft, null, 3),\n      {\n        label: \"X̅ (sample mean)\",\n        data: sampleMeans.map(fmt),\n        borderColor: XBAR_CLR, borderWidth: 2,\n        pointBackgroundColor: xPointBg, pointBorderColor: xPointBg,\n        pointRadius: xPointR, pointHoverRadius: xPointR,\n        fill: false, tension: 0, order: 1,\n      },\n    ],\n  },\n  options: {\n    responsive: true, maintainAspectRatio: false, animation: false,\n    plugins: {\n      title: { display: false },\n      legend: { labels: { color: t.inkSoft, font: { size: 13 }, boxWidth: 20, padding: 8, filter: legendFilter } },\n      tooltip: { enabled: false },\n    },\n    scales: {\n      x: {\n        ticks:  { display: false },\n        grid:   { color: t.grid },\n        border: { display: false },\n        title:  { display: false },\n      },\n      y: {\n        min: xMin, max: xMax,\n        ticks:  { color: t.inkSoft, font: { size: 12 }, maxTicksLimit: 6 },\n        grid:   { color: t.grid },\n        border: { display: false },\n        title:  { display: true, text: \"Sample Mean X̅ (mm)\", color: t.ink, font: { size: 14 } },\n      },\n    },\n  },\n});\n\n// R chart (bottom panel)\nnew Chart(botCanvas, {\n  type: \"line\",\n  data: {\n    labels,\n    datasets: [\n      refLine(\"±2σ Warning\", ruwl, t.palette[3], [4, 4], 4),\n      refLine(\"UCL\",         rucl, OOC_CLR,      [8, 4], 3),\n      refLine(\"Center (R̅)\", rBar, t.inkSoft,    null,   2),\n      {\n        label: \"R (sample range)\",\n        data: sampleRanges.map(fmt),\n        borderColor: RANG_CLR, borderWidth: 2,\n        pointBackgroundColor: rPointBg, pointBorderColor: rPointBg,\n        pointRadius: rPointR, pointHoverRadius: rPointR,\n        fill: false, tension: 0, order: 1,\n      },\n    ],\n  },\n  options: {\n    responsive: true, maintainAspectRatio: false, animation: false,\n    plugins: {\n      title: { display: false },\n      legend: { labels: { color: t.inkSoft, font: { size: 13 }, boxWidth: 20, padding: 8, filter: legendFilter } },\n      tooltip: { enabled: false },\n    },\n    scales: {\n      x: {\n        ticks:  { color: t.inkSoft, font: { size: 12 } },\n        grid:   { color: t.grid },\n        border: { display: false },\n        title:  { display: true, text: \"Sample Number\", color: t.ink, font: { size: 14 } },\n      },\n      y: {\n        min: 0, max: rMax,\n        ticks:  { color: t.inkSoft, font: { size: 12 }, maxTicksLimit: 6 },\n        grid:   { color: t.grid },\n        border: { display: false },\n        title:  { display: true, text: \"Sample Range R (mm)\", color: t.ink, font: { size: 14 } },\n      },\n    },\n  },\n});\n"}