Decision Tree Viz
A top-down decision tree. Internal nodes render as rounded rectangles holding a condition ("x < 5?", "feature == cat"); leaves render as pills holding an outcome label ("buy", 42). Each internal node's outgoing edges are labeled "yes" (left) and "no" (right) by default. A highlightPath prop traces the active root-to-leaf decision in the accent color — ideal for narrating ML decision trees, expression trees, or game-tree search.
Customize
Shape
3
Highlight
none
Installation
npx shadcn@latest add https://craftbits.dev/r/decision-tree-viz.jsonUsage
import { DecisionTreeViz, type DecisionTreeNode } from "@craft-bits/core";
const tree: DecisionTreeNode = {
condition: "income > 80k?",
yes: {
condition: "stay > 5y?",
yes: {
condition: "price < 500k?",
yes: { outcome: "buy" },
no: { outcome: "rent" },
},
no: { outcome: "rent" },
},
no: { outcome: "rent" },
};
<DecisionTreeViz tree={tree} highlightPath={["yes", "no"]} />Numeric outcomes (expression tree style):
<DecisionTreeViz
tree={{
condition: "x > 0?",
yes: {
condition: "x > 10?",
yes: { outcome: 100 },
no: { outcome: 10 },
},
no: { outcome: 0 },
}}
highlightPath={["yes", "yes"]}
/>Understanding the component
- Recursive shape, two-pass layout. The
DecisionTreeNodetype is recursive —condition+yes/nofor internals,outcomefor leaves. The renderer first walks DFS to assign each leaf a horizontal slot, then walks top-down placing each internal at the midpoint of its children's centroids. This handles unbalanced trees, single-branch chains, and deep recursion without overlap. - Two node shapes. Internals are rounded rectangles (
rx = 6); leaves are pills (rx = h / 2). The shape difference encodes the algorithmic role at a glance — branching question vs. terminal answer. - Highlight path is an array of decisions.
highlightPath={["yes", "no", "yes"]}is the sequence of branches taken from the root. The component resolves which nodes + edges lie on that path and gives them the accent treatment; everything off-path drops to ~65% opacity so the active trace pops. - Edge labels live in tiny pills. Each labeled edge has a
var(--cb-bg)-filled rounded rect behind its text so theyes/noglyph reads cleanly when it crosses a line. Disable withshowLabels={false}for dense or numerically-labeled trees. - Reduced motion.
usePrefersReducedMotion()short-circuits every transition to{ duration: 0 }— node entries, edge draws, and path-highlight transitions all snap into place.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
tree | DecisionTreeNode | required | Recursive tree definition. Internal nodes use condition + yes / no; leaves use outcome. |
highlightPath | readonly ("yes" | "no")[] | [] | Sequence of decisions traversed from root. On-path nodes + edges get the accent treatment. |
showLabels | boolean | true | Render the yes / no glyph on each outgoing edge. |
compact | boolean | false | Squeeze padding ~15% for embedded usage. |
className | string | — | Merged onto the outer <svg>. |
Accessibility
- The outer
<svg>isrole="img". When ahighlightPathis supplied, thearia-labelflattens the decisions to a screen-readable trail ("Decision tree path: income > 80k? -> yes; stay > 5y? -> no; outcome: rent."); without a path it reads"Decision tree visualization.". - Color is never the only signal — every internal node renders its condition string, every leaf renders its outcome string, and every labeled edge carries its
yes/noglyph. The accent treatment is layered on top of those labels, not in place of them. - Motion respects
prefers-reduced-motion: node entries, edge draws, and the path-highlight transition all collapse to instant when the user has opted out.
Credits
- Extracted from:
algoflashcards(src/lessons/primitives/viz/DecisionTreeViz.tsx). The original was a backtracking-lesson widget with six explicit node states and atrackHexaccent prop. The library version reframes the abstraction around the user-facing concept — internal nodes (condition) and leaves (outcome), with a singlehighlightPathto drive emphasis — so the same component reads cleanly for ML, expression, and game-tree contexts.