Term By Term Decomposer
Visual decomposition of polynomial differentiation for f(x) = 3x² + 2x + 1. Three colour-coded term curves morph simultaneously into their derivatives (the parabola flattens to a line, the line to a horizontal, the constant fades out), the sum curve becomes f′(x) = 6x + 2, and a probe at x = 1 drops intersection dots on the derivative curves and reveals f′(1) = 8 — the tangent slope.
The component runs as a four-phase narrative — original, differentiating, differentiated, probed — advanced by a single action button or by a controlling parent via the phase / onPhaseChange API. The walkthrough is the visual proof of the sum rule: every term contributes its own derivative, and the slope at a probe point is the sum of those local contributions.
f(x) = 3x² + 2x + 1. Three terms, three curves. The sum curve shows the full polynomial. Click to differentiate each term independently.
Installation
npx shadcn@latest add https://craftbits.dev/r/term-by-term-decomposer.jsonUsage
import { TermByTermDecomposer } from "@craft-bits/viz/term-by-term-decomposer";
<TermByTermDecomposer />Drive the walkthrough from a parent:
const [phase, setPhase] = useState("original");
<TermByTermDecomposer phase={phase} onPhaseChange={setPhase} />Render a custom action button instead of the built-in one:
<TermByTermDecomposer
hideAction
renderAction={({ label, disabled, onClick }) => (
<MyButton onClick={onClick} disabled={disabled}>{label}</MyButton>
)}
/>Hide the narration when embedding inside an article that already explains the sum rule:
<TermByTermDecomposer hideNarration />Understanding the component
- Three curves, one polynomial. The original phase renders the polynomial as three coloured term curves (
3x²accent,2xsuccess,1subtle) plus a prominent sum curve drawn with a glow halo so the eye reads it as the "answer". - Sum-rule decomposition. Differentiation is presented as a per-term operation: every term morphs into its derivative independently, and the sum curve morphs into the sum of those derivatives. The visual story matches the algebra exactly.
- Path morph is rAF-driven. SVG
dis a string, so it cannot be sprung directly. The component runs an ease-out cubic loop withrequestAnimationFrameand rebuilds every term path + the sum path on each frame — the constant-term curve simultaneously fades to opacity 0 as it collapses. - Equation cross-fades. While the curves morph, the equation label cross-fades from
f(x) = 3x² + 2x + 1tof′(x) = 6x + 2, anchoring the visual change in algebraic notation. - Probe at x = 1. Once differentiated, a single click drops a dashed warning-coloured probe at
x = 1, places intersection dots on the two non-vanishing derivative curves (6from6x,2from2), lands a glowing sum dot atf′(1) = 8, and surfaces the readout in a token-tinted pill — emphasising that the sum rule applies to the slope too. - Reduced motion. Under
prefers-reduced-motion: reduce, every spring is replaced by an instant attribute set and the morph loop snaps straight to the derivative paths — the visual end-state of each phase still renders, but no motion plays.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
phase | "original" | "differentiating" | "differentiated" | "probed" | — | Controlled phase. Pair with onPhaseChange. |
defaultPhase | same | "original" | Initial phase for the uncontrolled API. |
onPhaseChange | (next) => void | — | Fires after the component commits to the next phase. |
transition | Transition | SPRINGS.smooth | Override the spring for probe dot springs and label fades. |
actionLabels | Partial<Record<phase, string>> | — | Override the action-button label per phase. |
narrationCopy | Partial<Record<phase, string>> | — | Override the narration copy per phase. |
hideNarration | boolean | false | Hide the narration paragraph. |
hideAction | boolean | false | Hide the built-in action button. |
renderAction | ({ phase, label, disabled, onClick }) => ReactNode | — | Render slot for a custom action button. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The root carries
aria-labelledbypointing to a hidden<span>summary of the current phase, so screen readers hear the current narrative state without parsing the SVG. - The SVG itself is
role="img"with the samearia-label, so consumers who slot the SVG into another tree still surface a readable name. - An
aria-live="assertive"region announces each terminal phase transition ("Derivative: f prime of x equals 6x plus 2.", "f prime of 1 equals 8…") so non-sighted users hear when each new derivative or probe value lands. - The action button is a real
<button type="button">with:focus-visibleoutline,disabledstate during animation and during the transientdifferentiatingphase, and standard keyboard activation. - Colour signals (accent, success, warning, subtle) are reinforced by position and copy so the visualization is not colour-only.
- Motion respects
prefers-reduced-motion: reduce— every spring and the rAF-driven path morph collapse to an immediate attribute set, including the equation cross-fade, term-curve morph, probe-dot reveal, and label fade-in.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/math/TermByTermDecomposer.tsx). The library extract drops the project-specificSvgLabelandChallengeBtnchrome in favour of plain<text>and acn()-styled<button>, swaps the--color-accent-*/--color-success-*/--color-warn-*/--color-ink-*palette for the canonical--cb-accent/--cb-success/--cb-warning/--cb-fg-*/--cb-border-*token vars, replaces unknownSPRINGS.gentle/SPRINGS.snappyreferences withSPRINGS.smooth/SPRINGS.snap/SPRINGS.bouncy, lifts the implicit phase state into a controlledphase/onPhaseChangeAPI plusrenderActionslot, and consolidates theuseRefarrays into single named refs grouped viauseMemoso the file passes the library's strict React-hooks lint.