{"spec_id":"scatter-regression-linear","library":"muix","language":"javascript","code":"// anyplot.ai\n// scatter-regression-linear: Scatter Plot with Linear Regression\n// Library: muix 7.29.1 | JavaScript 22.23.1\n// Quality: 89/100 | Created: 2026-08-05\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { ScatterPlot } from \"@mui/x-charts/ScatterChart\";\nimport { LinePlot } from \"@mui/x-charts/LineChart\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { ChartsYAxis } from \"@mui/x-charts/ChartsYAxis\";\nimport { ChartsGrid } from \"@mui/x-charts/ChartsGrid\";\nimport { useXScale, useYScale } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\nconst THEME = window.ANYPLOT_THEME === \"dark\" ? \"dark\" : \"light\";\n// ANYPLOT_TOKENS has no \"muted\" anchor — derive it from default-style-guide.md\n// \"Theme-adaptive Chrome\" (tertiary text / confidence-band fill token).\nconst INK_MUTED = THEME === \"dark\" ? \"#A8A79F\" : \"#6B6A63\";\n\n// --- Deterministic PRNG (LCG) + Box-Muller for reproducible normal noise ----\nfunction makeLcg(seed: number) {\n  let state = seed;\n  return function next() {\n    state = (state * 1664525 + 1013904223) % 4294967296;\n    return state / 4294967296;\n  };\n}\nfunction randNormal(rand: () => number) {\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\n// --- Data: advertising spend ($k) vs sales revenue ($k) ---------------------\nconst rand = makeLcg(42);\nconst POINT_COUNT = 90;\nconst adSpend: number[] = [];\nconst salesRevenue: number[] = [];\nfor (let i = 0; i < POINT_COUNT; i += 1) {\n  const spend = 5 + rand() * 45;\n  const revenue = 22 + spend * 3.4 + randNormal(rand) * 24;\n  adSpend.push(Number(spend.toFixed(2)));\n  salesRevenue.push(Number(Math.max(revenue, 8).toFixed(2)));\n}\n\n// --- Ordinary least-squares regression + 95% confidence band ----------------\nconst n = adSpend.length;\nconst xMean = adSpend.reduce((a, b) => a + b, 0) / n;\nconst yMean = salesRevenue.reduce((a, b) => a + b, 0) / n;\nlet sxy = 0;\nlet sxx = 0;\nfor (let i = 0; i < n; i += 1) {\n  sxy += (adSpend[i] - xMean) * (salesRevenue[i] - yMean);\n  sxx += (adSpend[i] - xMean) ** 2;\n}\nconst slope = sxy / sxx;\nconst intercept = yMean - slope * xMean;\n\nlet ssRes = 0;\nlet ssTot = 0;\nfor (let i = 0; i < n; i += 1) {\n  const fitted = intercept + slope * adSpend[i];\n  ssRes += (salesRevenue[i] - fitted) ** 2;\n  ssTot += (salesRevenue[i] - yMean) ** 2;\n}\nconst rSquared = 1 - ssRes / ssTot;\nconst correlation = Math.sign(slope) * Math.sqrt(rSquared);\nconst residualStdErr = Math.sqrt(ssRes / (n - 2));\nconst T_CRIT_95 = 1.987; // two-tailed 95% critical value, t-distribution df=88\n\nconst GRID_POINTS = 60;\nconst xMin = Math.min(...adSpend);\nconst xMax = Math.max(...adSpend);\nconst gridX = Array.from(\n  { length: GRID_POINTS },\n  (_, i) => xMin + ((xMax - xMin) * i) / (GRID_POINTS - 1),\n);\nconst fittedY = gridX.map((x) => intercept + slope * x);\nconst bandHalfWidth = gridX.map(\n  (x) => T_CRIT_95 * residualStdErr * Math.sqrt(1 / n + (x - xMean) ** 2 / sxx),\n);\nconst upperY = fittedY.map((y, i) => y + bandHalfWidth[i]);\nconst lowerY = fittedY.map((y, i) => y - bandHalfWidth[i]);\n\nconst scatterData = adSpend.map((x, i) => ({\n  x,\n  y: salesRevenue[i],\n  id: `pt-${i}`,\n}));\n\nconst yAllValues = [...salesRevenue, ...upperY, ...lowerY];\nconst yPad = (Math.max(...yAllValues) - Math.min(...yAllValues)) * 0.08;\nconst yDomainMin = Math.min(...yAllValues) - yPad;\nconst yDomainMax = Math.max(...yAllValues) + yPad;\n\nconst regressionColor = t.palette[2]; // blue — distinct from the brand-green points\nconst equationLabel = `y = ${slope.toFixed(2)}x + ${intercept.toFixed(1)}   ·   R² = ${rSquared.toFixed(3)}   ·   r = ${correlation.toFixed(3)}`;\n\nconst title = \"scatter-regression-linear · javascript · muix · anyplot.ai\";\n\nfunction ConfidenceBand() {\n  const xScale = useXScale();\n  const yScale = useYScale();\n  const top = gridX\n    .map((x, i) => `${xScale(x)},${yScale(upperY[i])}`)\n    .join(\" L \");\n  const bottomIndices = [...gridX.keys()].reverse();\n  const bottom = bottomIndices\n    .map((i) => `${xScale(gridX[i])},${yScale(lowerY[i])}`)\n    .join(\" L \");\n  return (\n    <path\n      d={`M ${top} L ${bottom} Z`}\n      fill={INK_MUTED}\n      opacity={0.22}\n      stroke=\"none\"\n    />\n  );\n}\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    <ChartContainer\n      width={width}\n      height={height}\n      margin={{ top: 92, right: 56, bottom: 76, left: 132 }}\n      series={[\n        {\n          type: \"scatter\",\n          id: \"observations\",\n          data: scatterData,\n          markerSize: 7,\n          color: `${t.palette[0]}99`,\n        },\n        {\n          type: \"line\",\n          id: \"regression\",\n          data: fittedY,\n          curve: \"linear\",\n          color: regressionColor,\n          showMark: false,\n          disableHighlight: true,\n        },\n      ]}\n      xAxis={[\n        {\n          data: gridX,\n          scaleType: \"linear\",\n          label: \"Advertising Spend ($k)\",\n          tickLabelStyle: { fontSize: 14 },\n          labelStyle: { fontSize: 16 },\n        },\n      ]}\n      yAxis={[\n        {\n          scaleType: \"linear\",\n          min: yDomainMin,\n          max: yDomainMax,\n          label: \"Sales Revenue ($k)\",\n          // tickFontSize drives the y-axis label's clearance from the tick labels\n          // (MUI X positions the rotated label at tickFontSize + tickSize + 10 px\n          // from the axis line); it's set well above tickLabelStyle.fontSize so\n          // the label never collides with 3-digit tick values like \"140\".\n          tickFontSize: 32,\n          tickLabelStyle: { fontSize: 14 },\n          labelStyle: { fontSize: 16 },\n        },\n      ]}\n      skipAnimation\n    >\n      <ChartsGrid horizontal />\n      <ConfidenceBand />\n      <LinePlot\n        skipAnimation\n        slotProps={{ line: { style: { strokeWidth: 3 } } }}\n      />\n      <ScatterPlot />\n      <ChartsXAxis />\n      <ChartsYAxis />\n      <text\n        x={width / 2}\n        y={40}\n        textAnchor=\"middle\"\n        fontSize={22}\n        fontWeight={600}\n        fill={t.ink}\n      >\n        {title}\n      </text>\n      <text x={140} y={78} fontSize={15} fill={t.inkSoft}>\n        {equationLabel}\n      </text>\n      <text x={140} y={98} fontSize={13} fill={INK_MUTED}>\n        Shaded band: 95% confidence interval for the mean response\n      </text>\n    </ChartContainer>\n  );\n}\n"}