Neuron Graph Builder
A drag-to-slot computation graph builder for the canonical single-neuron computation y = relu(a × b + c). Three operation badges — ×, +, relu — sit in a palette beneath the graph. Three empty slots wait in the DAG above. The learner drags each badge to the slot whose inputs match — discovering, by assembling, that a neuron is itself a small computation graph: weighted inputs, a sum, an activation. Once every slot is filled, the assembled graph runs an animated forward pass that flashes each operation, sends value pulses along the edges, and lights up the output.
This is the canonical "a neuron is a graph" primitive. The three operands and four labels are parameterised so the same component teaches z = relu(w · x + b) or any other relu(a · b + c) shape.
A neuron computes relu(a × b + c). The expression decomposes into three operations, applied bottom to top. Drag each operation badge to its correct slot in the graph.
Installation
npx shadcn@latest add https://craftbits.dev/r/neuron-graph-builder.jsonUsage
import { NeuronGraphBuilder } from "@craft-bits/viz/neuron-graph-builder";
<NeuronGraphBuilder />Override the operands — handy when the same component drives a worked example with different numbers:
<NeuronGraphBuilder a={2} b={6} c={-3} />Relabel the nodes — handy when the same component is reused under different variable names (for instance, a linear neuron with weight w, input x, and bias b):
<NeuronGraphBuilder
a={2}
b={3}
c={-1}
aLabel="x"
bLabel="w"
cLabel="b"
yLabel="z"
/>Subscribe to placement and completion events to drive a sibling transcript or progress bar:
<NeuronGraphBuilder
onPlace={(op) => {
/* "mul" | "add" | "relu" */
}}
onComplete={(output) => {
/* the final relu(a×b + c) value */
}}
onReset={() => {
/* user reset the puzzle */
}}
/>Understanding the component
- Three-tier graph plus a palette. Three leaves anchor the bottom row, three operation slots stack bottom-to-top in the middle band, and the output node
ywaits at the top. A separate palette row below the graph holds the three draggable operation badges. Edge endpoints are computed from each pair of node centres minus the node radii so the line never crosses into a circle. - Drag-to-slot interaction. A pointer-down on a palette badge captures the pointer (so the badge follows the cursor outside the SVG), tracks the cursor position in SVG user-space via
getScreenCTM().inverse(), and snaps to the nearest unfilled slot within a fixed radius. Correct drops fill the slot, brighten the connected edge, and fire a glow pulse; wrong drops flash the target slot red and update the narration with a targeted hint. - Forward-pass animation. Once all three slots are filled, the component pauses briefly, transitions to a
completestage, then walks the forward pass: each operation node flashes its fill, reveals the computed value (a×b→12, then12+c→ result, thenrelu(result)), sends a value pulse along the outgoing edge, settles a value label on the midpoint of the edge, and lights up the next node as ready. The output nodeylights up green with the final value at the end. - Imperative motion. Every step uses motion's
animate()against a single SVG element — fill colour, stroke, opacity, position — so the broader scene never re-renders mid-animation. Outstanding timeouts are tracked and cancelled on unmount or reset. - Reduced motion. Under
prefers-reduced-motion: reduce, every staged animation collapses to an instant attribute set, the value pulses jump straight to their destination at zero opacity, the hover sonar pulse around a slot is suppressed, and step delays are skipped entirely. - Live region narration. A polite
aria-liveparagraph below the SVG narrates the current stage and any error hint; an assertivearia-liveSR-only region announces thecompleteanddonemilestones the moment they commit. - Keyboard fallback. Each palette badge is a
role="button"withtabIndex={0}.EnterorSpaceon a focused badge auto-places it in its correct slot — same animation, no drag required.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
a | number | 3 | First multiplicand. |
b | number | 4 | Second multiplicand. |
c | number | 5 | Addend (bias). |
aLabel | string | "a" | Label for the first leaf. |
bLabel | string | "b" | Label for the second leaf. |
cLabel | string | "c" | Label for the third leaf. |
yLabel | string | "y" | Label for the output node. |
transition | Transition | SPRINGS.snap | Override the per-node-evaluation transition. |
popTransition | Transition | SPRINGS.bouncy | Override the value-reveal pop transition. |
onPlace | (op) => void | — | Fires every time a badge is correctly placed. |
onComplete | (output) => void | — | Fires after the forward pass completes. |
onReset | () => void | — | Fires when the user resets the puzzle. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The SVG is
role="img"with anaria-labelsummarising the expression, the operand values, and the current stage. - Each palette badge is
role="button"withtabIndex={0}and a descriptivearia-label(Place × operation. Drag to a slot, or press Enter to auto-place.).EnterandSpaceauto-place the badge — no drag required — so keyboard-only and assistive-tech users can complete the puzzle. - An assertive live region announces the
completeanddonemilestones the moment they commit; a polite live region carries the per-stage narration so screen-reader users get the same explanation as sighted users. - All decorative arrows, axes, glow rings, breathing pulses, edge labels, and value pulses are
aria-hidden— the same information is encoded in the SVG label and the live regions. - Colour is never the only signal — every stage is also encoded in the narration text and the textual
?→ symbol → value transitions inside every node. - Motion respects
prefers-reduced-motion: reduce— the staged animation collapses to an instant attribute set, the hover sonar pulse is suppressed, and step delays are skipped entirely.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/math/NeuronGraphBuilder.tsx). The source was a tightly bundled lesson component — it consumedSvgLabelfrom the SVG primitives andChallengeBtnfrom the lesson chrome, hardcodeda = 3, b = 4, c = 5and the labelsa, b, c, y, baked the narration prose directly into the file, and depended on the per-track palette tokens. The viz extract drops the lesson chrome, parameterises the three operand values and the four labels, swaps the colour palette over tovar(--cb-*)semantic tokens, and re-keys the per-stage springs toSPRINGS.snapandSPRINGS.bouncy.