{"spec_id":"histogram-kde","library":"d3","language":"javascript","code":"// anyplot.ai\n// histogram-kde: Histogram with KDE Overlay\n// Library: d3 7.9.0 | JavaScript 22.23.1\n// Quality: 89/100 | Created: 2026-08-05\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 110, right: 70, bottom: 90, left: 110 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Bolt head diameter measurements from a QC gauge station (target spec 12.00mm).\n// A tiny fixed-seed LCG stands in for the browser's lack of a seeded RNG.\nfunction lcg(seed) {\n  let state = seed;\n  return () => {\n    state = (state * 1664525 + 1013904223) % 4294967296;\n    return state / 4294967296;\n  };\n}\nconst rand = lcg(42);\nfunction randNormal() {\n  const u1 = Math.max(rand(), 1e-9);\n  const u2 = rand();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\nconst n = 500;\nconst diameters = [];\nfor (let i = 0; i < n; i++) {\n  // 90% of parts cluster tightly on the target; 10% come from a slightly\n  // undersized secondary mode (early tool wear), pulling the tail left.\n  const drifted = rand() < 0.1;\n  const mean = drifted ? 11.86 : 12.0;\n  const std = drifted ? 0.05 : 0.06;\n  diameters.push(mean + std * randNormal());\n}\n\nconst mean = d3.mean(diameters);\nconst std = d3.deviation(diameters);\n\n// --- Scales -------------------------------------------------------------\nconst x = d3.scaleLinear().domain(d3.extent(diameters)).nice().range([0, iw]);\n\nconst bins = d3.bin().domain(x.domain()).thresholds(30)(diameters);\nconst histDensity = bins.map((b) => ({\n  x0: b.x0,\n  x1: b.x1,\n  density: b.length / (n * (b.x1 - b.x0)),\n}));\n\nconst bandwidth = 1.06 * std * Math.pow(n, -0.2);\nfunction gaussianKernel(bw) {\n  return (u) => Math.exp(-0.5 * (u / bw) ** 2) / (bw * Math.sqrt(2 * Math.PI));\n}\nconst kernel = gaussianKernel(bandwidth);\nconst [xMin, xMax] = x.domain();\nconst grid = d3.range(xMin, xMax + (xMax - xMin) / 200, (xMax - xMin) / 200);\nconst kde = grid.map((gx) => [gx, d3.mean(diameters, (v) => kernel(gx - v))]);\n\nconst yMax = Math.max(d3.max(histDensity, (d) => d.density), d3.max(kde, (d) => d[1]));\nconst y = d3.scaleLinear().domain([0, yMax * 1.08]).nice().range([ih, 0]);\n\n// --- SVG mount ------------------------------------------------------------\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// --- Gridlines (y-axis only) -----------------------------------------------\ng.append(\"g\")\n  .attr(\"class\", \"grid\")\n  .call(d3.axisLeft(y).ticks(6).tickSize(-iw).tickFormat(\"\"))\n  .call((sel) => sel.select(\".domain\").remove())\n  .selectAll(\"line\")\n  .attr(\"stroke\", t.grid);\n\n// --- Histogram bars (density-scaled) ----------------------------------------\ng.selectAll(\".bar\")\n  .data(histDensity)\n  .join(\"rect\")\n  .attr(\"class\", \"bar\")\n  .attr(\"x\", (d) => x(d.x0) + 1)\n  .attr(\"y\", (d) => y(d.density))\n  .attr(\"width\", (d) => Math.max(0, x(d.x1) - x(d.x0) - 2))\n  .attr(\"height\", (d) => ih - y(d.density))\n  .attr(\"fill\", t.palette[0])\n  .attr(\"fill-opacity\", 0.5);\n\n// --- Rug (raw observations along the x-axis baseline) -----------------------\ng.selectAll(\".rug\")\n  .data(diameters)\n  .join(\"line\")\n  .attr(\"class\", \"rug\")\n  .attr(\"x1\", (d) => x(d))\n  .attr(\"x2\", (d) => x(d))\n  .attr(\"y1\", ih)\n  .attr(\"y2\", ih - 8)\n  .attr(\"stroke\", t.palette[0])\n  .attr(\"stroke-width\", 1)\n  .attr(\"stroke-opacity\", 0.3);\n\n// --- Target spec reference line (12.00mm) ------------------------------\nconst targetX = x(12.0);\ng.append(\"line\")\n  .attr(\"x1\", targetX)\n  .attr(\"x2\", targetX)\n  .attr(\"y1\", 0)\n  .attr(\"y2\", ih)\n  .attr(\"stroke\", t.ink)\n  .attr(\"stroke-width\", 1.5)\n  .attr(\"stroke-dasharray\", \"6,4\")\n  .attr(\"stroke-opacity\", 0.55);\ng.append(\"text\")\n  .attr(\"x\", targetX + 8)\n  .attr(\"y\", 16)\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"12px\")\n  .style(\"font-style\", \"italic\")\n  .text(\"Target spec 12.00mm\");\n\n// --- KDE curve ---------------------------------------------------------\nconst line = d3\n  .line()\n  .x((d) => x(d[0]))\n  .y((d) => y(d[1]))\n  .curve(d3.curveNatural);\n\ng.append(\"path\").datum(kde).attr(\"fill\", \"none\").attr(\"stroke\", t.palette[1]).attr(\"stroke-width\", 3.5).attr(\"d\", line);\n\n// --- Axes -------------------------------------------------------------------\nconst xAxis = g\n  .append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x).ticks(8).tickFormat((d) => d.toFixed(2)));\nconst yAxis = g.append(\"g\").call(d3.axisLeft(y).ticks(6));\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.inkSoft);\n  ax.select(\".domain\").attr(\"stroke\", t.inkSoft);\n}\n\n// --- Axis labels --------------------------------------------------------\ng.append(\"text\")\n  .attr(\"x\", iw / 2)\n  .attr(\"y\", ih + 60)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .text(\"Bolt Head Diameter (mm)\");\n\ng.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -ih / 2)\n  .attr(\"y\", -78)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .text(\"Density\");\n\n// --- Legend -------------------------------------------------------------\nconst legend = g.append(\"g\").attr(\"transform\", `translate(${iw - 210},0)`);\nlegend\n  .append(\"rect\")\n  .attr(\"x\", 0)\n  .attr(\"y\", 0)\n  .attr(\"width\", 16)\n  .attr(\"height\", 16)\n  .attr(\"rx\", 3)\n  .attr(\"ry\", 3)\n  .attr(\"fill\", t.palette[0])\n  .attr(\"fill-opacity\", 0.5)\n  .attr(\"stroke\", t.palette[0])\n  .attr(\"stroke-width\", 1);\nlegend.append(\"text\").attr(\"x\", 24).attr(\"y\", 13).attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\").text(\"Observed frequency\");\nlegend\n  .append(\"line\")\n  .attr(\"x1\", 0)\n  .attr(\"y1\", 38)\n  .attr(\"x2\", 16)\n  .attr(\"y2\", 38)\n  .attr(\"stroke\", t.palette[1])\n  .attr(\"stroke-width\", 3.5)\n  .attr(\"stroke-linecap\", \"round\");\nlegend.append(\"text\").attr(\"x\", 24).attr(\"y\", 42).attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\").text(\"KDE estimate\");\n\n// --- Title --------------------------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 50)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"histogram-kde · javascript · d3 · anyplot.ai\");\n"}