{"spec_id":"boxen-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// boxen-basic: Basic Boxen Plot (Letter-Value Plot)\n// Library: chartjs 4.4.7 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-09-01\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Deterministic PRNG (LCG) + Box-Muller normal sampler -------------------\nfunction makeLcg(seed) {\n  let state = seed >>> 0;\n  return function next() {\n    state = (Math.imul(1664525, state) + 1013904223) >>> 0;\n    return state / 4294967296;\n  };\n}\n\nfunction randNormal(rng) {\n  const u1 = Math.max(rng(), 1e-9);\n  const u2 = rng();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\nfunction hexToRgba(hex, alpha) {\n  const r = parseInt(hex.slice(1, 3), 16);\n  const g = parseInt(hex.slice(3, 5), 16);\n  const b = parseInt(hex.slice(5, 7), 16);\n  return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\n// --- Letter values (Tukey depths): nested boxes narrower than the previous\n// one at each recursion, from the fourths (innermost) outward -------------\nfunction letterValues(sorted, numLevels) {\n  const n = sorted.length;\n  const boxes = [];\n  let depth = (n + 1) / 2; // median depth\n  for (let level = 0; level < numLevels; level++) {\n    depth = (Math.floor(depth) + 1) / 2;\n    const loIdx = Math.max(0, Math.floor(depth) - 1);\n    const hiIdx = Math.min(n - 1, n - Math.floor(depth));\n    const coverage = Math.round((1 - (2 * depth) / (n + 1)) * 100);\n    boxes.push({ lo: sorted[loIdx], hi: sorted[hiIdx], coverage });\n  }\n  return boxes;\n}\n\nfunction median(sorted) {\n  const n = sorted.length;\n  return n % 2 === 1 ? sorted[(n - 1) / 2] : (sorted[n / 2 - 1] + sorted[n / 2]) / 2;\n}\n\n// --- Data: response-time distributions (ms) across 4 backend services,\n// each 1200 requests — large enough that a boxen plot's extra letter\n// values (beyond a standard box plot) reveal real tail behavior ----------\nconst services = [\n  { name: \"API Gateway\", meanLog: Math.log(90), sigmaLog: 0.35, seed: 11 },\n  { name: \"Auth Service\", meanLog: Math.log(60), sigmaLog: 0.45, seed: 23 },\n  { name: \"Database\", meanLog: Math.log(180), sigmaLog: 0.55, seed: 37 },\n  { name: \"Cache Layer\", meanLog: Math.log(15), sigmaLog: 0.3, seed: 53 },\n];\n\nconst SAMPLE_SIZE = 1200;\nconst NUM_LEVELS = 5; // fourths, eighths, sixteenths, 32nds, 64ths\n\nconst perService = services.map((svc, i) => {\n  const rng = makeLcg(svc.seed);\n  const values = Array.from(\n    { length: SAMPLE_SIZE },\n    () => Math.exp(svc.meanLog + svc.sigmaLog * randNormal(rng)),\n  );\n  values.sort((a, b) => a - b);\n\n  const boxes = letterValues(values, NUM_LEVELS);\n  const outermost = boxes[NUM_LEVELS - 1];\n  const jitterRng = makeLcg(svc.seed * 997);\n  const outliers = values\n    .filter((v) => v < outermost.lo || v > outermost.hi)\n    .map((v) => ({ x: i + 1 + (jitterRng() - 0.5) * 0.32, y: v }));\n\n  return { name: svc.name, catX: i + 1, boxes, median: median(values), outliers };\n});\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Datasets: outermost (widest range, thinnest bar) drawn first, each\n// narrower level drawn on top, ending with the median line and outliers ---\nconst brand = t.palette[0];\nconst thickness = [104, 82, 62, 44, 28]; // level 0 (fourths) -> level 4 (64ths)\nconst alpha = [0.85, 0.66, 0.5, 0.34, 0.2];\n\nconst boxDatasets = [];\nfor (let level = NUM_LEVELS - 1; level >= 0; level--) {\n  boxDatasets.push({\n    type: \"bar\",\n    label: `~${perService[0].boxes[level].coverage}% of data`,\n    data: perService.map((svc) => ({ x: svc.catX, y: [svc.boxes[level].lo, svc.boxes[level].hi] })),\n    backgroundColor: hexToRgba(brand, alpha[level]),\n    borderWidth: 0,\n    barThickness: thickness[level],\n    grouped: false,\n  });\n}\n\nconst medianDataset = {\n  type: \"bar\",\n  label: \"Median\",\n  data: perService.map((svc) => {\n    const eps = Math.max(0.4, svc.median * 0.008);\n    return { x: svc.catX, y: [svc.median - eps, svc.median + eps] };\n  }),\n  backgroundColor: t.ink,\n  borderWidth: 0,\n  barThickness: thickness[0] + 16,\n  grouped: false,\n};\n\nconst outlierDataset = {\n  type: \"scatter\",\n  label: \"Outliers\",\n  data: perService.flatMap((svc) => svc.outliers),\n  backgroundColor: hexToRgba(brand, 0.3),\n  borderColor: hexToRgba(brand, 0.6),\n  borderWidth: 1,\n  pointRadius: 4,\n  pointHoverRadius: 5,\n  showLine: false,\n};\n\n// --- Chart -------------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"bar\",\n  data: { datasets: [...boxDatasets, medianDataset, outlierDataset] },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: {\n        display: true,\n        text: \"boxen-basic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22 },\n      },\n      subtitle: {\n        display: true,\n        text: \"Nested boxes = successive letter values (fourths → 64ths); narrower boxes cover deeper quantiles\",\n        color: t.inkSoft,\n        font: { size: 14, style: \"italic\" },\n        padding: { bottom: 12 },\n      },\n      legend: {\n        position: \"bottom\",\n        labels: { color: t.ink, font: { size: 14 }, boxWidth: 16, boxHeight: 16 },\n      },\n    },\n    scales: {\n      x: {\n        type: \"linear\",\n        min: 0.5,\n        max: services.length + 0.5,\n        afterBuildTicks: (axis) => {\n          axis.ticks = services.map((_, i) => ({ value: i + 1 }));\n        },\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 16 },\n          callback: (value) => services[Math.round(value) - 1]?.name ?? \"\",\n        },\n        grid: { display: false },\n        title: { display: true, text: \"Service Endpoint\", color: t.ink, font: { size: 18 } },\n      },\n      y: {\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: { display: true, text: \"Response Time (ms)\", color: t.ink, font: { size: 18 } },\n      },\n    },\n  },\n});\n"}