Scatter Projector
A square SVG plane that visualizes projecting a 2D scatter cloud onto a chosen direction. The user rotates the projection axis with a slider; the component drops a perpendicular from every point onto the axis, lands a projected dot at each foot, and updates a 1D histogram of projected values beneath the plot. Used to teach scalar projection, variance along an axis, and the intuition behind PCA and random projections.
Projecting 20 points onto direction 45 degrees. Projected variance 4.34.
Customize
Angle
45°
Data
elongated
Overlays
Installation
npx shadcn@latest add https://craftbits.dev/r/scatter-projector.jsonUsage
import { ScatterProjector } from "@craft-bits/core";
<ScatterProjector
points={[
{ x: 1.2, y: 0.5 },
{ x: 2.4, y: 1.1 },
{ x: -1.0, y: -0.4 },
]}
defaultTheta={Math.PI / 4}
/>Control the angle from a parent so an external animation can sweep it:
const [theta, setTheta] = useState(0);
<ScatterProjector
points={cloud}
theta={theta}
onThetaChange={setTheta}
/>Hide the perpendicular drops and the histogram for a minimal embed:
<ScatterProjector
points={cloud}
showProjectionLines={false}
showHistogram={false}
/>Understanding the component
- One direction, one dot product. Every projection is the scalar product of the point with the unit vector
u = (cos θ, sin θ). For a pointpthe foot on the axis ist · u, with signed lengtht = p.x · cos θ + p.y · sin θ. - Rotate the line, watch the spread. Aligning the axis with the cloud's long direction keeps projected dots far apart (high projected variance); rotating 90 degrees collapses them toward the origin. The 1D histogram makes the change quantitative — the wider the bars, the more variance survives.
- Native slider as the primary controller. A single range input sweeps theta across
[0, 2π]in one-degree steps. Browsers ship full a11y for free — Tab to focus, arrow keys to nudge, screen readers announce the current degree value. - Auto-fit viewport. The SVG plane sizes itself to the maximum absolute coordinate in
pointswith a 15 percent margin — norangeprop. The projection axis extends to the viewport diagonal so it always covers the visible plane regardless of theta. - Two colors. Original scatter points draw in
cb-fg-muted; the axis and projected dots draw incb-accent; the perpendicular drops draw incb-fg-mutedat 55 percent opacity with a dashed pattern. - Histogram bins over a stable range. Bin edges span the viewport's diagonal radius regardless of theta, so widths don't change as the axis rotates — only the heights animate.
- Two spring tiers. Projection axis, foot dots, and perpendicular drops follow with
SPRINGS.smooth. Histogram bars useSPRINGS.snapso changes in counts feel crisp.prefers-reduced-motion: reducecollapses both toduration: 0. - Controlled and uncontrolled. Pass
thetaplusonThetaChangefor controlled, ordefaultThetafor uncontrolled. The default angle is0— the projection axis aligned with the positive x-axis.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
points | readonly ScatterPoint[] | required | 2D points to project. |
theta | number | — | Controlled projection angle in radians. Pair with onThetaChange. |
defaultTheta | number | 0 | Uncontrolled initial angle in radians. |
onThetaChange | (theta: number) => void | — | Fires on every slider change. |
showProjectionLines | boolean | true | Drop a dashed perpendicular from each point onto the axis. |
showHistogram | boolean | true | Render a 1D histogram of projected scalars beneath the plot. |
histogramBins | number | 12 | Number of histogram bins. |
size | number | 320 | SVG side length in pixels (the plane is square). |
transition | Transition | SPRINGS.smooth | Spring for projected-dot and projection-line transitions. |
className | string | — | Merged onto the root via cn(). |
The ScatterPoint shape:
| Field | Type | Description |
|---|---|---|
x | number | x coordinate in math space. |
y | number | y coordinate in math space. |
Accessibility
- The scatter SVG is
role="figure"with anaria-labelledbyheading also rendered as a visually hiddenaria-live="polite"summary — screen readers hear the projected variance and current degree whenever theta changes. - The histogram SVG is
role="img"with anaria-labeldescribing the projected-value distribution. - The slider is a native range input carrying
aria-valuemin/aria-valuemax/aria-valuenow/aria-valuetext. Keyboard parity is automatic. - Motion respects
prefers-reduced-motion: reduce— every spring collapses to an instant swap.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/math/ScatterProjector.tsx). The source paired the visualization with an Observe / PC1 / PC2 mode strip, narration heuristics, baked-in spread-bar overlays, and seeded Box-Muller cloud generation driven by eigenvalue and rotation props. The library extract is the pure visualization primitive — the caller supplies the points and the angle. Mode strips, narration, and seeded cloud generators belong in the consuming lesson, not the primitive.