ROC Curve Explorer
Plots true-positive-rate against false-positive-rate as the classifier's threshold sweeps from permissive to strict. The area under the curve (AUC) summarizes overall discriminative power; the marker on the curve tracks the current threshold.
ROC curve. AUC 1.000. Threshold 0.50: FPR 0.0%, TPR 100.0%.
AUC1.000
t0.50
FPR0.0%
TPR100.0%
Customize
Classifier
strong
Threshold
0.50
Display
Installation
npx shadcn@latest add https://craftbits.dev/r/roc-curve-explorer.jsonUsage
import { ROCCurveExplorer } from "@craft-bits/core";
<ROCCurveExplorer scores={scores} labels={labels} />Pass pre-computed ROC points instead:
<ROCCurveExplorer
points={[
{ threshold: 1.0, fpr: 0, tpr: 0 },
{ threshold: 0.7, fpr: 0.1, tpr: 0.6 },
{ threshold: 0.4, fpr: 0.3, tpr: 0.9 },
{ threshold: 0.0, fpr: 1, tpr: 1 },
]}
/>Drive the threshold from outside:
const [t, setT] = useState(0.5);
<ROCCurveExplorer
scores={scores}
labels={labels}
threshold={t}
onThresholdChange={setT}
/>Understanding the component
- Two input modes. Either pass paired
scores+labelsarrays and let the component derive the ROC points, or passpointsdirectly. When both are supplied,pointswins — useful for plotting curves that don't have raw scores (e.g., remote inference results). - Threshold sweep. When
scores+labelsare given, the component sorts every unique score, computes(FPR, TPR)at each, and dedupes to keep the path compact. The resulting curve runs corner-to-corner from(0, 0)to(1, 1). - AUC via trapezoidal integration. AUC is the trapezoidal area under the ROC line. AUC = 0.5 means random guessing (the diagonal); 1.0 means perfect separation. The readout below the chart shows the value to three decimals.
- Threshold marker. A filled circle on the curve locates the current threshold. Dragging the threshold slider — or driving
thresholdexternally — animates the marker between curve points withSPRINGS.snap. The closest curve point by threshold wins (the curve is piecewise-constant between observed scores). - Diagonal reference. When
showDiagonalis on, a dashedy = xline invar(--cb-fg-subtle)shows where a random classifier would sit. Curves above the diagonal beat random. - Reduced motion.
prefers-reduced-motion: reduceshort-circuits both the curve fade-in and the marker spring — they snap instantly.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
scores | readonly number[] | — | Raw classifier scores, paired with labels. |
labels | readonly boolean[] | — | Ground-truth labels (true = positive class). |
points | readonly ROCPoint[] | — | Pre-computed ROC points. Takes precedence over scores + labels. |
threshold | number | — | Controlled threshold in [0, 1]. |
defaultThreshold | number | 0.5 | Initial uncontrolled threshold. |
onThresholdChange | (t: number) => void | — | Fires when the threshold changes. |
showAUC | boolean | true | Render the AUC readout. |
showDiagonal | boolean | true | Render the y = x random-classifier reference. |
transition | Transition | SPRINGS.snap | Override the marker transition. |
className | string | — | Merged onto the root <div> via cn(). |
The shape:
interface ROCPoint {
threshold: number; // in [0, 1]
fpr: number; // in [0, 1]
tpr: number; // in [0, 1]
}Accessibility
- The root
<div>isrole="figure"with a visually hidden summary of AUC plus the current threshold's(FPR, TPR). - The threshold slider is a native
<input type="range">— keyboard control (arrow keys, Home, End) and screen-reader semantics come for free. - Focus ring uses
:focus-visiblewith the accent color and a 2px offset so keyboard users always see where they are. - Motion respects
prefers-reduced-motion: reduce— curve and marker animations collapse to instant updates.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/viz/ROCCurveExplorer.tsx). The source had two lesson modes (Explore / Predict), a paired score-scatter panel, threshold-quiz rounds, narration heuristics, and ausePredictRoundscontroller. The library extract is the bare ROC primitive — points in, curve + AUC + threshold marker out.