{"spec_id":"histogram-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// histogram-basic: Basic Histogram\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-08-26\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 100, right: 60, bottom: 90, left: 100 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Data (in-memory, deterministic LCG) ------------------------------------\n// Bolt shear-strength measurements (N), quality-control sample with a slight\n// right skew from occasional over-torqued fasteners.\nlet seed = 42;\nfunction lcg() {\n  seed = (seed * 1664525 + 1013904223) % 4294967296;\n  return seed / 4294967296;\n}\nfunction gaussian() {\n  const u1 = 1 - lcg();\n  const u2 = lcg();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\nconst shearStrength = [];\nfor (let i = 0; i < 420; i++) {\n  const base = 820 + gaussian() * 45;\n  const skewBump = lcg() < 0.12 ? lcg() * 90 : 0;\n  shearStrength.push(base + skewBump);\n}\n\n// Over-torque caution zone: mean + 1.5 standard deviations, derived from the\n// sample itself rather than a hard-coded spec number.\nconst overTorqueThreshold = d3.mean(shearStrength) + 1.5 * d3.deviation(shearStrength);\n\n// --- Bins ---------------------------------------------------------------\nconst binGenerator = d3.bin().domain(d3.extent(shearStrength)).thresholds(24);\nconst bins = binGenerator(shearStrength);\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// --- Scales ---------------------------------------------------------------\nconst x = d3.scaleLinear().domain(d3.extent(shearStrength)).nice().range([0, iw]);\nconst y = d3\n  .scaleLinear()\n  .domain([0, d3.max(bins, (d) => d.length)])\n  .nice()\n  .range([ih, 0]);\n\n// --- Gridlines (y-axis only) -----------------------------------------------\ng.append(\"g\")\n  .selectAll(\"line\")\n  .data(y.ticks(6))\n  .join(\"line\")\n  .attr(\"x1\", 0)\n  .attr(\"x2\", iw)\n  .attr(\"y1\", (d) => y(d))\n  .attr(\"y2\", (d) => y(d))\n  .attr(\"stroke\", t.grid)\n  .attr(\"stroke-width\", 1);\n\n// --- Bars ---------------------------------------------------------------\n// Bins beyond the over-torque caution zone are flagged in amber (the Imprint\n// warning anchor) so the skewed tail the data already contains reads as a\n// deliberate QC signal instead of just \"the rightmost bars\".\nconst bars = g\n  .selectAll(\"rect.bar\")\n  .data(bins)\n  .join(\"rect\")\n  .attr(\"class\", \"bar\")\n  .attr(\"x\", (d) => x(d.x0) + 1)\n  .attr(\"y\", (d) => y(d.length))\n  .attr(\"width\", (d) => Math.max(0, x(d.x1) - x(d.x0) - 1))\n  .attr(\"height\", (d) => ih - y(d.length))\n  .attr(\"fill\", (d) => ((d.x0 + d.x1) / 2 >= overTorqueThreshold ? t.amber : t.palette[0]))\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 1);\n\n// --- Over-torque reference line ------------------------------------------\ng.append(\"line\")\n  .attr(\"x1\", x(overTorqueThreshold))\n  .attr(\"x2\", x(overTorqueThreshold))\n  .attr(\"y1\", 0)\n  .attr(\"y2\", ih)\n  .attr(\"stroke\", t.inkSoft)\n  .attr(\"stroke-width\", 1.5)\n  .attr(\"stroke-dasharray\", \"5,4\");\n\n// --- Axes -------------------------------------------------------------------\nconst xAxis = g\n  .append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x).ticks(8).tickSizeOuter(0));\nconst yAxis = g.append(\"g\").call(d3.axisLeft(y).ticks(6).tickSizeOuter(0));\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.inkSoft);\n  ax.select(\".domain\").attr(\"stroke\", t.inkSoft);\n}\nxAxis.selectAll(\"line\").remove();\nyAxis.selectAll(\"line\").remove();\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(\"Shear Strength (N)\");\n\ng.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -ih / 2)\n  .attr(\"y\", -70)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .text(\"Number of Fasteners\");\n\n// --- Over-torque legend (explains the amber semantic, top-right of chart) --\nconst legend = g.append(\"g\").attr(\"transform\", `translate(${iw - 230}, 6)`);\nlegend.append(\"rect\").attr(\"width\", 14).attr(\"height\", 14).attr(\"y\", -11).attr(\"fill\", t.amber);\nlegend\n  .append(\"text\")\n  .attr(\"x\", 20)\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"13px\")\n  .text(`Over-torque risk (>${Math.round(overTorqueThreshold)} N)`);\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-basic · javascript · d3 · anyplot.ai\");\n\n// --- Hover tooltips (interactive; opacity 0 by default, invisible in the ---\n// --- static PNG, revealed via d3 mouse events in the exported HTML) --------\nconst tooltip = svg\n  .append(\"g\")\n  .attr(\"class\", \"tooltip\")\n  .style(\"opacity\", 0)\n  .style(\"pointer-events\", \"none\");\nconst tooltipRect = tooltip.append(\"rect\").attr(\"fill\", t.elevatedBg).attr(\"stroke\", t.inkSoft).attr(\"rx\", 4);\nconst tooltipText = tooltip.append(\"text\").attr(\"fill\", t.ink).style(\"font-size\", \"13px\");\n\nbars\n  .on(\"mouseover\", function (event, d) {\n    tooltipText.text(`${Math.round(d.x0)}–${Math.round(d.x1)} N: ${d.length}`);\n    const bbox = tooltipText.node().getBBox();\n    tooltipRect.attr(\"width\", bbox.width + 16).attr(\"height\", bbox.height + 10);\n    tooltipText.attr(\"x\", 8).attr(\"y\", bbox.height + 1);\n    const [mx, my] = d3.pointer(event, svg.node());\n    tooltip.attr(\"transform\", `translate(${mx + 12},${my - bbox.height - 20})`);\n    tooltip.style(\"opacity\", 1);\n  })\n  .on(\"mouseout\", () => tooltip.style(\"opacity\", 0));\n"}