Gradient Field Viz

A 2-D heatmap of every layer's gradient magnitude at every training step. Rows are layers (top = output-side, bottom = deeper). Columns are training steps (left → right). Cell intensity encodes |grad|. A vertical column outline highlights the current step; hovering or focusing a cell reveals its exact (step, layer, value) triple. Use it to show the canonical vanishing- and exploding-gradient signatures unfold over training.

Gradient field heatmap. 8 layers across 60 training steps. Current step 60 of 60.
Gradient fieldstep 60 / 60

current step 60 / 60

Customize
Field
vanishing
Display
accent

Installation

npx shadcn@latest add https://craftbits.dev/r/gradient-field-viz.json

Usage

import { GradientFieldViz } from "@craft-bits/core";
 
<GradientFieldViz gradients={field} />

Scrub the heatmap from a parent controller (controlled mode):

<GradientFieldViz
  gradients={field}
  currentStep={step}
  onCurrentStepChange={setStep}
/>

Show the sign of the gradient with the diverging ramp:

<GradientFieldViz gradients={signedField} colorScheme="diverging" />

Understanding the component

  1. Cell layout. Input is gradients: number[][] shaped numSteps × numLayers. Each row i is a training step; each column j inside that row is the gradient magnitude (or signed gradient, under the diverging scheme) at layer j. Rows aren't required to be the same length — shorter rows are right-padded with 0 so a half-finished training run still renders cleanly without throwing.
  2. Two ramps, one job. accent (the default) maps |value| / max(|value|) to an alpha ramp on --cb-accent. diverging keeps the same magnitude mapping but switches the hue with the sign: negatives → --cb-info, positives → --cb-error. Both ramps share the same dynamic range so cross-scheme comparisons stay honest.
  3. Current-step column. A vertical motion.rect outlined in --cb-accent floats over the cursor column. It springs to its new x on currentStep change under SPRINGS.snap; reduced-motion users get an instant snap with no spring.
  4. Controlled+uncontrolled cursor. currentStep (controlled) and defaultCurrentStep (uncontrolled) mirror the Radix pattern. Uncontrolled defaults to numSteps − 1 so first paint lands on end-of-training, which is what most callers want to inspect.
  5. Hover tooltip. Pointer-enter on a cell pops a small L<n> · step <m> · <value> label hugging the top edge of the chart. The same triple is mirrored in an inline readout under the chart and announced through aria-live="polite" so screen-reader users hear the diagnosis.
  6. Keyboard scrub. Focus the SVG and press / to move the cursor one step at a time, Home / End to jump to the bounds. Clicking any cell sets the cursor to that step.

Props

PropTypeDefaultDescription
gradientsreadonly (readonly number[])[]numSteps × numLayers field. Non-finite values render as muted bg.
currentStepnumberControlled cursor step. Clamped to [0, numSteps − 1].
defaultCurrentStepnumbernumSteps − 1Uncontrolled initial cursor.
onCurrentStepChange(step: number) => voidFires on keyboard scrub and cell click.
colorScheme'accent' | 'diverging''accent'Cell-fill ramp.
transitionTransitionSPRINGS.snapSpring for the column-highlight transition.
classNamestringMerged onto the root <div> via cn().

Accessibility

  • The figure is role="figure" with an aria-labelledby that names the chart "Gradient field," and an aria-describedby that updates whenever the cursor or the hovered cell changes.
  • The SVG accepts focus (tabIndex=0) and exposes a visible focus-visible ring so keyboard users can find it. Arrow keys move the cursor one step at a time; Home / End snap to the bounds.
  • An aria-live="polite" summary announces the layer / step / value of the hovered cell, or the current step when no cell is hovered — so screen-reader users hear the same readout the chart shows visually.
  • All decorative axis labels, tick labels, layer labels, the column-highlight <rect>, and the floating tooltip carry aria-hidden so they don't double-announce.
  • prefers-reduced-motion: reduce snaps the column highlight instead of springing.
  • Color is never the only signal: the floating tooltip and the inline readout both spell out the value, and the cursor column is outlined as well as filled.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/viz/GradientFieldViz.tsx). The source was a 2-D function-surface visualizer — a heatmap of f(x, y) with overlaid gradient arrows and a draggable point that decomposed ∇f into ∂f/∂x and ∂f/∂y — built as a single-purpose teaching artefact for the partial-derivatives lesson (Explore / Predict / Challenge modes, narration, bookmarks, sin·cos / saddle / bowl preset picker). The library variant reframes the slot entirely to match the Backprop sidebar's pedagogical need: a 1-D-per-layer gradient field over training steps (rows = layers, cols = steps, intensity = |grad|). The shared phrase "gradient field" is the only structural overlap; everything else is rebuilt as a generic primitive — gradients: number[][], controlled+uncontrolled currentStep, accent / diverging ramps, keyboard scrub, hover tooltip, and an aria-live summary. Stripped the Explore / Predict / Challenge modes, function presets, draggable point, arrow-field overlay, per-axis decomposition, bookmarks, and every Widget / ChallengeBtn / FeedbackBadge dependency.