{"spec_id":"bland-altman-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// bland-altman-basic: Bland-Altman Agreement Plot\n// Library: muix 7.29.1 | JavaScript 22.23.1\n// Quality: 94/100 | Created: 2026-08-11\nimport { ScatterChart } from \"@mui/x-charts/ScatterChart\";\nimport { ChartsReferenceLine } from \"@mui/x-charts/ChartsReferenceLine\";\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic LCG) ------------------------------------\n// Systolic blood pressure (mmHg) for 70 patients, each measured once with a\n// new oscillometric cuff and once with the mercury-column reference standard.\nlet seed = 42;\nfunction nextRandom() {\n  seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n  return seed / 0x7fffffff;\n}\nfunction gaussian() {\n  const u1 = Math.max(nextRandom(), 1e-9);\n  const u2 = nextRandom();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\nconst PATIENT_COUNT = 70;\nconst reference = Array.from({ length: PATIENT_COUNT }, () => 122 + gaussian() * 15);\n// The cuff reads slightly high on average, and its scatter widens a little\n// for higher pressures — a realistic proportional-error pattern.\nconst cuff = reference.map((value) => value + 3.4 + gaussian() * (4 + value * 0.02));\n\nconst meanValues = reference.map((value, i) => (value + cuff[i]) / 2);\nconst diffValues = reference.map((value, i) => cuff[i] - value);\n\nconst bias = diffValues.reduce((sum, d) => sum + d, 0) / PATIENT_COUNT;\nconst variance =\n  diffValues.reduce((sum, d) => sum + (d - bias) ** 2, 0) / (PATIENT_COUNT - 1);\nconst stdDev = Math.sqrt(variance);\nconst upperLimit = bias + 1.96 * stdDev;\nconst lowerLimit = bias - 1.96 * stdDev;\n\n// Points outside the ±1.96 SD limits of agreement get an accent treatment —\n// they're the story the reviewer of a Bland-Altman plot looks for first.\nconst withinLimits = [];\nconst outsideLimits = [];\nmeanValues.forEach((mean, i) => {\n  const diff = diffValues[i];\n  const point = { x: mean, y: diff, id: i };\n  if (diff > upperLimit || diff < lowerLimit) {\n    outsideLimits.push(point);\n  } else {\n    withinLimits.push(point);\n  }\n});\n\nconst TITLE_HEIGHT = 66;\n\n// --- Chart (default-exported component — the harness mounts it) ------------\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n\n  return (\n    <Box\n      sx={{\n        width,\n        height,\n        display: \"flex\",\n        flexDirection: \"column\",\n        paddingTop: \"20px\",\n      }}\n    >\n      <Typography\n        sx={{\n          color: t.ink,\n          fontSize: 26,\n          fontWeight: 600,\n          textAlign: \"center\",\n          lineHeight: 1.2,\n        }}\n      >\n        bland-altman-basic · javascript · muix · anyplot.ai\n      </Typography>\n      <ScatterChart\n        width={width}\n        height={height - TITLE_HEIGHT}\n        skipAnimation\n        series={[\n          {\n            id: \"within\",\n            data: withinLimits,\n            label: \"Within limits of agreement\",\n            markerSize: 7,\n            color: \"rgba(0, 158, 115, 0.6)\",\n          },\n          {\n            id: \"outside\",\n            data: outsideLimits,\n            label: \"Outside limits of agreement\",\n            markerSize: 11,\n            color: \"rgba(174, 48, 48, 0.85)\",\n          },\n        ]}\n        xAxis={[\n          {\n            label: \"Mean of Cuff & Reference (mmHg)\",\n            labelStyle: { fontSize: 16, fill: t.ink },\n            tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n          },\n        ]}\n        yAxis={[\n          {\n            label: \"Difference: Cuff − Reference (mmHg)\",\n            labelStyle: { fontSize: 16, fill: t.ink },\n            tickLabelStyle: { fontSize: 14, fill: t.inkSoft },\n          },\n        ]}\n        margin={{ left: 100, right: 140, top: 20, bottom: 90 }}\n        grid={{ horizontal: true, vertical: true }}\n        slotProps={{\n          legend: {\n            position: { vertical: \"top\", horizontal: \"middle\" },\n            direction: \"row\",\n            labelStyle: { fontSize: 13, fill: t.inkSoft },\n          },\n        }}\n        sx={{\n          \"& .MuiChartsGrid-line\": { stroke: t.grid, strokeWidth: 1 },\n          \"& circle\": { stroke: t.pageBg, strokeWidth: 1 },\n        }}\n      >\n        <ChartsReferenceLine\n          y={bias}\n          label={`Bias: ${bias.toFixed(1)} mmHg`}\n          labelAlign=\"end\"\n          lineStyle={{ stroke: t.ink, strokeWidth: 2.5 }}\n          labelStyle={{ fill: t.ink, fontSize: 14, fontWeight: 600 }}\n        />\n        <ChartsReferenceLine\n          y={upperLimit}\n          label={`+1.96 SD: ${upperLimit.toFixed(1)}`}\n          labelAlign=\"end\"\n          lineStyle={{ stroke: t.inkSoft, strokeDasharray: \"8 6\", strokeWidth: 1.75 }}\n          labelStyle={{ fill: t.inkSoft, fontSize: 14 }}\n        />\n        <ChartsReferenceLine\n          y={lowerLimit}\n          label={`−1.96 SD: ${lowerLimit.toFixed(1)}`}\n          labelAlign=\"end\"\n          lineStyle={{ stroke: t.inkSoft, strokeDasharray: \"8 6\", strokeWidth: 1.75 }}\n          labelStyle={{ fill: t.inkSoft, fontSize: 14 }}\n        />\n      </ScatterChart>\n    </Box>\n  );\n}\n"}