Plot colours#
PyTES has its own colours for plots: two colour cycles, for lines on light and on dark backgrounds, and two colourmaps, for fields such as a temperature over time and distance. All of them lead with the PyTES orange and stay readable for people with colour-vision deficiency (colour blindness).
They are available now but are not yet the default: a plot uses matplotlib’s colours
(or PyBaMM’s, in pybamm.QuickPlot) until you ask for these. See
Why they are not the default yet.
Using them#
import matplotlib.pyplot as plt
from matplotlib.colors import CenteredNorm
import pytes.plotting # registers the colourmaps with matplotlib
plt.style.use("pytes.thermal") # the colour cycle for every figure from now on
with plt.style.context(["dark_background", "pytes.thermal_dark"]):
... # a figure for a dark background
ax.pcolormesh(t, z, T, cmap="pytes.ember")
ax.pcolormesh(t, z, T, cmap="pytes.thaw", norm=CenteredNorm(vcenter=T_melt))
The colours are also pytes.plotting.THERMAL and
pytes.plotting.THERMAL_DARK, for example to pass to
pybamm.QuickPlot(solution, colors=list(pytes.plotting.THERMAL)). Each colourmap has a
reversed version, "pytes.ember_r" and "pytes.thaw_r". The
Plot colours notebook shows all four on a packed bed
charge. The styles need matplotlib 3.7 or later, which the plot extra installs.
Colour cycles#
PyTES Thermal, for light backgrounds (style "pytes.thermal"). Each colour is shown
with its contrast against white:
#E8590C1. ember, 3.6:1#1F4E9C2. deep blue, 8.0:1#14A08A3. teal, 3.3:1#F0A9304. amber, 2.0:1#A23B725. berry, 6.2:1#88C7F06. powder, 1.8:1#36312E7. charcoal, 12.8:1PyTES Thermal (dark), for dark backgrounds (style "pytes.thermal_dark"), with its
contrast against the documentation’s dark background:
#F767071. ember, 5.8:1#6785F92. blue, 5.3:1#04B68B3. teal, 6.8:1#FFC66C4. amber, 11.5:1#B06C995. mauve, 4.6:1#B2D5F86. powder, 11.7:1#D9D0C77. warm grey, 11.7:1Why these colours#
Hot first, then cold. The cycle starts with the PyTES orange and a deep blue, so a plot of two lines, such as charge against discharge or inlet against outlet, reads as hot against cold. Blue against orange is also the pair of colours that every kind of colour-vision deficiency keeps apart.
Readable with colour-vision deficiency. About 1 man in 12 and 1 woman in 200 has some form of colour-vision deficiency, mostly red-green. The colours were chosen by simulating the three kinds of dichromacy (protanopia, deuteranopia and tritanopia, at full severity, following Machado et al., 2009) and measuring how far apart each pair of colours stays, in CIEDE2000, the standard measure of perceived colour difference. A palette scores the distance of its closest pair, in the worst of the four visions (typical vision included). Above about 10, colours are easy to tell apart; below 5, lines merge.
Palette |
Closest pair, all 7 colours |
Closest pair, first 4 |
|---|---|---|
PyTES Thermal |
10.1 |
12.4 |
Okabe–Ito, with the PyTES orange first |
9.9 |
9.9 |
Paul Tol’s “vibrant”, orange first |
8.6 |
8.6 |
Open Color shades (the logo’s palette) |
7.7 |
14.6 |
matplotlib’s default, |
1.3 |
1.3 |
Okabe–Ito and Tol’s schemes are the best-known colour-blind-safe palettes; PyTES Thermal
is as robust while keeping the PyTES orange and a more modern look. In tab10, the second
and third colours, orange and green, look almost the same to a protanope.
Known limits. The closest pair is berry and deep blue, for protan vision: every lighter or pinker berry tried came closer to the teal for deutan vision instead. Powder blue has only 1.8:1 contrast on white, so it suits fills and reference lines better than thin data lines. With more than about six lines, add line styles or markers as well as colours.
The dark twin keeps the same hues in the same order, each made lighter for a dark background while keeping most of its saturation, so that it looks like the same palette rather than a pastel one. Charcoal becomes a light warm grey. Its closest pair is 13.3, and every colour has at least 4.6:1 contrast on the documentation’s dark background (5.5:1 on black).
Colourmaps#
ember, sequential ("pytes.ember"), for a quantity from low to high:
thaw, diverging ("pytes.thaw"), for a quantity above and below a reference:
Why perceptually uniform#
A colourmap turns numbers into colours, and the eye reads changes in colour as changes in
the numbers. In a perceptually uniform colourmap, equal steps in the data look like equal
steps in colour, and lightness changes steadily from one end to the other. Rainbow maps
such as jet do neither: their lightness rises and falls, which draws edges in the data
that are not there and hides real ones, and they lose their order for colour-blind readers
and in greyscale print.
Both PyTES colourmaps are built in CAM02-UCS, the colour space in which matplotlib’s viridis and inferno were designed. Lightness changes linearly, hue and saturation follow smooth curves through anchor colours, and the result is resampled so that neighbouring colours are the same perceptual distance apart everywhere along the map.
ember#
Ember runs from a dark brown close to the ink of the PyTES wordmark, through the PyTES orange, to cream. Its lightness rises monotonically, the perceptual step between neighbouring colours varies by 0.013 %, and the map passes through the PyTES orange itself. No published colourmap is both uniform and orange throughout:
Colourmap |
Lightness |
Step size varies by |
Closest to the PyTES orange |
|---|---|---|---|
ember |
rises steadily |
0.013 % |
0.2 |
inferno (matplotlib) |
rises steadily |
1.1 % |
3.5 |
solar (cmocean) |
rises steadily |
0.0 % |
13.6 |
lajolla (Crameri) |
rises steadily |
14 % |
9.6 |
YlOrBr (matplotlib) |
rises steadily |
22 % |
3.4 |
jet (matplotlib) |
rises and falls |
43 % |
5.3 |
The last column is the CAM02-UCS distance from the map to the PyTES orange. Inferno is as uniform as ember, but its dark half is black and purple.
thaw#
Thaw is diverging: dark navy, through the deep blue of the colour cycle, to a near-white
centre, then through the PyTES orange to a dark red-brown. It is meant for a field
centred on a reference value, such as the phase-change temperature, so that the pale band
marks the melting front, with colder fluid in blue and hotter in orange. Centre it with
matplotlib’s CenteredNorm, as above, which makes the range
symmetric about the reference, so that a kelvin is the same step in colour on both sides.
TwoSlopeNorm instead stretches each half over its own range:
with fluid from 28 K below the reference to 10 K above it, a kelvin would look 2.8 times
bigger above the reference than below, undoing the map’s equal steps. Use it only when
the two sides are not comparable, for example when they are in different units.
Its lightness falls steadily from the centre to both ends, and colours equally far from
the centre match in lightness to within 2.4 CAM02-UCS units, so neither side looks
stronger. The step size varies by 2.8 %, as the blue half is 5 % shorter: blue reaches the
edge of the colours a screen can show. Beyond the pale centre, where the two sides meet, colours equally far either side stay
at least 19.6 units apart for every kind of colour-vision deficiency, so the two sides are
never confused. Among published maps, cmocean’s balance is as uniform but runs from blue
to red, Crameri’s vik has the right hues but lightness that does not change steadily, and
matplotlib’s coolwarm, RdBu and PuOr vary in step size by 19 % to 23 %.
The colourmaps need no dark twin: a colourmap fills its axes, whatever the background.
Why they are not the default yet#
PyTES models are PyBaMM models, and most of their plots come from PyBaMM’s
pybamm.QuickPlot (and so Simulation.plot), which today uses its own colours
whatever matplotlib’s colour cycle is. Making the PyTES colours the default would mean
changing matplotlib’s global settings when PyTES is imported, which would also restyle
every other figure in the session. Instead, PyTES will make them the default for its own
models once PyBaMM lets a package set the default style of its models’ plots. Your own
matplotlib settings will still take precedence. Progress is tracked in
issue #43.
Reproducing the colours#
The colourmap values are in src/pytes/colourmaps/, generated by
docs/design/colours/make_colourmaps.py,
which documents each step. The decisions are recorded in
DESIGN D15.