Colour and Colormaps

Naming colours, the cycle, and picking a colormap that does not invent structure.

Overview

Naming a colour

Colour is one of the strongest visual channels in a chart. It distinguishes series, encodes values, highlights points and sets the mood of the figure. Using it well makes a chart clearer. Using it poorly makes it confusing or inaccessible.

matplotlib has a flexible colour system. You can specify colours by name, by hex code, by RGB tuple, by cycle position or by grey level. Each notation has its place. Named colours are readable in code. Hex codes match design tools. Cycle positions keep colours stable across style changes.

Colormaps are the other half of the colour system. A colormap maps a range of numbers to a range of colours. Scatter plots, heatmaps and filled contours all use colormaps. Choosing the right one matters. A sequential colormap works for data that goes from low to high. A diverging colormap works for data with a meaningful centre, like a change from negative to positive. A qualitative colormap works for categories.

The most common mistake is using a rainbow colormap for sequential data. Rainbow is not perceptually uniform: equal steps in the data do not produce equal steps in perceived colour. Bright bands of yellow and cyan draw the eye to arbitrary positions. The viridis family was designed to avoid this. It is perceptually uniform, readable in greyscale, and accessible to most forms of colour vision deficiency.

This page covers colour specification, the property cycle, colormaps, normalisation, colour bars, custom maps and accessibility. It also covers alpha transparency, which is the simplest fix for overplotted data.

matplotlib accepts a few notations wherever a colour is wanted:

A CSS name: "crimson", "steelblue", "rebeccapurple".

Hex: "#4c72b0", with an optional alpha suffix.

A cycle link: "C0" through "C9", meaning the nth colour of the current property cycle. Using these keeps a figure steady when the style changes.

A grey level as a string: "0.55". The quotes matter. The bare number 0.55 isn't a colour.

An RGB or RGBA tuple of floats from 0 to 1.

Single letters "r", "g", "b", "k" also work. Are the ones the format-string shorthand uses.

Worth knowing

Colours accept CSS names, hex, C0–C9 from the cycle, a grey level as a string, or an RGB tuple.
set_prop_cycle changes the colours for one axes; a cycler can vary linestyle too, so the chart survives greyscale.
Colormaps are sequential (magnitude), diverging (around a meaningful centre) or qualitative (categories). The wrong kind invents structure.
jet is not perceptually uniform — its brightness rises and falls, so the eye reads boundaries the data does not have.
A colormap needs a colorbar; fig.colorbar takes the mappable that imshow or scatter returned.
Roughly one man in twelve cannot separate red from green — vary linestyle or marker as well as colour.

Colour and Colormaps

Naming colours, the cycle, and picking a colormap that doesn't invent structure.

Ways to name a colour

Five notations, all accepted anywhere a colour is wanted.

example_01.pymatplotlib
Output

The property cycle

Replace it and every later plot on that axes follows.

example_02.pymatplotlib
Output

Each axes has a property cycle that supplies the colour for successive plots. The default has ten colours and then repeats.

ax.set_prop_cycle(cycler(color=[...])) replaces it for one axes. plt.rcParams["axes.prop_cycle"] replaces it globally. This Is how you apply a house palette to a whole script.

A cycler can carry more than colour:

cycler(color=palette) + cycler(linestyle=["-", "--", ":", "-."])

+ pairs them elementwise, so the first series is the first colour and the first linestyle. That single change makes each chart in a script live being printed in greyscale. This Is worth more than it costs.

* gives the outer product instead, cycling each mix.

Three kinds of colormap

Picking the wrong kind invents structure that is not in the data.

example_03.pymatplotlib
Output

The choice of colormap is a statement about the data. The wrong one asserts a thing false.

Sequential — viridis, plasma, Blues — runs from low to high in one direction. Use for magnitudes: counts, temperatures, concentrations.

Diverging — coolwarm, RdBu, BrBG — has two directions from a neutral centre. Use when there's a real midpoint. Zero, an mean, a baseline. Applying one to data with no natural centre invents a boundary in the middle of the range.

Qualitative — tab10, Set2 — is a set of distinct hues with no sorting. Use for groups. Using a sequential map for groups implies an order that doesn't exist.

Cyclic — twilight, hsv — wraps around, for angles and phases.

Why not jet

It is not perceptually uniform, so equal steps in value look unequal.

example_04.pymatplotlib
Output

Colorbars

A colormap without a scale is decoration.

example_05.pymatplotlib
Output

A colour-mapped plot without a colorbar is decoration: the reader can see that values differ and not by how much.

