Skillversitas

Colors and Colormaps

Color in matplotlib has three jobs: distinguish series, encode a data value, and keep the figure legible. The API treats them separately - a series gets one color from the palette, a continuous value gets a position in a colormap, and a categorical value gets a slot in a categorical map. Getting these three mechanisms straight covers nearly every styling question a figure raises.

Specifying Colors

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.plot([0, 1, 2], [1, 3, 2], color="#2563eb")     # hex
ax.plot([0, 1, 2], [2, 2.5, 1], color="tab:red")   # palette name
ax.plot([0, 1, 2], [0.5, 1, 0.8], color=(0.1, 0.5, 0.3, 0.7))  # RGBA
plt.show()

Accepted forms: CSS hex, CSS names, the palette entries ("tab:blue"), matplotlib's extended names ("C0" through "C9" = the active cycle), and RGBA tuples. C0-style names stay in sync with whatever cycle is active - useful when the palette changes via a style sheet.

The Color Cycle

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
for i in range(4):
    ax.plot([0, 1, 2], [i, i + 0.5, i], label=f"s{i}")
ax.legend()
plt.show()

# inspect or override the active cycle
print(plt.rcParams["axes.prop_cycle"])
plt.rcParams["axes.prop_cycle"] = plt.cycler(
    color=["#2563eb", "#f59e0b", "#10b981", "#ef4444"])

Series without an explicit color draw from the cycle in order; the default (tab10) is designed for distinguishability and color-blind safety. Override the cycle globally for brand palettes, or set colors per call for deliberate emphasis - "hero series dark, context series gray" is the most common professional move.

Colormaps for Continuous Values

import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(9)
x = rng.normal(size=400)
y = rng.normal(size=400) + 0.4 * x
z = rng.gamma(2, 1, size=400)

fig, ax = plt.subplots()
sc = ax.scatter(x, y, c=z, cmap="viridis", s=25, alpha=0.8)
fig.colorbar(sc, ax=ax, label="value")
plt.show()

Choose the map by the data's structure: sequential (viridis, plasma, Blues) for magnitude, diverging (RdYlBu, coolwarm) for values around a meaningful center like zero, categorical (tab10, Set2) for discrete groups. Viridis is the default for good reason - perceptually uniform and color-blind safe. Avoid rainbow/jet for scientific data: its brightness wiggles create false contours.

Custom Colormaps

import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap

# a 4-step sequential brand palette
cmap = ListedColormap(["#dbeafe", "#93c5fd", "#3b82f6", "#1e3a8a"])

import numpy as np
data = np.random.default_rng(10).random((8, 12))
fig, ax = plt.subplots()
ax.imshow(data, cmap=cmap, vmin=0, vmax=1)
plt.show()

ListedColormap takes an ordered list of colors; BoundaryNorm or vmin/vmax control where the data lands on it. Three to six steps is the practical range for a discrete-step map.

Working Rules

  • Series count above ~8: collapse, facet, or filter - no palette survives that many lines.
  • Grayscale printing: verify the figure in black and white before shipping; the tab palette is designed to survive it.
  • Colorblind check: use the Coblis or similar simulator on the final figure; viridis + tab10 cover the common cases.
  • One color, one meaning: never let red mean "danger" in one panel and just "series 4" in another without a legend.

Summary

Give series explicit or cyclic colors from a safe palette, map continuous values with viridis (sequential) or RdYlBu (diverging) plus a colorbar, use categorical maps for groups, and build brand palettes with ListedColormap. Test the result in grayscale and a colorblind simulator - the figure should survive both.