{"spec_id":"histogram-stepwise","library":"d3","language":"javascript","code":"// anyplot.ai\n// histogram-stepwise: Step Histogram\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 86/100 | Created: 2026-09-05\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 110, right: 260, 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 — no seeded RNG in the browser) -----\nfunction lcg(seed) {\n  let state = seed >>> 0;\n  return () => {\n    state = (state * 1664525 + 1013904223) >>> 0;\n    return state / 4294967296;\n  };\n}\n\nfunction approxNormal(rand) {\n  // Irwin-Hall sum-of-12-uniforms approximation of a standard normal draw\n  let sum = 0;\n  for (let i = 0; i < 12; i += 1) sum += rand();\n  return sum - 6;\n}\n\nfunction commuteTimes(rand, mean, sd, n) {\n  const out = [];\n  for (let i = 0; i < n; i += 1) {\n    out.push(Math.max(2, mean + sd * approxNormal(rand)));\n  }\n  return out;\n}\n\nconst busTimes = commuteTimes(lcg(42), 32, 8, 1500);\nconst trainTimes = commuteTimes(lcg(1337), 24, 6, 1500);\n\n// Shared bin thresholds so both step outlines are directly comparable\nconst combined = busTimes.concat(trainTimes);\nconst domainMin = d3.min(combined);\nconst domainMax = d3.max(combined);\nconst thresholds = d3.range(20).map((i) => domainMin + ((domainMax - domainMin) * i) / 19);\n\nconst bin = d3.bin().domain([domainMin, domainMax]).thresholds(thresholds);\nconst busBins = bin(busTimes);\nconst trainBins = bin(trainTimes);\n\n// --- Scales -------------------------------------------------------------\nconst x = d3.scaleLinear().domain([domainMin, domainMax]).nice().range([0, iw]);\nconst maxCount = Math.max(d3.max(busBins, (b) => b.length), d3.max(trainBins, (b) => b.length));\nconst y = d3.scaleLinear().domain([0, maxCount]).nice().range([ih, 0]);\n\n// Build step-outline points: horizontal segment per bin, vertical connector\n// between bins, and a drop to zero at both ends — matplotlib histtype='step' style\nfunction stepPoints(bins) {\n  const points = [[bins[0].x0, 0]];\n  for (const b of bins) {\n    points.push([b.x0, b.length]);\n    points.push([b.x1, b.length]);\n  }\n  points.push([bins[bins.length - 1].x1, 0]);\n  return points;\n}\n\nconst stepLine = d3\n  .line()\n  .x((d) => x(d[0]))\n  .y((d) => y(d[1]));\n\n// --- SVG mount ------------------------------------------------------------\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\nsvg.append(\"rect\").attr(\"width\", width).attr(\"height\", height).attr(\"fill\", t.pageBg);\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// --- Gridlines (y-axis only, subtle) ---------------------------------------\ng.append(\"g\")\n  .attr(\"class\", \"grid\")\n  .call(d3.axisLeft(y).tickSize(-iw).tickFormat(\"\"))\n  .selectAll(\"line\")\n  .attr(\"stroke\", t.grid);\ng.select(\".grid .domain\").remove();\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));\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// --- Step outlines (no fill, matching the spec's unfilled requirement) ----\nconst series = [\n  { label: \"Bus\", bins: busBins, color: t.palette[0] },\n  { label: \"Train\", bins: trainBins, color: t.palette[1] },\n];\n\nfor (const s of series) {\n  g.append(\"path\")\n    .datum(stepPoints(s.bins))\n    .attr(\"fill\", \"none\")\n    .attr(\"stroke\", s.color)\n    .attr(\"stroke-width\", 3.5)\n    .attr(\"stroke-linejoin\", \"round\")\n    .attr(\"d\", stepLine);\n}\n\n// --- Axis labels --------------------------------------------------------\ng.append(\"text\")\n  .attr(\"x\", iw / 2)\n  .attr(\"y\", ih + 64)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"18px\")\n  .text(\"Commute Time (minutes)\");\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\", \"18px\")\n  .text(\"Number of Commuters\");\n\n// --- Legend -----------------------------------------------------------------\nconst legend = svg\n  .append(\"g\")\n  .attr(\"transform\", `translate(${margin.left + iw + 40},${margin.top + 20})`);\nseries.forEach((s, i) => {\n  const row = legend.append(\"g\").attr(\"transform\", `translate(0,${i * 36})`);\n  row.append(\"line\").attr(\"x1\", 0).attr(\"x2\", 28).attr(\"y1\", 0).attr(\"y2\", 0)\n    .attr(\"stroke\", s.color).attr(\"stroke-width\", 3.5);\n  row.append(\"text\").attr(\"x\", 38).attr(\"y\", 5).attr(\"fill\", t.ink)\n    .style(\"font-size\", \"16px\").text(s.label);\n});\n\n// --- Title ------------------------------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 52)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"18px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"Commute Times by Transit Mode · histogram-stepwise · javascript · d3 · anyplot.ai\");\n"}