{"spec_id":"bland-altman-basic","library":"echarts","language":"javascript","code":"// anyplot.ai\n// bland-altman-basic: Bland-Altman Agreement Plot\n// Library: echarts 6.1.0 | JavaScript 22.23.1\n// Quality: 90/100 | Created: 2026-08-11\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic LCG) ------------------------------------\n// mulberry32 PRNG — the browser has no seeded Math.random()\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}\nconst rand = mulberry32(42);\nfunction gaussian() {\n  const u1 = rand() || 1e-9;\n  const u2 = rand();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\n// Systolic blood pressure (mmHg): reference mercury sphygmomanometer vs a new\n// automated device, paired readings from the same subjects.\nconst n = 100;\nconst referenceBp = [];\nconst deviceBp = [];\nfor (let i = 0; i < n; i++) {\n  const reference = 90 + rand() * 90;\n  const device = reference - 2.5 + gaussian() * 3.5;\n  referenceBp.push(reference);\n  deviceBp.push(device);\n}\n\nconst points = referenceBp.map((reference, i) => {\n  const device = deviceBp[i];\n  const mean = (device + reference) / 2;\n  const diff = device - reference;\n  return [mean, diff];\n});\n\nconst diffs = points.map((p) => p[1]);\nconst bias = diffs.reduce((a, b) => a + b, 0) / diffs.length;\nconst variance =\n  diffs.reduce((a, b) => a + (b - bias) ** 2, 0) / (diffs.length - 1);\nconst sd = Math.sqrt(variance);\nconst upperLoA = bias + 1.96 * sd;\nconst lowerLoA = bias - 1.96 * sd;\n\n// Irregular (non-round-number) axis padding so the auto-computed tick grid\n// never lands exactly on the plot boundary — a round min/max there produces\n// a splitLine coincident with the edge, which reads as a false box border.\nconst means = points.map((p) => p[0]);\nconst xPad = (Math.max(...means) - Math.min(...means)) * 0.065;\nconst xMin = Math.min(...means) - xPad;\nconst xMax = Math.max(...means) + xPad;\nconst yPad = (Math.max(...diffs) - Math.min(...diffs)) * 0.08;\nconst yMin = Math.min(...diffs) - yPad;\nconst yMax = Math.max(...diffs) + yPad;\n\n// --- Init ---------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option ---------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"bland-altman-basic · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: 500 },\n  },\n  grid: { left: 110, right: 150, top: 100, bottom: 90 },\n  tooltip: {\n    trigger: \"item\",\n    backgroundColor: t.elevatedBg,\n    borderColor: t.grid,\n    textStyle: { color: t.ink },\n    formatter: (params) =>\n      Array.isArray(params.value)\n        ? `Mean: ${params.value[0].toFixed(1)} mmHg<br/>Difference: ${params.value[1].toFixed(1)} mmHg`\n        : params.name,\n  },\n  xAxis: {\n    type: \"value\",\n    min: xMin,\n    max: xMax,\n    name: \"Mean of Two Methods (mmHg)\",\n    nameLocation: \"middle\",\n    nameGap: 45,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    axisLabel: {\n      color: t.inkSoft,\n      fontSize: 14,\n      showMinLabel: false,\n      showMaxLabel: false,\n    },\n    axisLine: { onZero: false, lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  yAxis: {\n    type: \"value\",\n    min: yMin,\n    max: yMax,\n    name: \"Difference: Device − Reference (mmHg)\",\n    nameLocation: \"middle\",\n    nameGap: 70,\n    nameTextStyle: { color: t.ink, fontSize: 16 },\n    axisLabel: {\n      color: t.inkSoft,\n      fontSize: 14,\n      showMinLabel: false,\n      showMaxLabel: false,\n    },\n    axisLine: { onZero: false, lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  series: [\n    {\n      type: \"scatter\",\n      data: points,\n      symbolSize: 14,\n      itemStyle: { color: t.palette[0], opacity: 0.6 },\n      markArea: {\n        silent: true,\n        itemStyle: { color: t.palette[0], opacity: 0.08 },\n        data: [[{ yAxis: lowerLoA }, { yAxis: upperLoA }]],\n      },\n      markLine: {\n        symbol: \"none\",\n        silent: true,\n        animation: false,\n        lineStyle: { width: 2 },\n        label: { fontSize: 14, position: \"end\" },\n        data: [\n          {\n            yAxis: bias,\n            lineStyle: { color: t.ink, type: \"solid\" },\n            label: { color: t.ink, formatter: `Bias ${bias.toFixed(1)}` },\n          },\n          {\n            yAxis: upperLoA,\n            lineStyle: { color: t.inkSoft, type: \"dashed\", width: 1.5 },\n            label: {\n              color: t.inkSoft,\n              fontSize: 13,\n              formatter: `+1.96 SD  ${upperLoA.toFixed(1)}`,\n            },\n          },\n          {\n            yAxis: lowerLoA,\n            lineStyle: { color: t.inkSoft, type: \"dashed\", width: 1.5 },\n            label: {\n              color: t.inkSoft,\n              fontSize: 13,\n              formatter: `−1.96 SD  ${lowerLoA.toFixed(1)}`,\n            },\n          },\n        ],\n      },\n    },\n  ],\n});\n"}