Anatomy of a colormap
Summary
A scroll-driven teardown of every component that makes a colormap useful — anchor stops, oklab mixing, the load-bearing lightness axis, diverging midpoints, and the grayscale test.
A colormap is the small, opinionated function that turns a number into a color. Get it right and the data speaks; get it wrong and the picture lies. What follows pulls every component apart and puts it back together — for both sequential and diverging maps. Scroll the disassembly, then keep the two labeled plates at the end as a checklist next time you reach for a default.
A colormap is, at heart, a function.
It takes a normalized data value in [0, 1] and returns a color. Continuous in, continuous out. Every pixel in a heatmap, every cell in a choropleth — the same tiny function evaluated again and again.
The whole craft sits in which color it returns, and how those colors change as you walk across the input range.
Built from a handful of anchor stops.
A colormap is rarely defined point-by-point. A designer picks a handful of anchor colors at fixed positions and lets math fill in the rest.
Viridis — the bar above — climbs in lightness from a dark violet to a bright yellow.
The space between stops is interpolated.
Between two stops, the renderer walks a line through some color space. The thin ramps above mix only Viridis's two endpoints. In sRGB the dark violet disappears and most of the bar is yellow. In oklab the two ends share the length, and the straight path between them runs through tan.
The wide bar is the real map: every stop, mixed in oklab. We still read lightness, chroma, and hue in oklch — that is a measurement, not the mixing space. Oklab is the one that passes through a neutral midpoint without picking up a stray hue.
Lightness carries the magnitude.
This is the load-bearing axis. A well-built sequential map has lightness that climbs monotonically from one end to the other — no dips, no plateaus. Viridis was designed exactly this way.
Hue tells the story; chroma sets the volume.
Hue is the angle around the color wheel; chroma is how far from gray you push. Viridis rotates hue the long way — violet → blue → teal → green → yellow.
Its chroma sags through the blue-teal middle, where the gamut is narrow, and surges at the yellow end. Push chroma too hard anywhere and the colors clip outside the screen's gamut.
Some data has a middle.
Two hues stretch outward from a near-neutral midpoint: cool below, neutral at zero, warm above.
The midpoint is sacred.
A diverging map must be symmetric in lightness, chroma, and magnitude — the two arms are mirror images. Lightness peaks (Vik) or troughs (Berlin) at the midpoint, and equal data distances on either side must look like equal perceptual distances.
Anchor the midpoint to a meaningful number — not the data minimum or median. Otherwise the bar misreads small noise as a signal.
The map only works if the domain fits.
The same ten measurements, from 1 to 1,000, are drawn on both bars. On the linear bar, 1 through 50 land on one dark color, and 200, 500, and 1,000 take the rest of the ramp. On the log bar each decade gets an equal share, so 1, 10, 100, and 1,000 sit evenly apart.
Mismatch the scale and a map is dominated by outliers — or the interesting variation is crushed into one color.
Equal data steps deserve equal perceptual steps.
This is what people mean by “perceptually uniform.” A change of 0.1 near the bottom should look about as different as a change of 0.1 near the top. Otherwise the picture invents drama where there is none — and hides drama that is real.
Viridis was built for that evenness; we are only plotting it here. The two rows use the same colors. The top row samples them at equal steps. The bottom row warps the positions, so equal steps in data become a flat stretch and then a cliff. The mixing space does not buy this — the spacing of the samples does.
Roughly 8% of men see it differently.
Red-green color vision deficiency is the common case. The bar is filtered through an approximation of deuteranopia, and the lightness curve stays up. Hue separation shrinks. The climb in L* does not.
That is why lightness carries the ordering. Hue can help a reader who sees it; it cannot be the only thing the map relies on.
Strip the color. Does the order survive?
The simplest diagnostic. Convert the colormap to luminance only. If you can still read the data — small is dark, large is bright, every step distinct from its neighbors — the map is doing real work.
If the grayscale collapses into bands or zebra stripes, the map was leaning on hue alone. Time to redesign.
Now: put it back together.
Two maps, fully labeled.
Side by side: the same components, deployed differently. The sequential map runs low to high. The diverging map pivots around a meaningful center. Each curve is read straight off the stops.
Anatomy of a sequential map · Viridis
for ordinal magnitudes · low → high · no meaningful zero
for signed deviations · cool ← center → warm · meaningful zero
A short field checklist.
Before you ship a colormap, ask yourself:
- LightnessIs L* monotonic (sequential) or symmetric around the midpoint (diverging)?
- InterpolationAre the in-between colors mixed in oklab — not raw sRGB? Read L, C, and h in oklch; mix the ramp in oklab.
- DomainDoes the data scale (linear · log · symlog · quantile) match the distribution?
- MidpointIf diverging, is the midpoint anchored to a meaningful reference, not the data median?
- CVDDoes the ordering survive deuteranopia and protanopia simulation?
- GrayscaleStripped of color, is the order still readable end-to-end?
- GamutDo any anchor stops clip outside sRGB / P3 at the chroma you've asked for?
- DiscretizationContinuous, or binned? If binned, are step boundaries placed where they tell the truth?
Related resources
Citation
colorcurio.us. “Anatomy of a colormap.” Published 2026-05-30. https://colorcurio.us/notes/anatomy-of-a-colormap