{"spec_id":"strip-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// strip-basic: Basic Strip Plot\n// Library: chartjs 4.4.7 | JavaScript 22.23.1\n// Quality: 92/100 | Created: 2026-08-05\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic fixed-seed LCG) -------------------------\n// Patient response time (minutes) to a treatment, across escalating drug doses.\nfunction makeLcg(seed) {\n  let state = seed;\n  return () => {\n    state = (state * 1664525 + 1013904223) % 4294967296;\n    return state / 4294967296;\n  };\n}\nconst rand = makeLcg(42);\n\nfunction gaussian() {\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 categories = [\"Placebo\", \"Low Dose\", \"Standard Dose\", \"High Dose\"];\nconst groupMeans = [42, 35, 24, 18];\nconst groupStd = [8, 7, 6, 5];\nconst perGroup = 45;\nconst jitterWidth = 0.22;\n\nfunction withAlpha(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\nconst groupPoints = categories.map((_, i) =>\n  Array.from({ length: perGroup }, () => ({\n    x: i + (rand() - 0.5) * 2 * jitterWidth,\n    y: Math.max(2, groupMeans[i] + groupStd[i] * gaussian()),\n  })),\n);\n\n// Local neighbor count per point drives a Chart.js scriptable style function below —\n// denser sub-clusters render slightly more saturated, reinforcing the strip plot's\n// accumulation-by-overplotting story beyond what a static per-dataset color can show.\nconst xTol = jitterWidth * 0.6;\nconst yTol = 3;\nfunction localDensity(points, idx) {\n  const p = points[idx];\n  let count = 0;\n  for (let j = 0; j < points.length; j++) {\n    if (j === idx) continue;\n    const q = points[j];\n    if (Math.abs(q.x - p.x) < xTol && Math.abs(q.y - p.y) < yTol) count++;\n  }\n  return count;\n}\n\nconst scatterDatasets = categories.map((label, i) => {\n  const points = groupPoints[i];\n  const densities = points.map((_, idx) => localDensity(points, idx));\n  const maxDensity = Math.max(1, ...densities);\n  const baseColor = t.palette[i % t.palette.length];\n  return {\n    label,\n    data: points,\n    order: 0,\n    pointBackgroundColor: (ctx) => withAlpha(baseColor, 0.5 + 0.35 * (densities[ctx.dataIndex] / maxDensity)),\n    pointRadius: 6,\n    pointHoverRadius: 6,\n    pointBorderWidth: 1,\n    pointBorderColor: t.pageBg,\n  };\n});\n\n// Dashed per-group mean reference lines (spec: \"Consider adding horizontal lines for\n// group means or medians\"), drawn behind the points in the chart's neutral ink tone.\nconst meanLineDatasets = categories.map((label, i) => ({\n  label: `${label} mean`,\n  type: \"line\",\n  order: 1,\n  data: [\n    { x: i - jitterWidth - 0.06, y: groupMeans[i] },\n    { x: i + jitterWidth + 0.06, y: groupMeans[i] },\n  ],\n  borderColor: withAlpha(t.ink, 0.55),\n  borderWidth: 2,\n  borderDash: [6, 4],\n  pointRadius: 0,\n  fill: false,\n}));\n\nconst datasets = [...meanLineDatasets, ...scatterDatasets];\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Chart ---------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"scatter\",\n  data: { datasets },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    parsing: false,\n    plugins: {\n      title: {\n        display: true,\n        text: \"strip-basic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 26 },\n        padding: { bottom: 24 },\n      },\n      legend: { display: false },\n    },\n    scales: {\n      x: {\n        type: \"linear\",\n        min: -0.6,\n        max: categories.length - 1 + 0.6,\n        ticks: {\n          color: t.inkSoft,\n          font: { size: 14 },\n          stepSize: 1,\n          callback: (value) => (Number.isInteger(value) && categories[value] !== undefined ? categories[value] : \"\"),\n        },\n        grid: { display: false },\n        title: { display: true, text: \"Treatment Group\", color: t.ink, font: { size: 16 } },\n      },\n      y: {\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: { display: true, text: \"Response Time (minutes)\", color: t.ink, font: { size: 16 } },\n      },\n    },\n  },\n});\n"}