# 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 {class}`pybamm.QuickPlot`) until you ask for these. See [Why they are not the default yet](#why-they-are-not-the-default-yet). ## Using them ```python 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 {data}`pytes.plotting.THERMAL` and {data}`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](examples/plot_colours.ipynb) 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: **PyTES Thermal (dark)**, for dark backgrounds (style `"pytes.thermal_dark"`), with its contrast against the documentation's dark background: ### Why 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, `tab10` | 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:
lowhigh
**thaw**, diverging (`"pytes.thaw"`), for a quantity above and below a reference:
below the referenceabove the 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 {class}`~matplotlib.colors.CenteredNorm`, as above, which makes the range symmetric about the reference, so that a kelvin is the same step in colour on both sides. {class}`~matplotlib.colors.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 {class}`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](https://github.com/mmsg-warwick/PyTES/issues/43). ## Reproducing the colours The colourmap values are in `src/pytes/colourmaps/`, generated by [`docs/design/colours/make_colourmaps.py`](https://github.com/mmsg-warwick/PyTES/blob/main/docs/design/colours/make_colourmaps.py), which documents each step. The decisions are recorded in [DESIGN D15](design/DESIGN.md#d15-kpis-and-standard-plots-live-in-the-toolkit).