{"spec_id":"histogram-capability","library":"d3","language":"javascript","code":"// anyplot.ai\n// histogram-capability: Process Capability Plot with Specification Limits\n// Library: d3 7.9.0 | JavaScript 22.22.3\n// Quality: 86/100 | Created: 2026-06-20\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 80, right: 100, bottom: 80, left: 90 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Data (deterministic LCG + Box-Muller) ---\nlet seed = 42;\nfunction lcg() {\n  seed = (seed * 1664525 + 1013904223) & 0xffffffff;\n  return (seed >>> 0) / 4294967296;\n}\nfunction randNorm() {\n  const u = lcg() || 1e-10;\n  const v = lcg();\n  return Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);\n}\n\nconst LSL = 9.95, USL = 10.05, TARGET = 10.00;\nconst N = 200;\nconst trueMean = 10.001, trueSigma = 0.012;\nconst measurements = Array.from({ length: N }, () => trueMean + randNorm() * trueSigma);\n\n// --- Statistics ---\nconst mean = d3.mean(measurements);\nconst sigma = d3.deviation(measurements);\nconst Cp = (USL - LSL) / (6 * sigma);\nconst Cpk = Math.min((USL - mean) / (3 * sigma), (mean - LSL) / (3 * sigma));\n\n// --- Histogram bins ---\nconst xPad = (USL - LSL) * 0.18;\nconst xMin = LSL - xPad;\nconst xMax = USL + xPad;\n\nconst bins = d3.bin()\n  .domain([xMin, xMax])\n  .thresholds(22)(measurements);\n\nconst binWidth = bins[0].x1 - bins[0].x0;\nconst maxDensity = d3.max(bins, (d) => d.length / (N * binWidth));\n\n// Normal PDF\nfunction normalPDF(xv, mu, s) {\n  return Math.exp(-0.5 * ((xv - mu) / s) ** 2) / (s * Math.sqrt(2 * Math.PI));\n}\nconst maxPDF = normalPDF(mean, mean, sigma);\nconst yMax = Math.max(maxDensity, maxPDF) * 1.25;\n\n// --- SVG mount ---\nconst svg = d3.select(\"#container\")\n  .append(\"svg\").attr(\"width\", width).attr(\"height\", height);\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// --- Scales ---\nconst x = d3.scaleLinear().domain([xMin, xMax]).range([0, iw]);\nconst y = d3.scaleLinear().domain([0, yMax]).range([ih, 0]).nice();\n\n// --- Y grid ---\ng.append(\"g\").attr(\"class\", \"grid\")\n  .call(d3.axisLeft(y).tickSize(-iw).ticks(6).tickFormat(\"\"))\n  .call((ag) => ag.select(\".domain\").remove())\n  .call((ag) => ag.selectAll(\"line\").attr(\"stroke\", t.grid).attr(\"stroke-width\", 1));\n\n// --- In-spec zone (d3.area() fill under normal curve between LSL and USL) ---\nconst inSpecPoints = Array.from({ length: 201 }, (_, i) => {\n  const xv = LSL + (i / 200) * (USL - LSL);\n  return { x: xv, y: normalPDF(xv, mean, sigma) };\n});\nconst inSpecArea = d3.area()\n  .x((d) => x(d.x))\n  .y0(ih)\n  .y1((d) => y(d.y))\n  .curve(d3.curveBasis);\ng.append(\"path\")\n  .datum(inSpecPoints)\n  .attr(\"fill\", t.palette[0])\n  .attr(\"opacity\", 0.12)\n  .attr(\"d\", inSpecArea);\n\n// --- Histogram bars ---\ng.selectAll(\".bar\").data(bins).join(\"rect\")\n  .attr(\"class\", \"bar\")\n  .attr(\"x\", (d) => x(d.x0) + 1)\n  .attr(\"y\", (d) => y(d.length / (N * binWidth)))\n  .attr(\"width\", (d) => Math.max(0, x(d.x1) - x(d.x0) - 2))\n  .attr(\"height\", (d) => Math.max(0, ih - y(d.length / (N * binWidth))))\n  .attr(\"fill\", t.palette[0])\n  .attr(\"opacity\", 0.65);\n\n// --- Normal distribution curve ---\nconst curvePoints = d3.range(xMin, xMax, (xMax - xMin) / 300).map((xv) => ({\n  x: xv,\n  y: normalPDF(xv, mean, sigma),\n}));\nconst curveLine = d3.line().x((d) => x(d.x)).y((d) => y(d.y)).curve(d3.curveBasis);\ng.append(\"path\")\n  .datum(curvePoints)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.palette[2])\n  .attr(\"stroke-width\", 3)\n  .attr(\"d\", curveLine);\n\n// --- Spec limit lines (LSL / USL) ---\nfor (const [val, label] of [[LSL, \"LSL\"], [USL, \"USL\"]]) {\n  g.append(\"line\")\n    .attr(\"x1\", x(val)).attr(\"x2\", x(val))\n    .attr(\"y1\", 0).attr(\"y2\", ih)\n    .attr(\"stroke\", t.palette[4])\n    .attr(\"stroke-width\", 2.5)\n    .attr(\"stroke-dasharray\", \"9,5\");\n  const lxOff = val < TARGET ? -6 : 6;\n  const lAnchor = val < TARGET ? \"end\" : \"start\";\n  g.append(\"text\")\n    .attr(\"x\", x(val) + lxOff)\n    .attr(\"y\", 18)\n    .attr(\"text-anchor\", lAnchor)\n    .attr(\"fill\", t.palette[4])\n    .style(\"font-size\", \"14px\")\n    .style(\"font-weight\", \"700\")\n    .text(label);\n}\n\n// --- Target line ---\ng.append(\"line\")\n  .attr(\"x1\", x(TARGET)).attr(\"x2\", x(TARGET))\n  .attr(\"y1\", 0).attr(\"y2\", ih)\n  .attr(\"stroke\", t.palette[3])\n  .attr(\"stroke-width\", 2)\n  .attr(\"stroke-dasharray\", \"6,4\");\ng.append(\"text\")\n  .attr(\"x\", x(TARGET) + 6)\n  .attr(\"y\", 38)\n  .attr(\"text-anchor\", \"start\")\n  .attr(\"fill\", t.palette[3])\n  .style(\"font-size\", \"14px\")\n  .style(\"font-weight\", \"700\")\n  .text(\"Target\");\n\n// --- Axes ---\nconst xAxis = g.append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x).ticks(8).tickFormat(d3.format(\".3f\")));\nconst yAxis = g.append(\"g\")\n  .call(d3.axisLeft(y).ticks(6));\n\nfor (const ax of [xAxis, yAxis]) {\n  ax.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\n  ax.selectAll(\"line\").attr(\"stroke\", t.grid);\n  ax.select(\".domain\").remove();\n}\n\n// --- Axis labels ---\nsvg.append(\"text\")\n  .attr(\"x\", margin.left + iw / 2)\n  .attr(\"y\", height - 20)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"16px\")\n  .text(\"Shaft Diameter (mm)\");\n\nsvg.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -(margin.top + ih / 2))\n  .attr(\"y\", 22)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"16px\")\n  .text(\"Density\");\n\n// --- Capability annotation box ---\nconst boxW = 195, boxH = 74;\nconst boxX = iw - boxW - 12;\nconst boxY = ih * 0.08;\ng.append(\"rect\")\n  .attr(\"x\", boxX).attr(\"y\", boxY)\n  .attr(\"width\", boxW).attr(\"height\", boxH)\n  .attr(\"fill\", t.elevatedBg)\n  .attr(\"rx\", 6)\n  .attr(\"stroke\", t.grid)\n  .attr(\"stroke-width\", 1);\ng.append(\"text\")\n  .attr(\"x\", boxX + boxW / 2).attr(\"y\", boxY + 26)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"15px\")\n  .style(\"font-weight\", \"700\")\n  .text(`Cp = ${Cp.toFixed(2)}`);\ng.append(\"text\")\n  .attr(\"x\", boxX + boxW / 2).attr(\"y\", boxY + 52)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"15px\")\n  .style(\"font-weight\", \"700\")\n  .text(`Cpk = ${Cpk.toFixed(2)}`);\n\n// --- Legend ---\nconst legendItems = [\n  { label: \"Measurements\", color: t.palette[0], shape: \"rect\" },\n  { label: \"Normal fit\",   color: t.palette[2], shape: \"line\" },\n  { label: \"LSL / USL\",   color: t.palette[4], shape: \"dash\" },\n  { label: \"Target\",       color: t.palette[3], shape: \"dash\" },\n];\nconst lx0 = 14, ly0 = 14;\nlegendItems.forEach((item, i) => {\n  const iy = ly0 + i * 26;\n  if (item.shape === \"rect\") {\n    g.append(\"rect\")\n      .attr(\"x\", lx0).attr(\"y\", iy - 10)\n      .attr(\"width\", 20).attr(\"height\", 14)\n      .attr(\"fill\", item.color).attr(\"opacity\", 0.65);\n  } else {\n    g.append(\"line\")\n      .attr(\"x1\", lx0).attr(\"x2\", lx0 + 20)\n      .attr(\"y1\", iy - 3).attr(\"y2\", iy - 3)\n      .attr(\"stroke\", item.color)\n      .attr(\"stroke-width\", item.shape === \"dash\" ? 2.5 : 3)\n      .attr(\"stroke-dasharray\", item.shape === \"dash\" ? \"6,3\" : null);\n  }\n  g.append(\"text\")\n    .attr(\"x\", lx0 + 28).attr(\"y\", iy)\n    .attr(\"fill\", t.inkSoft)\n    .style(\"font-size\", \"14px\")\n    .text(item.label);\n});\n\n// --- Title ---\nsvg.append(\"text\")\n  .attr(\"x\", width / 2).attr(\"y\", 46)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"histogram-capability · javascript · d3 · anyplot.ai\");\n"}