Skip to content

Colormap Selection

Choosing the right colormap is critical for effective data visualization. This guide covers colormap categories, selection criteria, and best practices.

Colormap Categories

Matplotlib provides several categories of colormaps:

Category Use Case Examples
Sequential Data with natural ordering (low→high) viridis, plasma, Blues
Diverging Data with meaningful center point coolwarm, RdBu, seismic
Cyclic Periodic data (angles, phases) twilight, hsv
Qualitative Categorical data Set1, tab10, Paired

Setup

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-3, 3, 100)
y = np.linspace(-3, 3, 100)
X, Y = np.meshgrid(x, y)

Sequential Colormaps

Best for data that progresses from low to high values.

Perceptually Uniform

These colormaps have uniform perceptual brightness changes, making them ideal for scientific visualization.

Z = np.exp(-(X**2 + Y**2))

fig, axes = plt.subplots(2, 3, figsize=(15, 9))

cmaps = ['viridis', 'plasma', 'inferno', 'magma', 'cividis', 'turbo']

for ax, cmap in zip(axes.flat, cmaps):
    im = ax.imshow(Z, cmap=cmap, origin='lower')
    ax.set_title(f"'{cmap}'")
    ax.axis('off')
    plt.colorbar(im, ax=ax, shrink=0.8)

plt.suptitle('Perceptually Uniform Sequential Colormaps', fontsize=14)
plt.tight_layout()
plt.show()

Single-Hue Sequential

fig, axes = plt.subplots(2, 3, figsize=(15, 9))

cmaps = ['Blues', 'Greens', 'Reds', 'Purples', 'Oranges', 'Greys']

for ax, cmap in zip(axes.flat, cmaps):
    im = ax.imshow(Z, cmap=cmap, origin='lower')
    ax.set_title(f"'{cmap}'")
    ax.axis('off')
    plt.colorbar(im, ax=ax, shrink=0.8)

plt.suptitle('Single-Hue Sequential Colormaps', fontsize=14)
plt.tight_layout()
plt.show()

Multi-Hue Sequential

fig, axes = plt.subplots(2, 3, figsize=(15, 9))

cmaps = ['YlOrRd', 'YlGnBu', 'BuPu', 'GnBu', 'PuBuGn', 'OrRd']

for ax, cmap in zip(axes.flat, cmaps):
    im = ax.imshow(Z, cmap=cmap, origin='lower')
    ax.set_title(f"'{cmap}'")
    ax.axis('off')
    plt.colorbar(im, ax=ax, shrink=0.8)

plt.suptitle('Multi-Hue Sequential Colormaps', fontsize=14)
plt.tight_layout()
plt.show()

When to Use Sequential

  • Density plots
  • Probability distributions
  • Temperature (single direction)
  • Elevation maps
  • Any monotonically increasing data
# Example: Probability density
from scipy import stats

rv = stats.multivariate_normal([0, 0], [[1, 0], [0, 1]])
Z_pdf = rv.pdf(np.dstack((X, Y)))

fig, ax = plt.subplots(figsize=(8, 6))
im = ax.imshow(Z_pdf, extent=[-3, 3, -3, 3], origin='lower', cmap='viridis')
ax.set_title('Bivariate Normal PDF')
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.colorbar(im, ax=ax, label='Density')
plt.show()

Diverging Colormaps

Best for data with a meaningful center point (often zero).

Standard Diverging

Z_div = X**2 - Y**2  # Saddle function with positive and negative values

fig, axes = plt.subplots(2, 3, figsize=(15, 9))

cmaps = ['coolwarm', 'RdBu', 'seismic', 'bwr', 'RdYlBu', 'PiYG']

for ax, cmap in zip(axes.flat, cmaps):
    im = ax.imshow(Z_div, cmap=cmap, origin='lower')
    ax.set_title(f"'{cmap}'")
    ax.axis('off')
    plt.colorbar(im, ax=ax, shrink=0.8)

plt.suptitle('Diverging Colormaps', fontsize=14)
plt.tight_layout()
plt.show()

Centered Normalization

For diverging data, center the colormap at zero:

from matplotlib.colors import TwoSlopeNorm

Z_asym = X**2 - Y**2 + 2  # Asymmetric around zero

fig, axes = plt.subplots(1, 2, figsize=(12, 5))

# Without centering
im1 = axes[0].imshow(Z_asym, cmap='RdBu', origin='lower')
axes[0].set_title('Default Normalization')
plt.colorbar(im1, ax=axes[0])

# With centering at zero
norm = TwoSlopeNorm(vmin=Z_asym.min(), vcenter=0, vmax=Z_asym.max())
im2 = axes[1].imshow(Z_asym, cmap='RdBu', norm=norm, origin='lower')
axes[1].set_title('Centered at Zero (TwoSlopeNorm)')
plt.colorbar(im2, ax=axes[1])

plt.tight_layout()
plt.show()

When to Use Diverging

  • Correlation matrices
  • Temperature anomalies
  • Profit/loss data
  • Deviations from mean
  • Any data with positive and negative values around a center
