{"spec_id":"scatter-regression-polynomial","library":"muix","language":"javascript","code":"// anyplot.ai\n// scatter-regression-polynomial: Scatter Plot with Polynomial Regression\n// Library: muix 7.29.1 | JavaScript 22.23.1\n// Quality: 91/100 | Created: 2026-08-11\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\";\n\nconst t = window.ANYPLOT_TOKENS;\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// --- Least-squares polynomial fit via normal equations (Gauss-Jordan) -------\nfunction polyFit(xs: number[], ys: number[], degree: number) {\n  const size = degree + 1;\n  const ata: number[][] = Array.from({ length: size }, () => new Array(size).fill(0));\n  const aty: number[] = new Array(size).fill(0);\n  for (let i = 0; i < xs.length; i += 1) {\n    const powers: number[] = new Array(size);\n    let p = 1;\n    for (let k = 0; k < size; k += 1) {\n      powers[k] = p;\n      p *= xs[i];\n    }\n    for (let r = 0; r < size; r += 1) {\n      aty[r] += powers[r] * ys[i];\n      for (let c = 0; c < size; c += 1) ata[r][c] += powers[r] * powers[c];\n    }\n  }\n  // Gauss-Jordan elimination on the augmented [ata | aty] system, in place.\n  const augmented = ata.map((row, i) => [...row, aty[i]]);\n  for (let col = 0; col < size; col += 1) {\n    let pivotRow = col;\n    for (let row = col + 1; row < size; row += 1) {\n      if (Math.abs(augmented[row][col]) > Math.abs(augmented[pivotRow][col])) {\n        pivotRow = row;\n      }\n    }\n    [augmented[col], augmented[pivotRow]] = [augmented[pivotRow], augmented[col]];\n    const pivot = augmented[col][col];\n    for (let c = col; c <= size; c += 1) augmented[col][c] /= pivot;\n    for (let row = 0; row < size; row += 1) {\n      if (row === col) continue;\n      const factor = augmented[row][col];\n      for (let c = col; c <= size; c += 1) augmented[row][c] -= factor * augmented[col][c];\n    }\n  }\n  return augmented.map((row) => row[size]); // ascending powers: [c0, c1, c2, ...]\n}\n\nfunction evalPoly(coeffs: number[], x: number) {\n  let y = 0;\n  let p = 1;\n  for (let i = 0; i < coeffs.length; i += 1) {\n    y += coeffs[i] * p;\n    p *= x;\n  }\n  return y;\n}\n\n// --- Data: projectile trajectory — horizontal distance vs. height -----------\n// Tracked ball flight: h(x) = -g/(2*v0x^2)*x^2 + (v0y/v0x)*x + h0, plus sensor noise.\nconst rand = makeLcg(42);\nconst V0X = 18; // m/s horizontal velocity\nconst V0Y = 14; // m/s vertical velocity\nconst H0 = 1.3; // m release height\nconst G = 9.81; // m/s^2\nconst trueA = -G / (2 * V0X ** 2);\nconst trueB = V0Y / V0X;\nconst trueC = H0;\nconst flightRange = (-trueB - Math.sqrt(trueB ** 2 - 4 * trueA * trueC)) / (2 * trueA);\n\nconst POINT_COUNT = 85;\nconst distance: number[] = [];\nconst height: number[] = [];\nfor (let i = 0; i < POINT_COUNT; i += 1) {\n  const x = rand() * flightRange;\n  const trueY = trueA * x ** 2 + trueB * x + trueC;\n  const measured = Math.max(trueY + randNormal(rand) * 0.4, 0);\n  distance.push(Number(x.toFixed(2)));\n  height.push(Number(measured.toFixed(2)));\n}\n\n// --- Quadratic regression + goodness of fit ----------------------------------\nconst coeffs = polyFit(distance, height, 2);\nconst n = height.length;\nconst yMean = height.reduce((sum, y) => sum + y, 0) / n;\nlet ssRes = 0;\nlet ssTot = 0;\nfor (let i = 0; i < n; i += 1) {\n  const fitted = evalPoly(coeffs, distance[i]);\n  ssRes += (height[i] - fitted) ** 2;\n  ssTot += (height[i] - yMean) ** 2;\n}\nconst rSquared = 1 - ssRes / ssTot;\n\nconst CURVE_POINTS = 60;\nconst xMin = Math.min(...distance);\nconst xMax = Math.max(...distance);\nconst curveX = Array.from(\n  { length: CURVE_POINTS },\n  (_, i) => xMin + ((xMax - xMin) * i) / (CURVE_POINTS - 1),\n);\nconst curveY = curveX.map((x) => evalPoly(coeffs, x));\n\nconst scatterData = distance.map((x, i) => ({ x, y: height[i], id: `pt-${i}` }));\n\nconst yAllValues = [...height, ...curveY];\nconst yPad = (Math.max(...yAllValues) - Math.min(...yAllValues)) * 0.1;\nconst yDomainMin = Math.min(0, Math.min(...yAllValues) - yPad);\nconst yDomainMax = Math.max(...yAllValues) + yPad;\n\nconst regressionColor = t.palette[1]; // lavender — canonical Imprint position 2\n\nconst linearTerm = `${coeffs[1] >= 0 ? \"+\" : \"-\"} ${Math.abs(coeffs[1]).toFixed(3)}`;\nconst constantTerm = `${coeffs[0] >= 0 ? \"+\" : \"-\"} ${Math.abs(coeffs[0]).toFixed(2)}`;\nconst equationLabel = `h(x) = ${coeffs[2].toFixed(4)}x² ${linearTerm}x ${constantTerm}`;\nconst fitLabel = `R² = ${rSquared.toFixed(3)}`;\n\nconst title = \"scatter-regression-polynomial · javascript · muix · anyplot.ai\";\n\n// --- Chart (default-exported component — the harness mounts it) ------------\nexport default function Chart() {\n  const { width, height: chartHeight } = window.ANYPLOT_SIZE;\n\n  return (\n    <ChartContainer\n      width={width}\n      height={chartHeight}\n      margin={{ top: 104, right: 56, bottom: 76, left: 104 }}\n      series={[\n        {\n          type: \"scatter\",\n          id: \"observations\",\n          data: scatterData,\n          markerSize: 7,\n          color: `${t.palette[0]}aa`,\n        },\n        {\n          type: \"line\",\n          id: \"regression\",\n          data: curveY,\n          curve: \"natural\",\n          color: regressionColor,\n          showMark: false,\n          disableHighlight: true,\n        },\n      ]}\n      xAxis={[\n        {\n          data: curveX,\n          scaleType: \"linear\",\n          label: \"Horizontal Distance (m)\",\n          tickLabelStyle: { fontSize: 14 },\n          labelStyle: { fontSize: 16 },\n        },\n      ]}\n      yAxis={[\n        {\n          scaleType: \"linear\",\n          min: yDomainMin,\n          max: yDomainMax,\n          label: \"Height (m)\",\n          tickFontSize: 28,\n          tickLabelStyle: { fontSize: 14 },\n          labelStyle: { fontSize: 16 },\n        },\n      ]}\n      skipAnimation\n    >\n      <ChartsGrid\n        vertical\n        horizontal\n        sx={{ \"& line\": { stroke: t.grid, strokeOpacity: 0.15 } }}\n      />\n      <LinePlot\n        skipAnimation\n        slotProps={{\n          line: {\n            style: { strokeWidth: 3.5, filter: `drop-shadow(0 0 1px ${t.ink})` },\n          },\n        }}\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={112} y={72} fontSize={15} fill={t.inkSoft}>\n        {equationLabel}\n      </text>\n      <text\n        x={112}\n        y={96}\n        fontSize={19}\n        fontWeight={700}\n        fill={regressionColor}\n        stroke={t.ink}\n        strokeWidth={0.5}\n        paintOrder=\"stroke\"\n      >\n        {fitLabel}\n      </text>\n    </ChartContainer>\n  );\n}\n"}