{"spec_id":"scatter-regression-lowess","library":"muix","language":"javascript","code":"// anyplot.ai\n// scatter-regression-lowess: Scatter Plot with LOWESS Regression\n// Library: muix 7.29.1 | JavaScript 22.23.2\n// Quality: 88/100 | Created: 2026-09-09\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 { ChartsLegend } from \"@mui/x-charts/ChartsLegend\";\nimport { ChartsTooltip } from \"@mui/x-charts/ChartsTooltip\";\nimport { useXScale, useYScale } from \"@mui/x-charts/hooks\";\nimport Box from \"@mui/material/Box\";\nimport Typography from \"@mui/material/Typography\";\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ---------------------------------------\n// Tiny fixed-seed LCG — the browser has no seeded RNG.\nfunction makeLcg(seed: number) {\n  let state = seed >>> 0;\n  return function next() {\n    state = (Math.imul(state, 1664525) + 1013904223) >>> 0;\n    return state / 4294967296;\n  };\n}\n\nfunction randNormal(rng: () => number) {\n  const u1 = Math.max(rng(), 1e-9);\n  const u2 = rng();\n  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);\n}\n\nfunction hexToRgba(hex: string, alpha: number) {\n  const value = parseInt(hex.slice(1), 16);\n  const r = (value >> 16) & 255;\n  const g = (value >> 8) & 255;\n  const b = value & 255;\n  return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\nconst rng = makeLcg(42);\nconst SAMPLE_SIZE = 170;\n\n// Fuel efficiency peaks at a moderate cruising speed and drops off at both\n// low speed (frequent idling/acceleration) and high speed (aerodynamic drag)\n// — a non-monotonic pattern LOWESS traces without assuming a parametric form.\nconst vehicleSpeed: number[] = [];\nconst fuelEfficiency: number[] = [];\nfor (let i = 0; i < SAMPLE_SIZE; i += 1) {\n  const speed = 20 + rng() * 120;\n  const trend = 18.5 - 0.0021 * (speed - 78) ** 2;\n  const value = Math.max(3, trend + randNormal(rng) * 1.6);\n  vehicleSpeed.push(speed);\n  fuelEfficiency.push(value);\n}\n\n// --- LOWESS (locally weighted scatterplot smoothing) ------------------------\nfunction tricube(distance: number, bandwidth: number) {\n  if (bandwidth <= 0) return distance === 0 ? 1 : 0;\n  const u = Math.min(Math.abs(distance) / bandwidth, 1);\n  return (1 - u ** 3) ** 3;\n}\n\nfunction lowess(xs: number[], ys: number[], frac: number, gridSize: number) {\n  const n = xs.length;\n  const windowSize = Math.max(2, Math.round(frac * n));\n  const xMin = Math.min(...xs);\n  const xMax = Math.max(...xs);\n  const grid = Array.from({ length: gridSize }, (_, i) => xMin + ((xMax - xMin) * i) / (gridSize - 1));\n\n  return grid.map((x0) => {\n    const distances = xs.map((xi) => Math.abs(xi - x0));\n    const bandwidth = [...distances].sort((a, b) => a - b)[windowSize - 1];\n    const weights = distances.map((d) => tricube(d, bandwidth));\n\n    // Locally weighted linear regression via weighted normal equations.\n    let sw = 0;\n    let swx = 0;\n    let swy = 0;\n    let swxx = 0;\n    let swxy = 0;\n    for (let i = 0; i < n; i += 1) {\n      const w = weights[i];\n      sw += w;\n      swx += w * xs[i];\n      swy += w * ys[i];\n      swxx += w * xs[i] * xs[i];\n      swxy += w * xs[i] * ys[i];\n    }\n    const denom = sw * swxx - swx * swx;\n    const slope = denom !== 0 ? (sw * swxy - swx * swy) / denom : 0;\n    const intercept = sw !== 0 ? (swy - slope * swx) / sw : 0;\n\n    // Local residual spread — the weighted RMS deviation of the raw points\n    // from this window's line, reused as a ±1 SD confidence band around the fit.\n    let swResidSq = 0;\n    for (let i = 0; i < n; i += 1) {\n      const resid = ys[i] - (intercept + slope * xs[i]);\n      swResidSq += weights[i] * resid * resid;\n    }\n    const band = sw !== 0 ? Math.sqrt(swResidSq / sw) : 0;\n\n    return { x: x0, y: intercept + slope * x0, band };\n  });\n}\n\nconst smoothed = lowess(vehicleSpeed, fuelEfficiency, 0.4, 120);\nconst smoothedX = smoothed.map((point) => point.x);\nconst smoothedY = smoothed.map((point) => point.y);\nconst smoothedUpper = smoothed.map((point) => point.y + point.band);\nconst smoothedLower = smoothed.map((point) => point.y - point.band);\n\nconst scatterData = vehicleSpeed.map((speed, i) => ({\n  x: speed,\n  y: fuelEfficiency[i],\n  id: i,\n}));\n\n// A shaded ±1 SD band behind the fit line, drawn from the chart's own scales\n// (community `useXScale`/`useYScale` hooks) rather than as a legend series —\n// it should read as context for the fit, not compete with it for attention.\nfunction ConfidenceBand({ x, upper, lower, fill }: { x: number[]; upper: number[]; lower: number[]; fill: string }) {\n  const xScale = useXScale(\"speed\");\n  const yScale = useYScale();\n  const topEdge = x.map((xi, i) => `${i === 0 ? \"M\" : \"L\"}${xScale(xi)},${yScale(upper[i])}`);\n  const bottomEdge = [...x]\n    .map((xi, i) => ({ xi, y: lower[i] }))\n    .reverse()\n    .map((point) => `L${xScale(point.xi)},${yScale(point.y)}`);\n  return <path d={`${topEdge.join(\" \")} ${bottomEdge.join(\" \")} Z`} fill={fill} stroke=\"none\" />;\n}\n\n// --- Chart (default-exported component — the harness mounts it) -------------\nexport default function Chart() {\n  const W = window.ANYPLOT_SIZE.width;\n  const H = window.ANYPLOT_SIZE.height;\n  const CHART_TOP = 60;\n\n  const title = \"scatter-regression-lowess · javascript · muix · anyplot.ai\";\n  const titleSize = title.length > 67 ? Math.round((22 * 67) / title.length) : 22;\n\n  // \"muted\" semantic anchor (adaptive, outside the categorical pool) — used at\n  // low alpha for the confidence-band fill so it sits behind the data.\n  const mutedHex = t.theme === \"dark\" ? \"#A8A79F\" : \"#6B6A63\";\n  const bandFill = hexToRgba(mutedHex, 0.18);\n\n  return (\n    <Box sx={{ position: \"relative\", width: W, height: H, bgcolor: t.pageBg }}>\n      <Box sx={{ position: \"absolute\", top: 20, left: 56, right: 56 }}>\n        <Typography sx={{ color: t.ink, fontSize: titleSize, fontWeight: 500 }}>{title}</Typography>\n      </Box>\n      <Box sx={{ position: \"absolute\", top: CHART_TOP, left: 0, right: 0, bottom: 0 }}>\n        <ChartContainer\n          width={W}\n          height={H - CHART_TOP}\n          skipAnimation\n          margin={{ top: 30, right: 40, bottom: 70, left: 90 }}\n          series={[\n            {\n              type: \"scatter\",\n              data: scatterData,\n              color: hexToRgba(t.palette[0], 0.6),\n              markerSize: 8,\n              label: \"Vehicles (observed)\",\n            },\n            {\n              type: \"line\",\n              data: smoothedY,\n              xAxisId: \"speed\",\n              color: t.palette[1],\n              curve: \"natural\",\n              showMark: false,\n              label: \"LOWESS fit\",\n            },\n          ]}\n          xAxis={[\n            {\n              id: \"speed\",\n              data: smoothedX,\n              scaleType: \"linear\",\n              label: \"Vehicle Speed (km/h)\",\n              labelStyle: { fontSize: 16 },\n              tickLabelStyle: { fontSize: 14 },\n              valueFormatter: (value: number) => value.toFixed(0),\n            },\n          ]}\n          yAxis={[\n            {\n              label: \"Fuel Efficiency (km/L)\",\n              labelStyle: { fontSize: 16 },\n              tickLabelStyle: { fontSize: 14 },\n            },\n          ]}\n          sx={{\n            \"& .MuiLineElement-root\": { strokeWidth: 3.5 },\n          }}\n        >\n          <ChartsGrid horizontal vertical />\n          <ConfidenceBand x={smoothedX} upper={smoothedUpper} lower={smoothedLower} fill={bandFill} />\n          <ScatterPlot />\n          <LinePlot />\n          <ChartsXAxis />\n          <ChartsYAxis />\n          <ChartsLegend direction=\"row\" position={{ horizontal: \"right\", vertical: \"top\" }} slotProps={{ legend: { labelStyle: { fontSize: 14 } } }} />\n          <ChartsTooltip trigger=\"item\" />\n        </ChartContainer>\n      </Box>\n    </Box>\n  );\n}\n"}