fig.colorbar(mappable, ax=ax) needs the object returned by the drawing call. imshow, scatter, pcolormesh, contourf. That is why those return values are captured here.

label= names the quantity. This Is as key as it's on an axis. shrink and pad adjust size and spacing. orientation="flat" puts it below.

For a grid of subplots, fig.colorbar(im, ax=axes.ravel().tolist()) makes one bar serve all panels. This Is right when they share a scale. And they should, or the panels aren't close.

Colour is not the only channel

About one man in twelve cannot distinguish red from green.

example_06.pymatplotlib
Output

Why jet is a problem

jet was the default in older tools and still appears all over. It's a bad choice, for a reason that is measurable rather than aesthetic.

A good colormap is perceptually uniform: equal steps in the data look like equal steps in colour. viridis was designed for this. Its brightness grows steadily from one end to the other.

jet doesn't. Its brightness rises, falls and rises again, with a bright band in the middle. The eye reads a sharp brightness change as a boundary. jet shows edges and structure where the data is just right smooth. And hides real grads in the flat regions.

It also merges when converted to greyscale. Other values map to the same brightness.

The fourth editor plots the brightness of both. This Makes the gap visible rather than assertable.

Accessibility

Around 8% of men and 0.5% of women have some form of colour vision deficiency, most often trouble separating red from green.

Three habits cover most of it:

Avoid red against green as the main gap. Blue against orange is distinguishable for almost all.

Vary a second channel — linestyle, marker, or spot — so colour is reinforcing rather than carrying the meaning alone.

**Use viridis and friends** for smooth data. They were designed to remain monotonic in brightness under common deficiencies. This Is why they work in greyscale too.

The greyscale test is a good proxy for all of it. Print the chart in black and white. If it's still readable, it will live most viewing rules.

Transparency

alpha runs from 0 to 1 and can be set per artist or baked into a colour as a fourth channel.

Three places it earns its keep: showing density in an overplotted scatter, letting a shaded band sit under a line without hiding it. De-emphasising context series without changing their colour.

Two cautions. Alpha compounds — ten shared shapes at 0.1 are opaque where they all coincide. This Is just the density signal you want in a scatter and an unwanted colour shift elsewhere. And transparency is lost when a figure is flattened onto a background. A chart designed with alpha over white looks other over grey.

Named palettes

matplotlib ships the tab10 and tab20 qualitative sets. plt.get_cmap("tab10").colors gives the list for use in a cycler.

For cat work, palettes designed for colour-vision deficiency are worth preferring. The Okabe–Ito set is eight colours chosen to remain distinguishable under the common deficiencies. It's a plain list of hex codes you can paste into a cycler.

For sequential data, the perceptually uniform family is viridis, plasma, inferno, magma and cividis. cividis is designed namely to look the same to viewers with and without colour-vision deficiency.

Appending _r to any colormap name reverses it: viridis_r.

Discrete colour from a continuous map

Sampling a smooth colormap gives a graded set for sorted groups:

cmap = plt.get_cmap("viridis")
colors = [cmap(i / (n - 1)) for i in range(n)]

That is right when the groups have an order. Age bands, quartiles, years. Because the colour then carries the sorting.

It's wrong for unordered groups. Here A qualitative palette should be used instead. It's a common way to imply a sequence that doesn't exist.

BoundaryNorm does the same thing for a mapped plot, banding a smooth scale into discrete steps with a colorbar that shows the boundaries.

Colorbar placement

The default steals space from the axes it's attached to. This Shrinks that panel and misaligns it with its neighbours.

fig.colorbar(im, ax=axes.ravel().tolist()) spans a few panels with one bar. This Is right when they share a scale.

shrink=0.8 and aspect=30 adjust its proportions. pad sets the gap. location="bottom" moves it.

For precise control, fig.add_axes([left, bottom, width, height]) creates a dedicated axes for it in figure coords. This Is how you get a colorbar that lines up just with a grid.

Backgrounds

fig.patch is the figure background and ax.patch the axes background, and they're split.

ax.set_facecolor("#f7f7f7") gives the plotting area a light tint. This Some styles use to make a white grid readable.

For a dark theme, both need setting along with the text, tick and spine colours. Which is just what the dark_background style does. A reason to use a style rather than setting six things by hand.

Building a palette

A palette for a project needs fewer colours than people expect.

One colour, for charts with a single series. Most charts.

Two, for a compare or a before-and-after.

A greyscale plus one accent, for highlighting one series among many. This covers more cases than any multi-colour palette.

Five or six, for truly cat work, drawn from a set designed for the purpose.

Beyond about six, colour stops distinguishing well. The answer is small multiples rather than more hues.

