{"spec_id":"box-horizontal","library":"d3","language":"javascript","code":"// anyplot.ai\n// box-horizontal: Horizontal Box Plot\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 89/100 | Created: 2026-09-02\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 90, right: 70, bottom: 80, left: 260 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Deterministic PRNG (mulberry32) + Box-Muller normal sampler ------------\nfunction mulberry32(seed) {\n  return () => {\n    seed |= 0;\n    seed = (seed + 0x6d2b79f5) | 0;\n    let x = Math.imul(seed ^ (seed >>> 15), 1 | seed);\n    x = (x + Math.imul(x ^ (x >>> 7), 61 | x)) ^ x;\n    return ((x ^ (x >>> 14)) >>> 0) / 4294967296;\n  };\n}\nconst rng = mulberry32(42);\nfunction normal(mean, sd) {\n  const u1 = Math.max(rng(), 1e-9);\n  const u2 = rng();\n  const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n  return mean + z * sd;\n}\n\n// --- Data: API response time distributions by service (ms) -----------------\nconst services = [\n  { name: \"Image CDN\", mean: 42, sd: 7, n: 45, outliers: [] },\n  { name: \"Auth Service\", mean: 58, sd: 11, n: 50, outliers: [] },\n  { name: \"Notification Queue\", mean: 88, sd: 18, n: 40, outliers: [340] },\n  { name: \"Payment Gateway\", mean: 112, sd: 22, n: 45, outliers: [420, 460] },\n  { name: \"Search API\", mean: 145, sd: 34, n: 55, outliers: [520, 610] },\n  { name: \"Recommendation Engine\", mean: 218, sd: 48, n: 40, outliers: [680, 740, 790] },\n];\n\nconst groups = services.map((s) => {\n  const samples = Array.from({ length: s.n }, () => Math.max(4, normal(s.mean, s.sd)));\n  const values = samples.concat(s.outliers).sort((a, b) => a - b);\n  const q1 = d3.quantile(values, 0.25);\n  const median = d3.quantile(values, 0.5);\n  const q3 = d3.quantile(values, 0.75);\n  const iqr = q3 - q1;\n  const lowFence = q1 - 1.5 * iqr;\n  const highFence = q3 + 1.5 * iqr;\n  const inRange = values.filter((v) => v >= lowFence && v <= highFence);\n  const whiskerLow = d3.min(inRange);\n  const whiskerHigh = d3.max(inRange);\n  const outliers = values.filter((v) => v < lowFence || v > highFence);\n  return { name: s.name, q1, median, q3, whiskerLow, whiskerHigh, outliers };\n});\n\n// Sort by median ascending so the fastest services read at the top.\ngroups.sort((a, b) => a.median - b.median);\n\nconst xMax = d3.max(groups, (g) => Math.max(g.whiskerHigh, d3.max(g.outliers) ?? 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// --- Scales -------------------------------------------------------------------\nconst x = d3.scaleLinear().domain([0, xMax]).nice().range([0, iw]);\nconst y = d3\n  .scaleBand()\n  .domain(groups.map((d) => d.name))\n  .range([0, ih])\n  .padding(0.45);\n\n// --- Gridlines (x-axis, subtle) ----------------------------------------------\ng.append(\"g\")\n  .attr(\"class\", \"grid\")\n  .call(d3.axisBottom(x).ticks(6).tickSize(ih).tickFormat(\"\"))\n  .attr(\"transform\", `translate(0,0)`)\n  .call((sel) => sel.select(\".domain\").remove())\n  .call((sel) => sel.selectAll(\"line\").attr(\"stroke\", t.grid));\n\n// --- Boxes, whiskers, outliers -------------------------------------------------\nconst boxHeight = y.bandwidth();\nconst boxGroups = g\n  .selectAll(\".box-group\")\n  .data(groups)\n  .join(\"g\")\n  .attr(\"class\", \"box-group\")\n  .attr(\"transform\", (d) => `translate(0,${y(d.name)})`);\n\n// whisker lines\nboxGroups\n  .append(\"line\")\n  .attr(\"x1\", (d) => x(d.whiskerLow))\n  .attr(\"x2\", (d) => x(d.q1))\n  .attr(\"y1\", boxHeight / 2)\n  .attr(\"y2\", boxHeight / 2)\n  .attr(\"stroke\", t.inkSoft)\n  .attr(\"stroke-width\", 1.5);\n\nboxGroups\n  .append(\"line\")\n  .attr(\"x1\", (d) => x(d.q3))\n  .attr(\"x2\", (d) => x(d.whiskerHigh))\n  .attr(\"y1\", boxHeight / 2)\n  .attr(\"y2\", boxHeight / 2)\n  .attr(\"stroke\", t.inkSoft)\n  .attr(\"stroke-width\", 1.5);\n\n// whisker caps\nfor (const key of [\"whiskerLow\", \"whiskerHigh\"]) {\n  boxGroups\n    .append(\"line\")\n    .attr(\"x1\", (d) => x(d[key]))\n    .attr(\"x2\", (d) => x(d[key]))\n    .attr(\"y1\", boxHeight * 0.25)\n    .attr(\"y2\", boxHeight * 0.75)\n    .attr(\"stroke\", t.inkSoft)\n    .attr(\"stroke-width\", 1.5);\n}\n\n// Pre-mixed opaque tint (fixed 28% white mix) so the composited box color no\n// longer depends on the page background behind it — only chrome stays theme-adaptive.\nconst boxFill = d3.interpolateRgb(t.palette[0], \"#ffffff\")(0.28);\n\n// box body\nboxGroups\n  .append(\"rect\")\n  .attr(\"x\", (d) => x(d.q1))\n  .attr(\"y\", 0)\n  .attr(\"width\", (d) => x(d.q3) - x(d.q1))\n  .attr(\"height\", boxHeight)\n  .attr(\"rx\", 3)\n  .attr(\"fill\", boxFill)\n  .attr(\"stroke\", t.palette[0])\n  .attr(\"stroke-width\", 1.5);\n\n// median line\nboxGroups\n  .append(\"line\")\n  .attr(\"x1\", (d) => x(d.median))\n  .attr(\"x2\", (d) => x(d.median))\n  .attr(\"y1\", 0)\n  .attr(\"y2\", boxHeight)\n  .attr(\"stroke\", t.ink)\n  .attr(\"stroke-width\", 3);\n\n// outliers\nboxGroups\n  .selectAll(\".outlier\")\n  .data((d) => d.outliers.map((v) => ({ value: v })))\n  .join(\"circle\")\n  .attr(\"class\", \"outlier\")\n  .attr(\"cx\", (d) => x(d.value))\n  .attr(\"cy\", boxHeight / 2)\n  .attr(\"r\", 6)\n  .attr(\"fill\", t.palette[0])\n  .attr(\"fill-opacity\", 0.55)\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 1.5);\n\n// --- Axes ----------------------------------------------------------------------\nconst xAxis = g.append(\"g\").attr(\"transform\", `translate(0,${ih})`).call(\n  d3\n    .axisBottom(x)\n    .ticks(6)\n    .tickFormat((v) => `${v} ms`),\n);\nxAxis.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\nxAxis.selectAll(\"line\").remove();\nxAxis.select(\".domain\").attr(\"stroke\", t.inkSoft);\n\nconst yAxis = g.append(\"g\").call(d3.axisLeft(y));\nyAxis.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"15px\");\nyAxis.selectAll(\"line\").remove();\nyAxis.select(\".domain\").attr(\"stroke\", t.inkSoft);\n\n// x-axis label\nsvg\n  .append(\"text\")\n  .attr(\"x\", margin.left + iw / 2)\n  .attr(\"y\", height - 24)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .text(\"Response Time (ms)\");\n\n// --- Title -----------------------------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 44)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"box-horizontal · javascript · d3 · anyplot.ai\");\n"}