Images and 3D: imshow, contour and mplot3d
Gridded data on a colour scale, contour lines you can read, and the honest limits of a 3-D surface plot.
Grids with imshow and pcolormesh
imshow treats an array as pixels and is the fastest way to view a matrix. pcolormesh accepts explicit coordinate arrays and handles irregular spacing, at the cost of speed. Both need origin and aspect set deliberately or your image will be upside down and stretched.
import numpy as np
import matplotlib.pyplot as plt
grid = np.random.default_rng(0).random((40, 60))
fig, ax = plt.subplots(figsize=(7, 4))
im = ax.imshow(grid, origin="lower", extent=[0, 10, 0, 5],
aspect="auto", cmap="magma", interpolation="nearest")
fig.colorbar(im, ax=ax, label="Intensity")
ax.set_xlabel("Time (s)")
ax.set_ylabel("Channel")
# irregular grid: pcolormesh takes the cell edges
ax.pcolormesh(x_edges, y_edges, grid, shading="auto", cmap="viridis")
ax.set_aspect("equal")| Function | Input | Note |
|---|---|---|
imshow | One array | Fast; set origin and extent |
pcolormesh | Coordinate arrays plus values | Irregular grids, slower |
contour | X, Y, Z | Lines at chosen levels |
contourf | X, Y, Z | Filled bands; combine with contour |
hist2d | Two 1-D samples | Density without making a grid first |
Contour lines
x = np.linspace(-3, 3, 200)
y = np.linspace(-3, 3, 200)
X, Y = np.meshgrid(x, y)
Z = np.exp(-(X ** 2 + Y ** 2)) + 0.4 * np.exp(-((X - 1.5) ** 2 + Y ** 2))
cs = ax.contourf(X, Y, Z, levels=20, cmap="viridis")
ax.contour(X, Y, Z, levels=8, colors="white", linewidths=0.5, alpha=0.5)
ax.clabel(cs, inline=True, fontsize=7, fmt="%.2f")
fig.colorbar(cs, ax=ax, label="Density")meshgridturns two 1-D axes into the 2-D coordinate arrays contour expects.- A modest number of levels reads better than many; label them rather than relying on the colorbar alone.
- Filled contours plus thin line contours give both magnitude and shape in one panel.
- Contour lines interpolate between samples, so a coarse grid invents smoothness that is not in the data.
A first mplot3d surface
fig = plt.figure(figsize=(7, 5))
ax3 = fig.add_subplot(projection="3d")
ax3.plot_surface(X, Y, Z, cmap="viridis", linewidth=0, antialiased=True)
ax3.contour(X, Y, Z, zdir="z", offset=Z.min(), cmap="viridis", alpha=0.6)
ax3.set_xlabel("x")
ax3.set_zlabel("density")
ax3.view_init(elev=30, azim=-60) # camera angles in degrees
# sometimes the 2-D view is simply better
fig, ax = plt.subplots()
ax.contourf(X, Y, Z, levels=20, cmap="viridis")⚠️
A surface hides the values behind it, so two readers can disagree about which peak is higher. Use 3-D for shape and intuition, and a 2-D contour or small multiples when the reader has to compare numbers.
FAQ
Why is my image upside down?
imshow puts row 0 at the top by default, matching screen coordinates. Pass origin="lower" when row 0 should be at the bottom, as with spatial or time axes.Why is a 3-D plot so slow?
Every artist is projected and depth-sorted on each draw. Reduce the grid resolution, drop
antialiased, and avoid animating 3-D surfaces; 2-D contour views are far cheaper.Related
Colormaps, colour mapping and accessibility Animations and interactive figures
Last refreshed 2026-09-18.