Ramps for ordered categories
If reordering changes the meaning, use a one-hue ramp; else one color.
From dataviz (references/color-formula.md, palette.md).
Color by value, or by kind
Switch datasets; compare nominal and ordinal ones.
Stronger where biggerBad
fill: ramp[rankOf(value)]One hue, slot 1Good
fill: var(--series-1)const fill = isOrdinal
? (d, i) => ORDINAL_RAMP[i]
: () => "var(--series-1)";
const ORDINAL_RAMP = ["#86b6ef", "#5598e7", "#2a78d6", "#1c5cab", "#104281"];
The swap test
Shuffle a few times; watch the funnel's ramp.
ProductsNominal
Signup funnelOrdinal
stages.forEach((s, i) => (s.fill = ORDINAL_RAMP[i]));
render(shuffle(stages));
Where the ramp may start
Move the first step; keep it above 2:1.
Contrast vs surface
node scripts/validate_palette.js "#86b6ef,#5598e7,#2a78d6,#1c5cab,#104281" --ordinal --mode light
[PASS] Lightness monotone steps read light→dark
[PASS] Adjacent ΔL all gaps >= 0.06
[PASS] Light-end contrast #86b6ef at 2.06:1 vs surface
[PASS] Single hue hue spread 3°
Why it matters, pros and cons
Why it matters
Bar length already encodes value. Spend color on order when there is one.
Pros
- Nominal charts need no legend
- Ordinal sequence reads at a glance
- Sorting never repaints a bar
Cons
- The light end sits near 2:1; keep labels
- Past 5–7 steps, neighbors blur together
- Separate series need categorical slots instead