{"spec_id":"boxen-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// boxen-basic: Basic Boxen Plot (Letter-Value Plot)\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 92/100 | Created: 2026-09-01\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 120, right: 260, bottom: 90, left: 110 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Data (in-memory, deterministic log-normal latency per service) --------\nfunction makeLcg(seed) {\n  let state = seed;\n  return function () {\n    state = (state * 1664525 + 1013904223) >>> 0;\n    return state / 4294967296;\n  };\n}\nconst rand = makeLcg(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 = 1500;\nconst services = [\n  { name: \"Auth API\", meanLog: Math.log(120), sdLog: 0.32 },\n  { name: \"Search API\", meanLog: Math.log(185), sdLog: 0.48 },\n  { name: \"Payment API\", meanLog: Math.log(230), sdLog: 0.42 },\n  { name: \"Notify API\", meanLog: Math.log(95), sdLog: 0.58 },\n];\nconst groups = services.map((s) => {\n  const values = [];\n  for (let i = 0; i < N; i++) {\n    values.push(Math.exp(s.meanLog + s.sdLog * randNormal()));\n  }\n  values.sort((a, b) => a - b);\n  return { name: s.name, values };\n});\n\n// --- Letter-value statistics -------------------------------------------------\nfunction letterValueDepth(n) {\n  return Math.min(6, Math.max(3, Math.floor(Math.log2(n)) - 3));\n}\nfunction letterValueStats(sortedValues) {\n  const n = sortedValues.length;\n  const k = letterValueDepth(n);\n  const levels = [];\n  for (let i = 1; i <= k; i++) {\n    const pLow = Math.pow(0.5, i + 1);\n    const pHigh = 1 - pLow;\n    levels.push({\n      depth: i,\n      low: d3.quantileSorted(sortedValues, pLow),\n      high: d3.quantileSorted(sortedValues, pHigh),\n      coverage: pHigh - pLow,\n    });\n  }\n  const median = d3.quantileSorted(sortedValues, 0.5);\n  const deepest = levels[levels.length - 1];\n  const outliers = sortedValues.filter((v) => v < deepest.low || v > deepest.high);\n  return { median, levels, outliers, k };\n}\nconst stats = groups.map((g) => letterValueStats(g.values));\nconst k = stats[0].k;\nconst spreads = groups.map((g) => d3.max(g.values) - d3.min(g.values));\nconst standoutIdx = spreads.indexOf(d3.max(spreads));\n\n// --- Scales -------------------------------------------------------------------\nconst x = d3\n  .scaleBand()\n  .domain(groups.map((g) => g.name))\n  .range([0, iw])\n  .padding(0.38);\n\nconst allValues = groups.flatMap((g) => g.values);\nconst y = d3\n  .scaleLinear()\n  .domain([0, d3.max(allValues) * 1.05])\n  .nice()\n  .range([ih, 0]);\n\nconst widthScale = d3.scaleLinear().domain([1, k]).range([x.bandwidth(), x.bandwidth() * 0.3]);\nconst opacityScale = d3.scaleLinear().domain([1, k]).range([0.92, 0.32]);\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// --- Axes -----------------------------------------------------------------\nconst xAxisG = g.append(\"g\").attr(\"transform\", `translate(0,${ih})`).call(d3.axisBottom(x));\nconst yAxisG = g.append(\"g\").call(d3.axisLeft(y).ticks(6).tickFormat((v) => `${v}`));\nfor (const axG of [xAxisG, yAxisG]) {\n  axG.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"15px\");\n  axG.selectAll(\"line\").attr(\"stroke\", t.grid);\n  axG.select(\".domain\").attr(\"stroke\", t.inkSoft);\n}\ng.append(\"g\")\n  .attr(\"class\", \"grid\")\n  .call(d3.axisLeft(y).ticks(6).tickSize(-iw).tickFormat(\"\"))\n  .selectAll(\"line\")\n  .attr(\"stroke\", t.grid)\n  .attr(\"stroke-opacity\", 0.5);\ng.select(\".grid .domain\").remove();\n\ng.append(\"text\")\n  .attr(\"x\", -ih / 2)\n  .attr(\"y\", -80)\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"17px\")\n  .text(\"Response Time (ms)\");\n\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\", \"17px\")\n  .text(\"Service Endpoint\");\n\n// --- Boxen (letter-value) boxes ------------------------------------------------\ngroups.forEach((grp, idx) => {\n  const color = t.palette[idx % t.palette.length];\n  const cx = x(grp.name) + x.bandwidth() / 2;\n  const s = stats[idx];\n  const cell = g.append(\"g\");\n\n  // Draw from the deepest (widest y-span, narrowest box) up to the innermost\n  // (narrowest y-span, widest box) so each subsequent box is not hidden.\n  for (let i = s.levels.length - 1; i >= 0; i--) {\n    const lvl = s.levels[i];\n    const w = widthScale(lvl.depth);\n    cell\n      .append(\"rect\")\n      .attr(\"x\", cx - w / 2)\n      .attr(\"y\", y(lvl.high))\n      .attr(\"width\", w)\n      .attr(\"height\", Math.max(1, y(lvl.low) - y(lvl.high)))\n      .attr(\"fill\", color)\n      .attr(\"fill-opacity\", opacityScale(lvl.depth))\n      .attr(\"stroke\", t.pageBg)\n      .attr(\"stroke-width\", 1.5);\n  }\n\n  // Median line spans the widest (innermost) box; the standout group gets a\n  // bolder line as a subtle storytelling cue.\n  const innerWidth = widthScale(1);\n  cell\n    .append(\"line\")\n    .attr(\"x1\", cx - innerWidth / 2)\n    .attr(\"x2\", cx + innerWidth / 2)\n    .attr(\"y1\", y(s.median))\n    .attr(\"y2\", y(s.median))\n    .attr(\"stroke\", t.ink)\n    .attr(\"stroke-width\", idx === standoutIdx ? 4 : 3);\n\n  // Outliers beyond the deepest letter value, jittered deterministically.\n  const jitterWidth = widthScale(k) * 0.9;\n  cell\n    .selectAll(\"circle\")\n    .data(s.outliers)\n    .join(\"circle\")\n    .attr(\"cx\", (v) => cx + (((v * 97) % 1) - 0.5) * jitterWidth)\n    .attr(\"cy\", (v) => y(v))\n    .attr(\"r\", 4.6)\n    .attr(\"fill\", color)\n    .attr(\"fill-opacity\", 0.6)\n    .attr(\"stroke\", t.pageBg)\n    .attr(\"stroke-width\", 0.8);\n\n  // Callout on the group with the widest overall spread.\n  if (idx === standoutIdx) {\n    const topY = Math.max(12, y(d3.max(grp.values)) - 16);\n    cell\n      .append(\"text\")\n      .attr(\"x\", cx)\n      .attr(\"y\", topY)\n      .attr(\"text-anchor\", \"middle\")\n      .attr(\"fill\", color)\n      .style(\"font-size\", \"13px\")\n      .style(\"font-weight\", \"600\")\n      .text(\"Widest spread ▲\");\n  }\n});\n\n// --- Legend: service colors + quantile depth encoding --------------------------\nconst legend = svg.append(\"g\").attr(\"transform\", `translate(${margin.left + iw + 50},${margin.top + 10})`);\nlegend\n  .append(\"text\")\n  .attr(\"x\", 0)\n  .attr(\"y\", 0)\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"Service\");\n\nconst catRowHeight = 26;\ngroups.forEach((grp, i) => {\n  const rowY = 24 + i * catRowHeight;\n  legend\n    .append(\"rect\")\n    .attr(\"x\", 0)\n    .attr(\"y\", rowY)\n    .attr(\"width\", 16)\n    .attr(\"height\", 16)\n    .attr(\"fill\", t.palette[i % t.palette.length]);\n  legend\n    .append(\"text\")\n    .attr(\"x\", 26)\n    .attr(\"y\", rowY + 13)\n    .attr(\"fill\", t.inkSoft)\n    .style(\"font-size\", \"14px\")\n    .text(grp.name);\n});\n\nconst depthLegendY = 24 + groups.length * catRowHeight + 24;\nlegend\n  .append(\"text\")\n  .attr(\"x\", 0)\n  .attr(\"y\", depthLegendY)\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"Letter-value depth\");\n\nconst legendRowHeight = 34;\nfor (let i = 1; i <= k; i++) {\n  const rowY = depthLegendY + 26 + (i - 1) * legendRowHeight;\n  const swatchW = 16 + (k - i) * 6;\n  const coverage = stats[0].levels[i - 1].coverage;\n  legend\n    .append(\"rect\")\n    .attr(\"x\", 0)\n    .attr(\"y\", rowY)\n    .attr(\"width\", swatchW)\n    .attr(\"height\", 16)\n    .attr(\"fill\", t.ink)\n    .attr(\"fill-opacity\", opacityScale(i));\n  legend\n    .append(\"text\")\n    .attr(\"x\", 60)\n    .attr(\"y\", rowY + 13)\n    .attr(\"fill\", t.inkSoft)\n    .style(\"font-size\", \"14px\")\n    .text(`${(coverage * 100).toFixed(1).replace(/\\.0$/, \"\")}% of data`);\n}\nconst outlierRowY = depthLegendY + 26 + k * legendRowHeight;\nlegend\n  .append(\"circle\")\n  .attr(\"cx\", 8)\n  .attr(\"cy\", outlierRowY + 8)\n  .attr(\"r\", 4.6)\n  .attr(\"fill\", t.inkSoft)\n  .attr(\"fill-opacity\", 0.7);\nlegend\n  .append(\"text\")\n  .attr(\"x\", 60)\n  .attr(\"y\", outlierRowY + 13)\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"14px\")\n  .text(\"Outliers\");\n\n// --- Title --------------------------------------------------------------------\n// Sized against the real rendered text width (Chromium layout, not a character-\n// count guess) so it reliably fills ~60% of the canvas width.\nconst title = \"API Latency by Service · boxen-basic · javascript · d3 · anyplot.ai\";\nconst baseTitleSize = 26;\nconst titleText = svg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 56)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", `${baseTitleSize}px`)\n  .style(\"font-weight\", \"600\")\n  .text(title);\nconst measuredWidth = titleText.node().getComputedTextLength();\nconst fittedTitleSize = Math.min(32, Math.max(18, Math.round((baseTitleSize * (width * 0.6)) / measuredWidth)));\ntitleText.style(\"font-size\", `${fittedTitleSize}px`);\n"}