Line Fit Viz
A 2D scatter chart with a user-controlled line y = slope x + intercept. Drag the slope and intercept sliders to fit the line through the points; dashed vertical drops show the residual at each point, and a faint dashed reference marks the analytical least-squares optimum so you can compare. R² is reported next to the formula.
Line fit. 12 points. y = 0.30 x + 0.20. R² 0.546.
y = 0.30 x + 0.20R² 0.546
0.30
0.20
Customize
Line
0.30
0.20
Display
Installation
npx shadcn@latest add https://craftbits.dev/r/line-fit-viz.jsonUsage
import { LineFitViz } from "@craft-bits/core";
<LineFitViz
points={[
{ x: -2, y: -0.6 },
{ x: -1, y: 0.1 },
{ x: 0, y: 0.6 },
{ x: 1, y: 1.0 },
{ x: 2, y: 1.7 },
]}
/>Drive slope and intercept from outside:
const [slope, setSlope] = useState(0.5);
const [intercept, setIntercept] = useState(0.2);
<LineFitViz
points={cloud}
slope={slope}
onSlopeChange={setSlope}
intercept={intercept}
onInterceptChange={setIntercept}
/>Hide the residuals and the optimal reference for a bare line-fit picker:
<LineFitViz
points={cloud}
showResiduals={false}
showOptimal={false}
/>Understanding the component
- Auto-fit viewport. The visible math domain is derived from the cloud's extents plus a 10–15 percent margin, then extended to keep both line endpoints (user's and, when shown, the optimum) inside the chart. No
rangeprop — the picture adapts to any reasonable point cloud. - Analytical least-squares fit. With centered data, the closed-form slope is the covariance of x and y divided by the variance of x; the intercept is
mean(y) − slope · mean(x). No gradient descent, no iteration — one pass over the points yields the optimum. Degenerate clouds (variance of x near zero) fall back to a horizontal line at the data's mean y. - R² against the user's line. The coefficient of determination is
1 − SSres / SStot, where SSres is the sum of squared residuals to the user's line and SStot is the total variance around mean y. When the data has zero variance the readout is dashed instead of NaN. - Residual drops. Each residual is a vertical segment from the point to the user's line, drawn in
var(--cb-warning)with a dashed stroke. They make the cost being minimised visible — the line that flattens the bars is the best fit. - Faint optimal reference. When
showOptimalis on, the analytical least-squares line is rendered invar(--cb-fg-muted)as a long-dash reference. The user's line is full-strength accent on top, so visual comparison is immediate. - Native slider semantics. Both controls are
LabeledSliderinstances wrapping a native<input type="range">— keyboard navigation (arrows, Home, End), focus ring, and AT semantics come for free. - Spring transitions. Line endpoints, residuals, and points all follow with
SPRINGS.smooth.prefers-reduced-motion: reducecollapses every spring to an instant swap.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
points | readonly LineFitPoint[] | required | Scatter points the line is fit against. |
slope | number | — | Controlled slope of the user's line. |
defaultSlope | number | 0 | Initial uncontrolled slope. |
onSlopeChange | (slope: number) => void | — | Fires when slope changes. |
intercept | number | — | Controlled intercept of the user's line. |
defaultIntercept | number | 0 | Initial uncontrolled intercept. |
onInterceptChange | (intercept: number) => void | — | Fires when intercept changes. |
showResiduals | boolean | true | Render vertical drops from each point to the user's line. |
showOptimal | boolean | true | Render the analytical least-squares line as a faint dashed reference. |
showRSquared | boolean | true | Render the R² readout next to the formula. |
slopeMin | number | -2 | Minimum slider value for slope. |
slopeMax | number | 2 | Maximum slider value for slope. |
interceptMin | number | -2 | Minimum slider value for intercept. |
interceptMax | number | 2 | Maximum slider value for intercept. |
transition | Transition | SPRINGS.smooth | Spring for line / residual / point transitions. |
className | string | — | Merged onto the root <div> via cn(). |
The LineFitPoint shape:
interface LineFitPoint {
x: number; // x coordinate in math space
y: number; // y coordinate in math space
}Accessibility
- The root
<div>isrole="figure"with a visually hidden summary of point count, current formula, and R² — screen readers hear the fit whenever the data or sliders change. - Both sliders are native
<input type="range">throughLabeledSlider, so keyboard control (arrows, Home, End) and AT semantics come for free. - Focus ring uses
:focus-visiblewith the accent color so keyboard users always see where they are. - Motion respects
prefers-reduced-motion: reduce— every spring collapses to an instant swap.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/viz/LineFitViz.tsx). The source paired the visualisation with Explore / Predict modes, a loss sparkline, gradient-direction quiz rounds, narration heuristics, history-undo state, and aWidgetchrome. The library extract is the pure regression primitive — points in, draggable line plus residuals plus optional least-squares reference plus R² out.