# Example: Correlation matrix
np.random.seed(42)
data = np.random.randn(5, 100)
corr = np.corrcoef(data)

fig, ax = plt.subplots(figsize=(8, 6))
im = ax.imshow(corr, cmap='RdBu', vmin=-1, vmax=1)
ax.set_title('Correlation Matrix')
ax.set_xticks(range(5))
ax.set_yticks(range(5))
plt.colorbar(im, ax=ax, label='Correlation')
plt.show()

Cyclic Colormaps

Best for periodic data like angles or phases.

Available Cyclic Colormaps

theta = np.arctan2(Y, X)  # Angle in radians

fig, axes = plt.subplots(1, 3, figsize=(15, 4))

cmaps = ['twilight', 'twilight_shifted', 'hsv']

for ax, cmap in zip(axes, cmaps):
    im = ax.imshow(theta, cmap=cmap, origin='lower')
    ax.set_title(f"'{cmap}'")
    ax.axis('off')
    plt.colorbar(im, ax=ax, shrink=0.8)

plt.suptitle('Cyclic Colormaps', fontsize=14)
plt.tight_layout()
plt.show()

When to Use Cyclic

  • Angular data (directions, phases)
  • Time of day (circular)
  • Hue values
  • Any data that wraps around
# Example: Phase angle
Z_complex = X + 1j * Y
phase = np.angle(Z_complex)

fig, ax = plt.subplots(figsize=(8, 6))
im = ax.imshow(phase, extent=[-3, 3, -3, 3], origin='lower', cmap='twilight')
ax.set_title('Complex Phase Angle')
ax.set_xlabel('Re(z)')
ax.set_ylabel('Im(z)')
plt.colorbar(im, ax=ax, label='Phase (radians)')
plt.show()

Qualitative Colormaps

Best for categorical data with no natural ordering.

Available Qualitative Colormaps

fig, axes = plt.subplots(2, 3, figsize=(15, 8))

cmaps = ['Set1', 'Set2', 'Set3', 'tab10', 'tab20', 'Paired']

# Create categorical-like data
np.random.seed(42)
Z_cat = np.random.randint(0, 8, (20, 20))

for ax, cmap in zip(axes.flat, cmaps):
    im = ax.imshow(Z_cat, cmap=cmap, origin='lower')
    ax.set_title(f"'{cmap}'")
    ax.axis('off')
    plt.colorbar(im, ax=ax, shrink=0.8)

plt.suptitle('Qualitative Colormaps', fontsize=14)
plt.tight_layout()
plt.show()

When to Use Qualitative

  • Segmentation maps
  • Cluster labels
  • Land use categories
  • Any discrete, unordered categories
# Example: Cluster visualization
from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans

X_data, _ = make_blobs(n_samples=300, centers=4, random_state=42)
kmeans = KMeans(n_clusters=4, random_state=42).fit(X_data)

fig, ax = plt.subplots(figsize=(8, 6))
scatter = ax.scatter(X_data[:, 0], X_data[:, 1], c=kmeans.labels_, cmap='Set1')
ax.set_title('K-Means Clustering')
plt.colorbar(scatter, ax=ax, label='Cluster')
plt.show()

Reversed Colormaps

Add _r suffix to reverse any colormap.

fig, axes = plt.subplots(2, 3, figsize=(15, 9))

Z = np.exp(-(X**2 + Y**2))

pairs = [
    ('viridis', 'viridis_r'),
    ('Blues', 'Blues_r'),
    ('coolwarm', 'coolwarm_r')
]

for i, (cmap1, cmap2) in enumerate(pairs):
    im1 = axes[0, i].imshow(Z, cmap=cmap1, origin='lower')
    axes[0, i].set_title(f"'{cmap1}'")
    axes[0, i].axis('off')
    plt.colorbar(im1, ax=axes[0, i], shrink=0.8)

    im2 = axes[1, i].imshow(Z, cmap=cmap2, origin='lower')
    axes[1, i].set_title(f"'{cmap2}'")
    axes[1, i].axis('off')
    plt.colorbar(im2, ax=axes[1, i], shrink=0.8)

plt.suptitle('Original vs Reversed Colormaps', fontsize=14)
plt.tight_layout()
plt.show()

Color Vision Deficiency

Consider colorblind-friendly options.

Colorblind-Safe Colormaps

fig, axes = plt.subplots(2, 3, figsize=(15, 9))

# Colorblind-friendly options
cmaps = ['viridis', 'plasma', 'cividis', 'inferno', 'magma', 'YlOrBr']

for ax, cmap in zip(axes.flat, cmaps):
    im = ax.imshow(Z, cmap=cmap, origin='lower')
    ax.set_title(f"'{cmap}' (colorblind-safe)")
    ax.axis('off')
    plt.colorbar(im, ax=ax, shrink=0.8)

plt.suptitle('Colorblind-Friendly Colormaps', fontsize=14)
plt.tight_layout()
plt.show()

Avoid These for Colorblind Users

  • jet (rainbow) - poor perceptual uniformity
  • hsv - hard to distinguish regions
  • rainbow - misleading intensity perception
