Algebra Geometry Bridge
A two-panel layout for visualising the correspondence between an algebraic identity and its geometric interpretation. The left panel renders the identity plus one slider per declared parameter; the right panel renders a caller-supplied SVG construction; between them sits an animated equivalence glyph that pulses whenever the shared derived value changes.
Generic over the underlying identity: works for vector dot product, polar ↔ cartesian, slope-intercept lines, quadratic factoring, complex multiplication, or any algebraic identity with a geometric reading.
Algebra
Algebra ≡
10.00
Geometry
Geometry ≡
10.00
Installation
npx shadcn@latest add https://craftbits.dev/r/algebra-geometry-bridge.jsonUsage
import { AlgebraGeometryBridge } from "@craft-bits/viz/algebra-geometry-bridge";
<AlgebraGeometryBridge
equation="a · b = a₁·b₁ + a₂·b₂"
params={params}
onParamsChange={setParams}
paramDefs={[
{ key: "ax", label: "a₁", min: -5, max: 5 },
{ key: "ay", label: "a₂", min: -5, max: 5 },
{ key: "bx", label: "b₁", min: -5, max: 5 },
{ key: "by", label: "b₂", min: -5, max: 5 },
]}
result={(p) => (p.ax * p.bx + p.ay * p.by).toFixed(2)}
geometry={({ params, size }) => (
/* SVG content drawing the two vectors from the origin */
<g>...</g>
)}
/>Drop the result prop entirely for a bridge with no shared scalar — just sliders ↔ a geometric drawing:
<AlgebraGeometryBridge
equation="y = m·x + b"
params={params}
onParamsChange={setParams}
paramDefs={[
{ key: "m", label: "m", min: -3, max: 3 },
{ key: "b", label: "b", min: -3, max: 3 },
]}
geometry={({ params, size }) => (/* draw the line */)}
/>Override the panel labels when "Algebra" / "Geometry" are too generic:
<AlgebraGeometryBridge
algebraLabel="Cartesian"
geometryLabel="Polar"
equation="x = r·cos θ, y = r·sin θ"
/* ... */
/>Understanding the component
- Two parallel panels. A tinted accent rectangle on the left for the algebraic identity, a neutral rectangle on the right for the geometric drawing. Each panel is a labelled
<section>so screen readers announce a clear boundary. - Controlled parameter map.
paramsis aRecord<string, number>flat map.onParamsChangeis invoked with the next full map whenever a slider moves or the geometry render-prop callssetParamfrom a pointer handler. The component never owns state. - One slider per
paramDefsentry. Each definition declares the paramkey, the visiblelabel, and themin/max/step. Optionalformatre-renders the numeric readout (e.g. degrees, percent). - Geometry render prop.
geometryreceives{ params, setParam, size }and returns SVG content placed inside<svg viewBox="0 0 size size">. Drag handles can callsetParamdirectly to drive the algebra side from a pointer interaction. - Shared result. When
resultis set, both panels renderAlgebra ≡/Geometry ≡readouts underneath, both showing the same scalar — the visual claim is that the two computations agree. - Equivalence bridge. The glyph between the panels remounts on every change of
result(params)and runs a one-shot scale + opacity pulse onSPRINGS.snap. Underprefers-reduced-motion: reducethe pulse collapses to a static render.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
equation | ReactNode | required | Algebraic identity rendered above the slider stack. |
geometry | (args) => ReactNode | required | Render prop returning SVG content. Receives params, setParam, size. |
params | Record<string, number> | required | Controlled parameter map. |
onParamsChange | (next) => void | required | Called with the full next map whenever any param changes. |
paramDefs | ParamDef[] | required | Slider definitions for the algebra side. |
result | (params) => ReactNode | — | Optional shared derived value, rendered under both panels. |
algebraLabel | ReactNode | "Algebra" | Label above the algebra panel. |
geometryLabel | ReactNode | "Geometry" | Label above the geometry panel. |
geometrySize | number | 240 | Side length (px) of the geometry SVG viewBox. |
transition | Transition | SPRINGS.snap | Equivalence-pulse transition. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- Both panels are
<section>elements witharia-labelledbylinked to their visible title, so screen readers announce a clear panel boundary. - Each slider gets an
aria-labelderived from the param def'slabel(falling back to thekey). - The geometry
<svg>isrole="img"with a generic"Geometric construction"label — callers wanting a richer description can wrap with their own labelled element or place a<title>inside the render prop. - The equivalence bridge is
aria-hidden— the equivalence is already encoded by the matchingAlgebra ≡/Geometry ≡readouts. - Colour is never the only signal — the algebra panel uses both an accent tint and an accent label; the geometry panel uses a neutral tint and a muted label.
- Motion respects
prefers-reduced-motion: reduce— the equivalence pulse collapses to a static render. Slider interactions are unaffected.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/math/AlgebraGeometryBridge.tsx). The source was a fully hand-rolled dot-product lesson: it baked in two specific vectors, a hard-codeda·b = |a||b|cos θidentity, three modes (explore / predict / challenge) wired to the lesson chrome, in-component scoring, and a custom drag-the-vector-tips SVG plane. The viz extract reduces all of that to a generic two-panel layout — algebra (sliders) ↔ geometry (caller-supplied SVG) — so the same component drives any algebraic identity with a geometric interpretation. Modes, scoring, and lesson-specific chrome live outside the component.