"use client";
import { motion } from "framer-motion";
import { useEffect, useState } from "react";
import { COLORS, VizFrame } from "./common";
function rng(seed: number) {
let s = seed;
return () => {
s = (s * 1664525 + 1013904223) % 4294967296;
return s / 4294967296 - 0.5;
};
}
function genPoints(N: number, slope: number, intercept: number, noise: number, seed: number) {
const r = rng(seed);
return Array.from({ length: N }, () => {
const x = (r() + 0.5) * 9 + 0.5;
const y = slope * x + intercept + r() * noise;
return { x, y };
});
}
function fitLine(pts: { x: number; y: number }[]) {
const n = pts.length;
const sx = pts.reduce((a, p) => a + p.x, 0) / n;
const sy = pts.reduce((a, p) => a + p.y, 0) / n;
const sxx = pts.reduce((a, p) => a + (p.x - sx) ** 2, 0);
const sxy = pts.reduce((a, p) => a + (p.x - sx) * (p.y - sy), 0);
const w = sxy / sxx;
return { w, b: sy - w * sx };
}
function Panel({
title,
noise,
width,
height,
seed,
truth,
}: {
title: string;
noise: number;
width: number;
height: number;
seed: number;
truth: { w: number; b: number };
}) {
const padX = 36;
const padY = 30;
const xMin = 0;
const xMax = 10;
const yMin = 0;
const yMax = 9;
const sx = (x: number) => padX + ((x - xMin) / (xMax - xMin)) * (width - padX * 2);
const sy = (y: number) =>
height - padY - ((y - yMin) / (yMax - yMin)) * (height - padY * 2);
const [seedTick, setSeedTick] = useState(0);
useEffect(() => {
const id = setInterval(() => setSeedTick((t) => t + 1), 1800);
return () => clearInterval(id);
}, []);
const pts = genPoints(40, truth.w, truth.b, noise, seed + seedTick * 1000);
const fit = fitLine(pts);
return (
{/* Frame */}
{title}
{/* Axes */}
{/* Truth line */}
{/* Points */}
{pts.map((p, i) => (
))}
{/* Fit line */}
{/* Stats */}
ŵ {fit.w.toFixed(2)} · b̂ {fit.b.toFixed(2)}
);
}
export function NoiseVsClean({
width = 880,
height = 380,
}: {
width?: number;
height?: number;
}) {
const panelW = (width - 30) / 2;
const panelH = height - 30;
const truth = { w: 0.6, b: 1.2 };
return (
);
}