{"spec_id":"swarm-basic","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// swarm-basic: Basic Swarm Plot\n// Library: highcharts 12.6.0 | JavaScript 22.23.1\n// Quality: 91/100 | Created: 2026-07-26\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Deterministic PRNG (mulberry32) + Box-Muller normal -------------------\nfunction mulberry32(seed) {\n  return function () {\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}\n\nconst rand = mulberry32(42);\nfunction randNormal(mean, sd) {\n  const u1 = Math.max(rand(), 1e-9);\n  const u2 = rand();\n  const z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n  return mean + z * sd;\n}\n\n// --- Data: CRP biomarker levels (mg/L) across treatment groups -------------\nconst groups = [\n  { name: \"Placebo\", mean: 8.2, sd: 2.4, n: 42 },\n  { name: \"Low Dose\", mean: 6.5, sd: 2.1, n: 40 },\n  { name: \"Med Dose\", mean: 4.8, sd: 1.9, n: 40 },\n  { name: \"High Dose\", mean: 3.1, sd: 1.6, n: 38 },\n];\nconst categories = groups.map((g) => g.name);\n\ngroups.forEach((g) => {\n  g.values = Array.from({ length: g.n }, () => Math.max(0.2, randNormal(g.mean, g.sd)));\n  g.sampleMean = g.values.reduce((a, b) => a + b, 0) / g.values.length;\n});\n\n// --- Beeswarm layout: bin points by value, fan them out from center-out ----\n// Also reports the densest bin size so denser groups can render more\n// transparent, easing within-bin overlap where points stack up the most.\nfunction beeswarmOffsets(values, binWidth, gap) {\n  const order = values.map((v, i) => i).sort((a, b) => values[a] - values[b]);\n  const offsets = new Array(values.length).fill(0);\n  let bin = [];\n  let binStart = null;\n  let maxBinSize = 0;\n\n  const flushBin = () => {\n    maxBinSize = Math.max(maxBinSize, bin.length);\n    bin.forEach((idx, k) => {\n      const side = k % 2 === 0 ? 1 : -1;\n      const rank = Math.ceil((k + 1) / 2);\n      offsets[idx] = side * rank * gap;\n    });\n    bin = [];\n  };\n\n  order.forEach((i) => {\n    const v = values[i];\n    if (binStart === null || v - binStart > binWidth) {\n      flushBin();\n      binStart = v;\n    }\n    bin.push(i);\n  });\n  flushBin();\n\n  return { offsets, maxBinSize };\n}\n\nconst BIN_WIDTH = 0.5; // mg/L — points within this range share a swarm bin\nconst SWARM_GAP = 0.045; // category-axis units between adjacent swarm points\n\ngroups.forEach((g, i) => {\n  const { offsets, maxBinSize } = beeswarmOffsets(g.values, BIN_WIDTH, SWARM_GAP);\n  g.points = g.values.map((v, k) => ({ x: i + offsets[k], y: Number(v.toFixed(2)) }));\n  g.maxBinSize = maxBinSize;\n});\n\n// Denser groups (bigger densest-bin stacks) render more transparent so\n// overlapping points stay individually readable; sparser groups stay opaque.\nconst densestBin = Math.max(...groups.map((g) => g.maxBinSize));\nconst sparsestBin = Math.min(...groups.map((g) => g.maxBinSize));\nconst binRange = densestBin - sparsestBin || 1;\ngroups.forEach((g) => {\n  const density = (g.maxBinSize - sparsestBin) / binRange;\n  g.opacity = 0.88 - density * 0.28; // 0.88 (sparsest) down to 0.60 (densest)\n});\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"scatter\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"swarm-basic · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  xAxis: {\n    categories,\n    min: -0.6,\n    max: categories.length - 1 + 0.6,\n    tickPositions: categories.map((_, i) => i),\n    lineWidth: 0,\n    tickColor: t.inkSoft,\n    gridLineWidth: 0,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n    title: { text: \"Treatment Group\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n  },\n  yAxis: {\n    title: { text: \"CRP Level (mg/L)\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    lineWidth: 0,\n    gridLineColor: t.grid,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  legend: {\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n  },\n  plotOptions: {\n    series: { animation: false },\n    scatter: { marker: { radius: 5, lineWidth: 0 } },\n  },\n  series: [\n    ...groups.map((g, i) => ({\n      name: g.name,\n      type: \"scatter\",\n      color: t.palette[i],\n      data: g.points,\n      opacity: g.opacity,\n      showInLegend: false,\n    })),\n    {\n      name: \"Group mean\",\n      type: \"scatter\",\n      color: t.ink,\n      zIndex: 5,\n      data: groups.map((g, i) => ({ x: i, y: Number(g.sampleMean.toFixed(2)) })),\n      marker: { symbol: \"diamond\", radius: 9, lineColor: t.pageBg, lineWidth: 3 },\n      dataLabels: {\n        enabled: true,\n        format: \"{point.y:.1f}\",\n        y: -16,\n        style: {\n          color: t.ink,\n          fontSize: \"13px\",\n          fontWeight: \"600\",\n          textOutline: \"2px \" + t.pageBg,\n        },\n      },\n      showInLegend: true,\n    },\n  ],\n});\n"}