Dual Parameter Explorer

Probe any two-parameter function f(a, b) interactively. Two sliders drive a and b over caller-supplied ranges; the live result renders as a big numeric readout (scalar), a 16x16 colour-encoded heatmap of the (a, b) plane (contour), or a 1-D line plot of f(·, b) with b held fixed (plot).

f(a, b)a = 0.50 · b = 0.60 · 0.175
0.50
0.60
Customize
Parameters
0.50
0.60
Display
contour

Installation

npx shadcn@latest add https://craftbits.dev/r/dual-parameter-explorer.json

Usage

import { DualParameterExplorer } from "@craft-bits/core";
 
<DualParameterExplorer
  fn={(a, b) => a * b - (a * a) / 2}
  aRange={[0, 1]}
  bRange={[0, 1]}
  displayMode="contour"
/>

Teach hyperparameter sensitivity — sweep a learning rate / momentum pair:

<DualParameterExplorer
  fn={(lr, momentum) => simulateOptimizer(lr, momentum).finalLoss}
  aRange={[0.001, 0.5]}
  bRange={[0, 0.99]}
  aLabel="η"
  bLabel="μ"
  displayMode="contour"
/>

Drive a and b from outside:

const [a, setA] = useState(0.5);
const [b, setB] = useState(0.5);
 
<DualParameterExplorer
  fn={(x, y) => Math.sin(x) * Math.cos(y)}
  aRange={[-3, 3]}
  bRange={[-3, 3]}
  a={a}
  onAChange={setA}
  b={b}
  onBChange={setB}
  displayMode="plot"
/>

Understanding the component

  1. Three display modes, one probe. scalar shows only the big numeric readout — useful when the magnitude of f is the story. contour renders a 16x16 grid heatmap of f across the entire (a, b) plane, with the current cell outlined. plot plots f(·, b) over aRange — a 1-D slice through the surface holding b fixed at its slider value.
  2. Contour normalization. The heatmap normalizes accent intensity across the visible grid: the smallest f is ~8% accent, the largest is ~93%. Non-finite samples fall back to a neutral range so degenerate functions don't crash the grid.
  3. Plot auto-scales. The 1-D plot pre-samples f(·, b) and grows the y-axis to fit, with 12% padding above and below. The marker for (a, f(a, b)) rides the curve as a moves; the vertical guide tracks the current a.
  4. Live readout is universal. Every mode surfaces a, b, and f(a, b) in the header bar — formatters are caller-controlled via formatA / formatB / formatValue.
  5. Controlled and uncontrolled, twice. Both a and b pair a controlled value + onChange with an uncontrolled default*. Out-of-range values are clamped on read so a stale controlled prop can't corrupt the layout.
  6. Spring transitions. Cell opacities, the curve, the vertical guide, and the marker all animate with SPRINGS.smooth (or duration: 0 under reduced motion).
  7. Built on LabeledSlider. The two sliders are stacked instances of the library's LabeledSlider — installing this component via shadcn pulls labeled-slider in as a transitive dependency.

Props

PropTypeDefaultDescription
fn(a: number, b: number) => numberThe function to explore.
aRangereadonly [number, number][0, 1]Domain of the a axis.
bRangereadonly [number, number][0, 1]Domain of the b axis.
aStepnumber(aMax − aMin) / 100Slider step for a.
bStepnumber(bMax − bMin) / 100Slider step for b.
aLabelstring"a"Slider + readout label for a.
bLabelstring"b"Slider + readout label for b.
anumberControlled value of a.
defaultAnumbermidpoint of aRangeUncontrolled initial a.
onAChange(a: number) => voidFires on every a commit.
bnumberControlled value of b.
defaultBnumbermidpoint of bRangeUncontrolled initial b.
onBChange(b: number) => voidFires on every b commit.
displayMode"scalar" | "contour" | "plot""scalar"Visualization style.
formatValue(value: number) => stringtofixed-3Format the result.
formatA(a: number) => stringtofixed-2Format a for the readout.
formatB(b: number) => stringtofixed-2Format b for the readout.
transitionTransitionSPRINGS.smoothSpring for readout / marker / heatmap cell updates.
classNamestringMerged onto the root <div> via cn().

Accessibility

  • The whole figure is role="figure" with an aria-label describing the probe ("Function probe over aLabel and bLabel").
  • The header readout is aria-live="polite", so screen readers announce a, b, and f(a, b) whenever any slider moves.
  • The contour grid is role="img" with an aria-label summarizing the value range.
  • The plot is role="img" with an aria-labelledby heading naming the function.
  • Each slider is a native <input type="range"> wrapped in LabeledSlider, inheriting first-class keyboard and screen-reader support (arrow keys, Home, End, aria-valuenow / aria-valuetext).
  • Animation respects prefers-reduced-motion: reduce — all springs collapse to an instant swap.

Credits

  • Extracted from: algoflashcards (src/lessons/primitives/viz/DualParameterExplorer.tsx). Stripped the lesson-specific distinct-sample gate, sparkline, prompt slot, and per-track trackHex styling; generalized to a pure fn(a, b) => number probe with three display modes, controlled + uncontrolled state pairs for both axes, and built on the library's LabeledSlider.