How to Create a Contour Plot in Matplotlib
Contour plots are one of the most effective ways to visualize three-dimensional data on a two-dimensional surface. They work by drawing lines (or filled regions) that connect points sharing the same...
Key Insights
- Contour plots transform 3D data into readable 2D visualizations by connecting points of equal value, making them essential for analyzing mathematical functions, scientific data, and geographic information
- Matplotlib provides both
contour()for line-based plots andcontourf()for filled regions, with extensive customization options for levels, colormaps, and labeling that suit different analytical needs - The quality of your contour plot depends heavily on choosing appropriate contour levels and colormaps—too few levels miss important features while too many create visual clutter
Introduction to Contour Plots
Contour plots are one of the most effective ways to visualize three-dimensional data on a two-dimensional surface. They work by drawing lines (or filled regions) that connect points sharing the same value, similar to elevation lines on a topographic map. When you see a weather map showing temperature zones or pressure systems, you’re looking at contour plots in action.
These visualizations excel in several scenarios: analyzing mathematical functions with two input variables, displaying scientific measurements across a surface, identifying peaks and valleys in data, and revealing patterns that would be difficult to spot in raw numerical tables. Matplotlib’s contour plotting capabilities are mature, flexible, and integrate seamlessly with NumPy arrays, making it the go-to choice for Python developers working with spatial or functional data.
Basic Contour Plot Setup
Before creating contour plots, you need to structure your data correctly. Contour plots require three components: X coordinates, Y coordinates, and Z values representing the height or intensity at each (X, Y) position. The most common approach uses NumPy’s meshgrid() function to create coordinate matrices.
import numpy as np
import matplotlib.pyplot as plt
# Create coordinate arrays
x = np.linspace(-3, 3, 100)
y = np.linspace(-3, 3, 100)
# Generate a meshgrid
X, Y = np.meshgrid(x, y)
# Calculate Z values using a mathematical function
Z = np.sin(X) + np.cos(Y)
The meshgrid() function takes one-dimensional arrays and produces two-dimensional grids where every point’s coordinates are explicitly defined. This grid structure is exactly what Matplotlib’s contour functions expect. The Z values can come from any calculation—mathematical functions, interpolated sensor data, or simulation results.
Creating Your First Contour Plot
The contour() function is your entry point for contour visualization. At its simplest, it takes three arguments: the X coordinates, Y coordinates, and Z values.
# Create a basic contour plot
plt.figure(figsize=(10, 6))
contours = plt.contour(X, Y, Z)
# Add labels to contour lines
plt.clabel(contours, inline=True, fontsize=8)
# Add title and axis labels
plt.title('Basic Contour Plot: sin(x) + cos(y)', fontsize=14, fontweight='bold')
plt.xlabel('X axis')
plt.ylabel('Y axis')
# Display the plot
plt.colorbar(contours, label='Z value')
plt.show()
This code produces a clean contour plot with automatically determined contour levels. Matplotlib analyzes your Z data and selects appropriate intervals. The clabel() function adds numerical labels directly on the contour lines, making it easy to read specific values. The colorbar provides a reference scale, crucial for interpreting the visualization.
Filled Contour Plots and Customization
While line contours are useful, filled contour plots using contourf() often communicate information more effectively by using color to represent value ranges. This approach is particularly powerful when you need to emphasize regions rather than boundaries.
# Create a filled contour plot with customization
plt.figure(figsize=(12, 7))
# Define specific contour levels for better control
levels = np.linspace(Z.min(), Z.max(), 20)
# Create filled contours with a custom colormap
filled_contours = plt.contourf(X, Y, Z, levels=levels, cmap='viridis')
# Add a colorbar with custom label
cbar = plt.colorbar(filled_contours, label='Function Value')
cbar.ax.tick_params(labelsize=10)
# Customize the plot appearance
plt.title('Filled Contour Plot with Custom Levels', fontsize=14, fontweight='bold')
plt.xlabel('X axis', fontsize=12)
plt.ylabel('Y axis', fontsize=12)
plt.grid(True, alpha=0.3, linestyle='--')
plt.tight_layout()
plt.show()
The levels parameter gives you precise control over how many contour bands appear and where they’re positioned. Using np.linspace() creates evenly spaced levels, but you can also specify irregular intervals to emphasize particular value ranges. The cmap parameter accepts any Matplotlib colormap—‘viridis’, ‘plasma’, ‘coolwarm’, and ‘RdYlBu’ are popular choices for different types of data.
Adding Labels and Annotations
Proper labeling transforms a contour plot from a pretty picture into a useful analytical tool. Beyond basic clabel() usage, you can customize label appearance and positioning.
# Create contour plot with advanced labeling
plt.figure(figsize=(12, 7))
# Create line contours
line_contours = plt.contour(X, Y, Z, levels=10, colors='black', linewidths=1.5)
# Add custom formatted labels
labels = plt.clabel(line_contours, inline=True, fontsize=10, fmt='%1.2f')
# Customize label appearance
for label in labels:
label.set_rotation(0)
label.set_bbox(dict(boxstyle='round,pad=0.3', facecolor='white',
edgecolor='black', alpha=0.8))
# Create filled contours underneath
filled = plt.contourf(X, Y, Z, levels=20, cmap='coolwarm', alpha=0.6)
plt.colorbar(filled, label='Z Value')
plt.title('Contour Plot with Custom Labels', fontsize=14, fontweight='bold')
plt.xlabel('X coordinate')
plt.ylabel('Y coordinate')
plt.show()
The fmt parameter in clabel() accepts Python format strings, letting you control decimal places and number presentation. Setting inline=True removes portions of contour lines where labels appear, preventing visual clutter. The bbox customization adds background boxes to labels, improving readability when labels overlap with contour lines or filled regions.
Advanced Techniques
Real-world applications often require combining multiple visualization techniques. Overlaying line contours on filled plots provides both the continuous color information and precise boundary lines.
# Simulate a practical dataset (e.g., temperature distribution)
x_temp = np.linspace(0, 10, 50)
y_temp = np.linspace(0, 10, 50)
X_temp, Y_temp = np.meshgrid(x_temp, y_temp)
# Create a more complex function simulating temperature variation
Z_temp = 20 + 5 * np.sin(X_temp/2) * np.cos(Y_temp/2) - 0.3 * (X_temp - 5)**2 - 0.2 * (Y_temp - 5)**2
# Create combined visualization
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(16, 6))
# Left plot: Filled contours only
cf1 = ax1.contourf(X_temp, Y_temp, Z_temp, levels=15, cmap='RdYlBu_r')
ax1.set_title('Temperature Distribution (Filled)', fontsize=12, fontweight='bold')
ax1.set_xlabel('X Position (m)')
ax1.set_ylabel('Y Position (m)')
plt.colorbar(cf1, ax=ax1, label='Temperature (°C)')
# Right plot: Combined filled and line contours
cf2 = ax2.contourf(X_temp, Y_temp, Z_temp, levels=15, cmap='RdYlBu_r', alpha=0.8)
c2 = ax2.contour(X_temp, Y_temp, Z_temp, levels=10, colors='black',
linewidths=1, alpha=0.6)
ax2.clabel(c2, inline=True, fontsize=8, fmt='%1.1f°C')
ax2.set_title('Temperature Distribution (Combined)', fontsize=12, fontweight='bold')
ax2.set_xlabel('X Position (m)')
ax2.set_ylabel('Y Position (m)')
plt.colorbar(cf2, ax=ax2, label='Temperature (°C)')
plt.tight_layout()
plt.show()
This example demonstrates a practical scenario where you might analyze temperature or pressure distributions. The combined approach on the right subplot provides both the intuitive color-based visualization and precise numerical contours, giving viewers multiple ways to interpret the data.
Best Practices and Common Pitfalls
Choosing Contour Levels: Start with 10-20 levels for most datasets. Too few levels (less than 5) create a blocky appearance that misses important transitions. Too many levels (over 30) create visual noise without adding meaningful information. For data with known critical values, specify levels explicitly to highlight those thresholds.
Colormap Selection: Choose colormaps appropriate for your data type. Sequential colormaps like ‘viridis’ or ‘plasma’ work well for data with a natural ordering. Diverging colormaps like ‘coolwarm’ or ‘RdBu’ are ideal when your data has a meaningful center point (like zero or an average). Avoid rainbow colormaps (‘jet’) as they create false perceptual boundaries and aren’t colorblind-friendly.
Performance Considerations: For large datasets (meshgrids larger than 500x500), contour plotting becomes computationally expensive. Consider downsampling your data or using tricontourf() for irregularly spaced data points, which can be more efficient than interpolating to a full grid.
Data Smoothness: If your contour plots show jagged, irregular lines when you expect smooth transitions, increase your meshgrid resolution. The visual quality depends directly on how many points you use to sample your function or data.
Aspect Ratio: Use plt.axis('equal') when your X and Y axes represent the same physical quantity (like spatial coordinates) to prevent distortion. For different quantities, let Matplotlib use automatic scaling.
Contour plots are powerful tools in your visualization arsenal. Master the basics with contour() and contourf(), then layer in customization as your needs grow. The key is matching your visualization choices to your data’s characteristics and your audience’s needs.