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.

Term-by-term polynomial decomposer. Phase: original. Showing f(x) = 3x squared + 2x + 1 as individual curves.f(x) = 3x² + 2x + 1f′(x) = 6x + 2f′(1) = 8

f(x) = 3x² + 2x + 1. Three terms, three curves. The sum curve shows the full polynomial. Click to differentiate each term independently.

Customize
State
original
Layout

Installation

npx shadcn@latest add https://craftbits.dev/r/term-by-term-decomposer.json

Usage

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

  1. Three curves, one polynomial. The original phase renders the polynomial as three coloured term curves (3x² accent, 2x success, 1 subtle) plus a prominent sum curve drawn with a glow halo so the eye reads it as the "answer".
  2. 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.
  3. Path morph is rAF-driven. SVG d is a string, so it cannot be sprung directly. The component runs an ease-out cubic loop with requestAnimationFrame and rebuilds every term path + the sum path on each frame — the constant-term curve simultaneously fades to opacity 0 as it collapses.
  4. Equation cross-fades. While the curves morph, the equation label cross-fades from f(x) = 3x² + 2x + 1 to f′(x) = 6x + 2, anchoring the visual change in algebraic notation.
  5. 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 (6 from 6x, 2 from 2), lands a glowing sum dot at f′(1) = 8, and surfaces the readout in a token-tinted pill — emphasising that the sum rule applies to the slope too.
  6. 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

PropTypeDefaultDescription
phase"original" | "differentiating" | "differentiated" | "probed"Controlled phase. Pair with onPhaseChange.
defaultPhasesame"original"Initial phase for the uncontrolled API.
onPhaseChange(next) => voidFires after the component commits to the next phase.
transitionTransitionSPRINGS.smoothOverride the spring for probe dot springs and label fades.
actionLabelsPartial<Record<phase, string>>Override the action-button label per phase.
narrationCopyPartial<Record<phase, string>>Override the narration copy per phase.
hideNarrationbooleanfalseHide the narration paragraph.
hideActionbooleanfalseHide the built-in action button.
renderAction({ phase, label, disabled, onClick }) => ReactNodeRender slot for a custom action button.
classNamestringMerged onto the root via cn().

Accessibility

  • The root carries aria-labelledby pointing 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 same aria-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-visible outline, disabled state during animation and during the transient differentiating phase, 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-specific SvgLabel and ChallengeBtn chrome in favour of plain <text> and a cn()-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 unknown SPRINGS.gentle / SPRINGS.snappy references with SPRINGS.smooth / SPRINGS.snap / SPRINGS.bouncy, lifts the implicit phase state into a controlled phase / onPhaseChange API plus renderAction slot, and consolidates the useRef arrays into single named refs grouped via useMemo so the file passes the library's strict React-hooks lint.