Subset Lattice

A Hasse diagram of the power-set lattice over a small set of elements. Renders 2^n nodes laid out in layers by popcount (the "diamond" — 1-3-3-1 for n=3, 1-4-6-4-1 for n=4), joined by edges between subsets that differ by exactly one element.

SubsetLattice is the canonical picture for SOS-DP, set-cover, bitmask-DP, and Travelling-Salesman-on-subsets narratives — anywhere the algorithm walks a subset lattice and the picture wants to read as which subsets are filled and how we got from the empty set to the full one.

0{}1{a}1{b}1{c}1{d}4{a,b}4{a,c}4{b,c}4{a,d}4{b,d}4{c,d}9{a,b,c}9{a,b,d}9{a,c,d}9{b,c,d}16{a,b,c,d}Subset lattice over {a, b, c, d} with 16 subsets.
Customize
Lattice
4
Overlays

Installation

npx shadcn@latest add https://craftbits.dev/r/subset-lattice.json

Usage

import { SubsetLattice } from "@craft-bits/core";
 
<SubsetLattice elements={["a", "b", "c"]} />;

Controlled selection on a bitmask:

const [mask, setMask] = useState<number | null>(0b011);
 
<SubsetLattice
  elements={["a", "b", "c"]}
  selected={mask}
  onSelectedChange={setMask}
/>;

Per-subset values plus a glowing optimal path:

const values = new Map<number, number>([
  [0b000, 0],
  [0b001, 3],
  [0b011, 5],
  [0b111, 9],
]);
 
<SubsetLattice
  elements={["a", "b", "c"]}
  values={values}
  optimalPath={[0b000, 0b001, 0b011, 0b111]}
/>;

Ghost mode — non-interactive nodes shrink into background scaffolding so the active sub-region pops:

<SubsetLattice
  elements={["a", "b", "c", "d"]}
  interactiveMasks={[0b0001, 0b0011, 0b0111, 0b1111]}
  ghostMode
/>

Understanding the component

  1. Popcount layering. Subsets are bucketed by popcount(mask) — empty set at the top, full set at the bottom. Within a layer, masks render in numeric order so geometry stays deterministic across renders.
  2. Cover edges only. Two subsets are joined iff one is reachable from the other by adding exactly one element. The lattice is drawn as undirected lines for visual clarity; the upward orientation of the picture conveys the lattice direction.
  3. Bitmask is the source of truth. The public API is a bitmask. The selectedSubset prop is sugar — it resolves to a bitmask via elements.indexOf, so passing element names you didn't declare is silently dropped.
  4. Controlled + uncontrolled selection. Pass selected + onSelectedChange (or selectedSubset) for controlled mode (the Radix pattern). Pass defaultSelected to let the component own the mask internally.
  5. Optimal path glows; everything else dims. When optimalPath is non-empty, its constituent edges glow at full opacity and width while every other edge dims to a whisper. Drop the prop to render every edge equally.
  6. Ghost mode. With interactiveMasks set and ghostMode enabled, non-interactive nodes shrink and dim. Use this when only a path or sub-lattice matters and the rest is scaffolding.
  7. Element cap. elements.length is clamped to 6 — the diagram tops out at 64 nodes. Beyond that, the lattice stops being readable and a different visualisation is the answer.
  8. Reduced motion. usePrefersReducedMotion() short-circuits every transition to instant. Node entries, edge draws, and the selected-node sonar pulse all snap into place.

Props

PropTypeDefaultDescription
elementsreadonly string[]requiredThe ground set indexing every subset. Clamped to at most 6.
valuesReadonlyMap<number, number | string | null>Per-subset values keyed by bitmask. Filled subsets render the value; others render the set-builder label.
selectednumber | nullControlled selected mask. Pair with onSelectedChange.
selectedSubsetreadonly string[] | nullSugar for selected — resolved via elements.indexOf.
defaultSelectednumber | nullnullUncontrolled initial mask.
onSelectedChange(mask: number) => voidFires when a subset is clicked or activated via keyboard.
highlightedEdgesreadonly [number, number][]Edges to highlight as [fromMask, toMask] pairs.
optimalPathreadonly number[]Sequence of masks forming an optimal path through the lattice.
interactiveMasksreadonly number[] | nullnull (all interactive)Restrict which subsets accept clicks.
ghostModebooleanfalseShrink non-interactive nodes into background scaffolding.
accentHexstringvar(--cb-accent)Track accent color. Use a #hex to enable drop-shadow glow on the selected node.
transitionTransitionSPRINGS.smoothOverride the node/edge transition spring.
classNamestringMerged onto the outer SVG element.

Accessibility

  • The outer SVG is role="img" with an aria-label summarising the ground set, subset count, and currently selected mask.
  • Every interactive node carries role="button", aria-label (subset + value), aria-pressed, and tabIndex={0}. Enter and Space both activate.
  • Every node's hit target is at least 44 by 44 px (WCAG 2.5.8 AAA), even though the visible circle is smaller.
  • Motion respects prefers-reduced-motion: node entries, edge draws, and the sonar pulse all snap to instant.

Credits

  • Extracted from: algoflashcards (src/lessons/primitives/viz/SubsetLattice.tsx). The original was scoped to a specific DP lesson — it took a dpValues: Map, hard-wired trackHex, and rendered HTML chrome around the SVG. The library extract generalises to elements: string[] (with the bitmask still the canonical key), adds a controlled / uncontrolled selection lever on the Radix pattern, accepts selectedSubset as sugar over the bitmask, routes node and edge dimensions through SVG_TOKENS, and drops every lesson-specific prop.