Add a grey for context elements, a light grey for gridlines. A dark grey rather than black for text. That is a full house palette.

Testing a colour scheme

Three checks, none of which needs a tool.

Greyscale. Convert the figure to greyscale and see whether the series are still distinguishable. If they aren't, luminance isn't varying and the chart depends fully on hue.

Small size. View the figure at the size it will really be seen. Colours that split at full screen often don't at thumbnail size.

Print. A projector and a printer both compress the range. Colours that differ on a monitor often don't live either.

Passing all three often means the encoding is solid. Failing any of them is fixed the same way: vary luminance, and vary a second channel.

Colour with meaning

The strongest use of colour is when it codes a thing the reader already understands.

Semantic colours — red for loss, green for gain, a brand colour for one product — are read without a legend because the meaning arrives with the hue. They're also culturally exact. The red/green pairing is the worst possible choice for colour-blind readers. The rule has to be weighed against accessibility.

Sequential colour for an sorted var. Darker for more. Is read right with almost no instruction.

One accent against grey is the most solid of all. It says "this one" and nothing else.

The weakest use is colour as decoration: seven groups in seven hues because the palette had seven. That asks the reader to learn an any mapping and consult it again and again. It's often a sign the chart should be small multiples.

The test is whether dropping the colour destroys the chart's meaning or only its appearance. If the former, the colour is doing work and should be chosen carefully. If the latter, it can be simplified away.

Colour and Colormaps, in summary

Colours accept names, hex, C0-style cycle links, greys as strings, and RGB tuples.

The property cycle supplies distinct colours on its own. A cycler can vary linestyle alongside colour so the chart lives greyscale.

Colormaps come in three kinds, and using the wrong kind asserts a thing false. Sequential for magnitude, diverging around a real centre, qualitative for unordered groups.

jet fails measurably rather than aesthetically. Its brightness isn't monotonic. It shows edges the data doesn't have.

A colour-mapped plot needs a colorbar, with a label. Two such plots need a shared scale.

And roughly one man in twelve can't split red from green. This Is why a second channel is worth varying and why greyscale is a good proxy test for the whole question.

A closing note on Colour and Colormaps

Colour is the most over-used channel in charting and the most rewarding to use sparingly.

The strongest charts often have one colour, or one colour against grey. Colour that codes a var earns its place. Colour that sets apart seven groups no one needs to set apart doesn't.

The technical points matter too. Visual uniformity, the right kind of colormap, a centred diverging scale, a colorbar that says what the colours mean — and they're all in service of the same thing. The reader should be able to work out what a colour means without being told twice.

The greyscale test remains the quickest check on all of it.

Colour and Colormaps: the short version

Colour is the most over-used channel in charting and the most rewarding to use sparingly.

One accent against grey beats a seven-hue palette in almost each case. The greyscale test is the quickest check on whether the encoding lives contact with the real world.

Questions people ask about Colour and Colormaps

How do I specify a colour? Use a name such as 'steelblue', a hex string such as '#1f77b4', an RGB tuple of 0 to 1 floats, or a cycle reference like 'C0'.

Which colormap should I use? Sequential maps such as viridis for ordered values, diverging maps such as RdBu for data with a meaningful centre, and qualitative maps for categories.

Why is jet discouraged? Its brightness changes unevenly, so some value ranges look like features that are not in the data. It is also hard for colour-blind readers.

How do I make a chart readable for colour-blind readers? Avoid red-green pairs, use perceptually uniform maps like viridis, and add another cue such as marker shape or direct labels.

How do I add a colorbar? Call fig.colorbar(mappable, ax=ax) with the object returned by imshow, scatter or pcolormesh.

How do I centre a diverging colormap on zero? Pass vmin=-m, vmax=m with the same magnitude, or use TwoSlopeNorm(vcenter=0).

Reading the code back

Colour choices are made once for a project and applied through a cycler, not chosen per chart. A palette of five or six for groups, a sequential map for magnitudes, a diverging one for data with a real centre. A grey for context is a full set. Anything beyond that is often a chart that should have been small multiples.

Check yourself

0 of 4

Answer without scrolling back up.

  1. What does the string `'0.55'` mean as a colour?

  2. When is a diverging colormap the wrong choice?

  3. What is measurably wrong with `jet`?

  4. What does `fig.colorbar` need as its first argument?

Cheat sheet

Colour and Colormaps

Pick colours by name, hex or RGB in matplotlib. Choose a colormap that is perceptually uniform and still readable in greyscale.

MATPLOTLIB · vizlearn.in/matplotlib/colours_and_colormaps.html

About the author

Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.