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.jsonUsage
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
- Three display modes, one probe.
scalarshows only the big numeric readout — useful when the magnitude offis the story.contourrenders a 16x16 grid heatmap offacross the entire(a, b)plane, with the current cell outlined.plotplotsf(·, b)overaRange— a 1-D slice through the surface holdingbfixed at its slider value. - Contour normalization. The heatmap normalizes accent intensity across the visible grid: the smallest
fis ~8% accent, the largest is ~93%. Non-finite samples fall back to a neutral range so degenerate functions don't crash the grid. - 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 asamoves; the vertical guide tracks the currenta. - Live readout is universal. Every mode surfaces
a,b, andf(a, b)in the header bar — formatters are caller-controlled viaformatA/formatB/formatValue. - Controlled and uncontrolled, twice. Both
aandbpair a controlledvalue+onChangewith an uncontrolleddefault*. Out-of-range values are clamped on read so a stale controlled prop can't corrupt the layout. - Spring transitions. Cell opacities, the curve, the vertical guide, and the marker all animate with
SPRINGS.smooth(orduration: 0under reduced motion). - Built on
LabeledSlider. The two sliders are stacked instances of the library'sLabeledSlider— installing this component via shadcn pullslabeled-sliderin as a transitive dependency.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
fn | (a: number, b: number) => number | — | The function to explore. |
aRange | readonly [number, number] | [0, 1] | Domain of the a axis. |
bRange | readonly [number, number] | [0, 1] | Domain of the b axis. |
aStep | number | (aMax − aMin) / 100 | Slider step for a. |
bStep | number | (bMax − bMin) / 100 | Slider step for b. |
aLabel | string | "a" | Slider + readout label for a. |
bLabel | string | "b" | Slider + readout label for b. |
a | number | — | Controlled value of a. |
defaultA | number | midpoint of aRange | Uncontrolled initial a. |
onAChange | (a: number) => void | — | Fires on every a commit. |
b | number | — | Controlled value of b. |
defaultB | number | midpoint of bRange | Uncontrolled initial b. |
onBChange | (b: number) => void | — | Fires on every b commit. |
displayMode | "scalar" | "contour" | "plot" | "scalar" | Visualization style. |
formatValue | (value: number) => string | tofixed-3 | Format the result. |
formatA | (a: number) => string | tofixed-2 | Format a for the readout. |
formatB | (b: number) => string | tofixed-2 | Format b for the readout. |
transition | Transition | SPRINGS.smooth | Spring for readout / marker / heatmap cell updates. |
className | string | — | Merged onto the root <div> via cn(). |
Accessibility
- The whole figure is
role="figure"with anaria-labeldescribing the probe ("Function probe overaLabelandbLabel"). - The header readout is
aria-live="polite", so screen readers announcea,b, andf(a, b)whenever any slider moves. - The contour grid is
role="img"with anaria-labelsummarizing the value range. - The plot is
role="img"with anaria-labelledbyheading naming the function. - Each slider is a native
<input type="range">wrapped inLabeledSlider, 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-tracktrackHexstyling; generalized to a purefn(a, b) => numberprobe with three display modes, controlled + uncontrolled state pairs for both axes, and built on the library'sLabeledSlider.