fig, axes = plt.subplots(1, 3, figsize=(15, 4))

# Problematic colormaps
cmaps = ['jet', 'rainbow', 'hsv']

for ax, cmap in zip(axes, cmaps):
    im = ax.imshow(Z, cmap=cmap, origin='lower')
    ax.set_title(f"'{cmap}' (avoid for scientific use)")
    ax.axis('off')
    plt.colorbar(im, ax=ax, shrink=0.8)

plt.suptitle('Colormaps to Avoid', fontsize=14)
plt.tight_layout()
plt.show()

Custom Colormaps

Create custom colormaps for specific needs.

From Listed Colors

from matplotlib.colors import ListedColormap

colors = ['#2ecc71', '#3498db', '#9b59b6', '#e74c3c', '#f39c12']
custom_cmap = ListedColormap(colors)

fig, ax = plt.subplots(figsize=(8, 6))
im = ax.imshow(np.random.randint(0, 5, (20, 20)), cmap=custom_cmap)
ax.set_title('Custom ListedColormap')
plt.colorbar(im, ax=ax)
plt.show()

Linear Segmented

from matplotlib.colors import LinearSegmentedColormap

# White to blue gradient
colors = ['white', 'lightblue', 'blue', 'darkblue']
custom_cmap = LinearSegmentedColormap.from_list('custom_blues', colors, N=256)

fig, ax = plt.subplots(figsize=(8, 6))
im = ax.imshow(Z, cmap=custom_cmap, origin='lower')
ax.set_title('Custom LinearSegmentedColormap')
plt.colorbar(im, ax=ax)
plt.show()

Truncated Colormap

def truncate_colormap(cmap, minval=0.0, maxval=1.0, n=256):
    new_cmap = LinearSegmentedColormap.from_list(
        f'trunc({cmap.name},{minval:.2f},{maxval:.2f})',
        cmap(np.linspace(minval, maxval, n))
    )
    return new_cmap

# Use only middle portion of viridis
cmap_full = plt.cm.viridis
cmap_trunc = truncate_colormap(cmap_full, 0.2, 0.8)

fig, axes = plt.subplots(1, 2, figsize=(12, 5))

im1 = axes[0].imshow(Z, cmap=cmap_full, origin='lower')
axes[0].set_title('Full viridis')
plt.colorbar(im1, ax=axes[0])

im2 = axes[1].imshow(Z, cmap=cmap_trunc, origin='lower')
axes[1].set_title('Truncated viridis (0.2-0.8)')
plt.colorbar(im2, ax=axes[1])

plt.tight_layout()
plt.show()

Selection Decision Tree

Is your data...
│
├── Ordered/Sequential (low → high)?
│   ├── Need colorblind-safe? → viridis, cividis, plasma
│   ├── Single hue preferred? → Blues, Greens, Greys
│   └── Multi-hue preferred? → YlOrRd, YlGnBu
│
├── Diverging (center is meaningful)?
│   ├── Center at zero? → coolwarm, RdBu, seismic
│   └── Asymmetric data? → Use TwoSlopeNorm
│
├── Cyclic (wraps around)?
│   └── Use twilight or twilight_shifted
│
└── Categorical (no order)?
    ├── Few categories (<10)? → Set1, tab10
    └── Many categories? → tab20, Set3

Quick Reference Table

Data Type Recommended Avoid
Density/PDF viridis, plasma jet, rainbow
Elevation terrain, gist_earth hsv
Temperature coolwarm, RdBu jet
Correlation RdBu, coolwarm sequential
Binary Greys, binary diverging
Categories Set1, tab10 sequential
Phase/Angle twilight, hsv sequential
Heatmap viridis, YlOrRd jet

List All Available Colormaps

import matplotlib.pyplot as plt

cmaps = plt.colormaps()
print(f"Total colormaps: {len(cmaps)}")
print("\nFirst 20:", cmaps[:20])

Visual Catalog

import matplotlib.pyplot as plt
import numpy as np

gradient = np.linspace(0, 1, 256).reshape(1, -1)

categories = {
    'Perceptually Uniform': ['viridis', 'plasma', 'inferno', 'magma', 'cividis'],
    'Sequential': ['Blues', 'Greens', 'Reds', 'YlOrRd', 'PuBuGn'],
    'Diverging': ['coolwarm', 'RdBu', 'seismic', 'bwr', 'RdYlBu'],
    'Cyclic': ['twilight', 'twilight_shifted', 'hsv'],
}

for category, cmap_list in categories.items():
    fig, axes = plt.subplots(len(cmap_list), 1, figsize=(8, len(cmap_list) * 0.4))
    fig.suptitle(category, fontsize=12, fontweight='bold')

    for ax, cmap in zip(axes, cmap_list):
        ax.imshow(gradient, aspect='auto', cmap=cmap)
        ax.set_ylabel(cmap, rotation=0, ha='right', va='center', fontsize=9)
        ax.set_xticks([])
        ax.set_yticks([])

    plt.tight_layout()
    plt.show()