{"spec_id":"bland-altman-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// bland-altman-basic: Bland-Altman Agreement Plot\n// Library: chartjs 4.4.7 | JavaScript 22.23.1\n// Quality: 90/100 | Created: 2026-08-11\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic fixed-seed LCG) -------------------------\nlet seed = 42;\nfunction nextUniform() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\nfunction nextGaussian() {\n  const u1 = Math.max(nextUniform(), 1e-9);\n  const u2 = nextUniform();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\n// Reference cuff (method2) vs. new oscillometric device (method1) — paired\n// systolic blood pressure readings from the same subjects.\nconst N = 80;\nconst referenceCuff = [];\nconst newDevice = [];\nfor (let i = 0; i < N; i++) {\n  const reference = 120 + nextGaussian() * 15;\n  const bias = 3.2 + nextGaussian() * 5.5;\n  referenceCuff.push(reference);\n  newDevice.push(reference + bias);\n}\n\nconst points = referenceCuff.map((reference, i) => ({\n  x: (newDevice[i] + reference) / 2,\n  y: newDevice[i] - reference,\n}));\n\nconst diffs = points.map((p) => p.y);\nconst meanDiff = diffs.reduce((a, b) => a + b, 0) / diffs.length;\nconst variance = diffs.reduce((a, b) => a + (b - meanDiff) ** 2, 0) / (diffs.length - 1);\nconst sd = Math.sqrt(variance);\nconst upperLoA = meanDiff + 1.96 * sd;\nconst lowerLoA = meanDiff - 1.96 * sd;\n\nconst xValues = points.map((p) => p.x);\nconst xDataMin = Math.min(...xValues);\nconst xDataMax = Math.max(...xValues);\nconst xPad = (xDataMax - xDataMin) * 0.08;\nconst xMin = xDataMin - xPad;\nconst xMax = xDataMax + xPad;\n\n// --- Colors -------------------------------------------------------------\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}\nconst pointFill = hexToRgba(t.palette[0], 0.55);\n\n// --- Mount -----------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Reference-line label plugin (native Chart.js plugin API, no external deps) --\nconst referenceLabelsPlugin = {\n  id: \"referenceLabels\",\n  afterDatasetsDraw(chart) {\n    const { ctx, scales } = chart;\n    const labelX = scales.x.getPixelForValue(xDataMin + (xDataMax - xDataMin) * 0.02);\n    ctx.save();\n    ctx.font = \"600 15px sans-serif\";\n    ctx.textAlign = \"left\";\n    ctx.textBaseline = \"bottom\";\n    ctx.fillStyle = t.ink;\n    ctx.fillText(`Bias ${meanDiff.toFixed(2)} mmHg`, labelX, scales.y.getPixelForValue(meanDiff) - 6);\n    ctx.fillStyle = t.inkSoft;\n    ctx.fillText(`+1.96 SD  ${upperLoA.toFixed(2)} mmHg`, labelX, scales.y.getPixelForValue(upperLoA) - 6);\n    ctx.fillText(`-1.96 SD  ${lowerLoA.toFixed(2)} mmHg`, labelX, scales.y.getPixelForValue(lowerLoA) - 6);\n    ctx.restore();\n  },\n};\n\n// --- Chart -----------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"scatter\",\n  data: {\n    datasets: [\n      {\n        type: \"scatter\",\n        label: \"Paired readings\",\n        data: points,\n        backgroundColor: pointFill,\n        borderColor: t.pageBg,\n        borderWidth: 1.5,\n        pointRadius: 7,\n        pointHoverRadius: 9,\n      },\n      {\n        type: \"line\",\n        label: \"Mean bias\",\n        data: [\n          { x: xMin, y: meanDiff },\n          { x: xMax, y: meanDiff },\n        ],\n        borderColor: t.ink,\n        borderWidth: 3,\n        pointRadius: 0,\n        fill: false,\n        tension: 0,\n      },\n      {\n        type: \"line\",\n        label: \"+1.96 SD limit\",\n        data: [\n          { x: xMin, y: upperLoA },\n          { x: xMax, y: upperLoA },\n        ],\n        borderColor: t.inkSoft,\n        borderWidth: 2.5,\n        borderDash: [10, 6],\n        pointRadius: 0,\n        fill: false,\n        tension: 0,\n      },\n      {\n        type: \"line\",\n        label: \"-1.96 SD limit\",\n        data: [\n          { x: xMin, y: lowerLoA },\n          { x: xMax, y: lowerLoA },\n        ],\n        borderColor: t.inkSoft,\n        borderWidth: 2.5,\n        borderDash: [10, 6],\n        pointRadius: 0,\n        fill: false,\n        tension: 0,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: {\n        display: true,\n        text: \"bland-altman-basic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22 },\n      },\n      legend: {\n        labels: { color: t.ink, font: { size: 16 } },\n      },\n    },\n    scales: {\n      x: {\n        min: xMin,\n        max: xMax,\n        title: {\n          display: true,\n          text: \"Mean of Two Methods (mmHg)\",\n          color: t.ink,\n          font: { size: 16 },\n        },\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n      },\n      y: {\n        title: {\n          display: true,\n          text: \"Difference: New Device − Reference (mmHg)\",\n          color: t.ink,\n          font: { size: 16 },\n        },\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n      },\n    },\n  },\n  plugins: [referenceLabelsPlugin],\n});\n"}