{"spec_id":"histogram-stepwise","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// histogram-stepwise: Step Histogram\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 91/100 | Created: 2026-09-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Small LCG PRNG + Box-Muller transform — the browser has no seeded RNG.\nlet seed = 42;\nfunction lcgRandom() {\n  seed = (seed * 1664525 + 1013904223) % 4294967296;\n  return seed / 4294967296;\n}\nfunction randomNormal(mean, stdDev) {\n  const u1 = 1 - lcgRandom();\n  const u2 = lcgRandom();\n  const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n  return mean + z * stdDev;\n}\n\nconst endpoints = [\n  { name: \"API v1 (legacy)\", mean: 220, stdDev: 55, n: 700 },\n  { name: \"API v2 (rewrite)\", mean: 140, stdDev: 30, n: 700 },\n];\nendpoints.forEach((endpoint) => {\n  endpoint.latencies = Array.from({ length: endpoint.n }, () =>\n    Math.max(5, randomNormal(endpoint.mean, endpoint.stdDev)),\n  );\n});\n\n// --- Shared bins across both endpoints (aligned edges for direct overlay) ---\nconst allLatencies = endpoints.flatMap((endpoint) => endpoint.latencies);\nconst binWidth = 12;\nconst minEdge = Math.floor(Math.min(...allLatencies) / binWidth) * binWidth;\nconst maxEdge = Math.ceil(Math.max(...allLatencies) / binWidth) * binWidth;\nconst binCount = (maxEdge - minEdge) / binWidth;\nconst edges = Array.from({ length: binCount + 1 }, (_, i) => minEdge + i * binWidth);\n\n// One point per bin's left edge (plus a trailing point at the final right\n// edge, zero-padded at both ends) — Chart.js's native `stepped: \"after\"` line\n// option draws the horizontal-then-vertical step outline between them, so no\n// point-doubling is needed to fake the shape.\nfunction stepOutline(latencies) {\n  const counts = new Array(binCount).fill(0);\n  latencies.forEach((value) => {\n    const idx = Math.min(binCount - 1, Math.max(0, Math.floor((value - minEdge) / binWidth)));\n    counts[idx] += 1;\n  });\n\n  const points = [{ x: edges[0], y: 0 }];\n  for (let i = 0; i < binCount; i++) {\n    points.push({ x: edges[i], y: counts[i] });\n  }\n  points.push({ x: edges[binCount], y: counts[binCount - 1] });\n  points.push({ x: edges[binCount], y: 0 });\n  return points;\n}\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart -----------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"line\",\n  data: {\n    datasets: endpoints.map((endpoint, i) => ({\n      label: endpoint.name,\n      data: stepOutline(endpoint.latencies),\n      stepped: \"after\",\n      borderColor: t.palette[i],\n      backgroundColor: \"transparent\",\n      // Rewrite's tighter, faster distribution is the story — give it the\n      // heavier line weight so it reads as the visual focal point.\n      borderWidth: i === 1 ? 4.5 : 2.75,\n      pointRadius: 0,\n      fill: false,\n    })),\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    layout: {\n      padding: { right: 24, top: 8 },\n    },\n    plugins: {\n      title: {\n        display: true,\n        text: \"API Latency · histogram-stepwise · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 20, weight: \"600\" },\n        padding: { bottom: 20 },\n      },\n      legend: {\n        labels: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          boxWidth: 14,\n          usePointStyle: true,\n          pointStyle: \"line\",\n        },\n      },\n    },\n    scales: {\n      x: {\n        type: \"linear\",\n        ticks: { color: t.inkSoft, font: { size: 13 } },\n        grid: { display: false },\n        title: {\n          display: true,\n          text: \"Response Time (ms)\",\n          color: t.ink,\n          font: { size: 16 },\n        },\n      },\n      y: {\n        beginAtZero: true,\n        ticks: { color: t.inkSoft, font: { size: 13 } },\n        grid: { color: t.grid },\n        title: {\n          display: true,\n          text: \"Number of Requests\",\n          color: t.ink,\n          font: { size: 16 },\n        },\n      },\n    },\n  },\n});\n"}