Gradient Arrow Field
A 2D vector field showing the gradient of a scalar function f(x, y) at each point on a regular grid. Each arrow points in the direction of steepest ascent (or flip them with descent to point downhill — the direction gradient descent would step). Arrow length is proportional to gradient magnitude, normalised so the longest arrow fills roughly one cell.
Use it to teach why optimizers do what they do: every gradient-descent step follows the local arrow. Where the field swirls, optimizers struggle; where it's near-zero, they stall (saddle points, plateaus).
Gradient arrow field over a 12 by 12 grid showing the gradient (ascent) of f over x in [-2, 2] and y in [-2, 2]. Arrow length is proportional to gradient magnitude.
Customize
Function
bowl
Resolution
12
Display
Installation
npx shadcn@latest add https://craftbits.dev/r/gradient-arrow-field.jsonUsage
import { GradientArrowField } from "@craft-bits/core";
<GradientArrowField />Show the gradient-descent direction (arrows flipped to point downhill) over a custom function, overlaid on the function's own contour heatmap:
<GradientArrowField
fn={(x, y) => Math.sin(x) * Math.cos(y) + 0.1 * (x * x + y * y)}
descent
showFunction
colorScheme="viridis"
/>Understanding the component
- Symmetric finite differences. The gradient is estimated numerically at each grid point with a centred difference:
(f(x + h, y) - f(x - h, y)) / (2h)and similarly fory. Thedhprop sets the step size; the default0.01is fine for any smooth function in the typical visible range. No symbolic differentiation required — drop in any pure function. - Length normalisation. Each arrow's screen length is scaled by
mag / maxMagso the longest arrow fills roughly 80% of a grid cell, regardless of the absolute scale off. A near-flat region next to a steep one always reads as "small arrow vs big arrow," never "everything is tiny." - Ascent vs descent.
descent={false}(default) points each arrow in the+gradientdirection (steepest ascent).descent={true}flips every arrow to-gradient— the direction a gradient-descent step would move from that point. The same plot tells the optimization story by flipping a single boolean. - Optional contour heatmap. Set
showFunctionto renderfitself as agridSize x gridSizeheatmap underneath the arrows. Three color schemes (accent,viridis,plasma) share the perceptual ramps used byLossLandscape, so the two components compose cleanly. - Near-zero hiding. Arrows whose magnitude is below 2% of the maximum are faded out — keeps saddle points and local minima looking like calm spots rather than a jittery cluster of tiny arrows.
- Reduced-motion fallback. With
prefers-reduced-motion: reduce, the arrows snap to their new pose on prop changes — no spring transitions.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
fn | (x: number, y: number) => number | bowl: (x, y) => x * x + y * y | Scalar field whose gradient is drawn. |
xRange | readonly [number, number] | [-2, 2] | Visible math-space x-range. |
yRange | readonly [number, number] | [-2, 2] | Visible math-space y-range. |
gridSize | number | 12 | Grid resolution in samples per side. |
descent | boolean | false | When true, arrows point in the -gradient direction (gradient descent). |
showFunction | boolean | false | Render f as a contour heatmap underneath the arrows. |
colorScheme | 'accent' | 'viridis' | 'plasma' | 'accent' | Color ramp for the contour heatmap. |
dh | number | 0.01 | Symmetric finite-difference step for the gradient estimate. |
size | number | 360 | SVG side length in pixels. |
transition | Transition | SPRINGS.smooth | Spring used for arrow pose transitions. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The figure is
role="figure"with anaria-live="polite"summary that announces the grid resolution, the visible x and y ranges, and whether the field shows the ascent or descent direction. - Color is never the only signal — direction and magnitude are encoded in arrow geometry, so the figure remains readable under any palette.
prefers-reduced-motion: reducesnaps every arrow to its new pose with no spring transitions.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/nn/GradientArrowField.tsx). The original was a tightly-scoped three-class softmax probability-bar interaction — it taught the gradientp - yfor a specific cross-entropy lesson. The library version generalises to a true 2D vector field over any(x, y) -> number: finite-difference gradients on a regular grid, length-normalised arrows, adescentflip, and an optional contour heatmap that shares the color ramps used byLossLandscape. The two compose: drop aLossLandscapeheatmap behind aGradientArrowFieldand you have a complete picture of "what the surface looks like" and "which way gradient descent would step."