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.

×+relu

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.

Customize
Operands
3
4
5

Installation

npx shadcn@latest add https://craftbits.dev/r/neuron-graph-builder.json

Usage

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

  1. 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 y waits 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.
  2. 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.
  3. Forward-pass animation. Once all three slots are filled, the component pauses briefly, transitions to a complete stage, then walks the forward pass: each operation node flashes its fill, reveals the computed value (a×b12, then 12+c → result, then relu(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 node y lights up green with the final value at the end.
  4. 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.
  5. 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.
  6. Live region narration. A polite aria-live paragraph below the SVG narrates the current stage and any error hint; an assertive aria-live SR-only region announces the complete and done milestones the moment they commit.
  7. Keyboard fallback. Each palette badge is a role="button" with tabIndex={0}. Enter or Space on a focused badge auto-places it in its correct slot — same animation, no drag required.

Props

PropTypeDefaultDescription
anumber3First multiplicand.
bnumber4Second multiplicand.
cnumber5Addend (bias).
aLabelstring"a"Label for the first leaf.
bLabelstring"b"Label for the second leaf.
cLabelstring"c"Label for the third leaf.
yLabelstring"y"Label for the output node.
transitionTransitionSPRINGS.snapOverride the per-node-evaluation transition.
popTransitionTransitionSPRINGS.bouncyOverride the value-reveal pop transition.
onPlace(op) => voidFires every time a badge is correctly placed.
onComplete(output) => voidFires after the forward pass completes.
onReset() => voidFires when the user resets the puzzle.
classNamestringMerged onto the root via cn().

Accessibility

  • The SVG is role="img" with an aria-label summarising the expression, the operand values, and the current stage.
  • Each palette badge is role="button" with tabIndex={0} and a descriptive aria-label (Place × operation. Drag to a slot, or press Enter to auto-place.). Enter and Space auto-place the badge — no drag required — so keyboard-only and assistive-tech users can complete the puzzle.
  • An assertive live region announces the complete and done milestones 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 consumed SvgLabel from the SVG primitives and ChallengeBtn from the lesson chrome, hardcoded a = 3, b = 4, c = 5 and the labels a, 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 to var(--cb-*) semantic tokens, and re-keys the per-stage springs to SPRINGS.snap and SPRINGS.bouncy.