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