Contour and Surface Plots¶
This document covers techniques for combining 2D contour plots with 3D surface visualizations to provide complementary views of the same data.
Setup¶
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
x = np.linspace(-3, 3, 100)
y = np.linspace(-3, 3, 100)
X, Y = np.meshgrid(x, y)
Z = np.exp(-(X**2 + Y**2))
Side-by-Side Visualization¶
1. Basic Comparison¶
fig = plt.figure(figsize=(14, 5))
# 2D Contour
ax1 = fig.add_subplot(1, 2, 1)
cf = ax1.contourf(X, Y, Z, levels=20, cmap='viridis')
ax1.contour(X, Y, Z, levels=10, colors='black', linewidths=0.5)
ax1.set_xlabel('x')
ax1.set_ylabel('y')
ax1.set_title('2D Contour Plot')
ax1.set_aspect('equal')
plt.colorbar(cf, ax=ax1)
# 3D Surface
ax2 = fig.add_subplot(1, 2, 2, projection='3d')
ax2.plot_surface(X, Y, Z, cmap='viridis', linewidth=0)
ax2.set_xlabel('x')
ax2.set_ylabel('y')
ax2.set_zlabel('z')
ax2.set_title('3D Surface Plot')
plt.tight_layout()
plt.show()
2. Multiple View Angles¶
fig = plt.figure(figsize=(15, 10))
# Contour plot
ax1 = fig.add_subplot(2, 2, 1)
cf = ax1.contourf(X, Y, Z, levels=15, cmap='viridis')
cs = ax1.contour(X, Y, Z, levels=8, colors='white', linewidths=0.5)
ax1.clabel(cs, inline=True, fontsize=8)
ax1.set_title('Contour (Top View)')
ax1.set_aspect('equal')
plt.colorbar(cf, ax=ax1)
# 3D views
views = [(30, -60), (60, -60), (30, 45)]
titles = ['Standard View', 'High Angle', 'Rotated View']
for idx, ((elev, azim), title) in enumerate(zip(views, titles)):
ax = fig.add_subplot(2, 2, idx + 2, projection='3d')
ax.plot_surface(X, Y, Z, cmap='viridis', linewidth=0)
ax.view_init(elev=elev, azim=azim)
ax.set_title(f'3D Surface: {title}')
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.tight_layout()
plt.show()
Contour Projection on 3D Plot¶
Project contours onto the base plane of a 3D surface plot.
1. Basic Projection¶
fig, ax = plt.subplots(figsize=(10, 8), subplot_kw={'projection': '3d'})
# Surface plot
ax.plot_surface(X, Y, Z, cmap='viridis', alpha=0.8)
# Contour projection on z=0 plane
ax.contour(X, Y, Z, zdir='z', offset=0, cmap='viridis', levels=10)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_title('Surface with Contour Projection')
plt.show()
2. Filled Contour Projection¶
fig, ax = plt.subplots(figsize=(10, 8), subplot_kw={'projection': '3d'})
# Surface plot
ax.plot_surface(X, Y, Z, cmap='viridis', alpha=0.7)
# Filled contour projection
ax.contourf(X, Y, Z, zdir='z', offset=-0.2, cmap='viridis', levels=15, alpha=0.8)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_zlim(-0.2, 1.1)
ax.set_title('Surface with Filled Contour Projection')
plt.show()
3. Multiple Projections¶
fig, ax = plt.subplots(figsize=(12, 10), subplot_kw={'projection': '3d'})
# Surface
surf = ax.plot_surface(X, Y, Z, cmap='viridis', alpha=0.6)
# XY plane projection (bottom)
ax.contourf(X, Y, Z, zdir='z', offset=-0.1, cmap='viridis', levels=10, alpha=0.7)
# XZ plane projection (back)
ax.contourf(X, Y, Z, zdir='y', offset=3.5, cmap='viridis', levels=10, alpha=0.5)
# YZ plane projection (side)
ax.contourf(X, Y, Z, zdir='x', offset=-3.5, cmap='viridis', levels=10, alpha=0.5)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
ax.set_xlim(-3.5, 3)
ax.set_ylim(-3, 3.5)
ax.set_zlim(-0.1, 1.1)
ax.set_title('Surface with Multiple Contour Projections')
plt.show()
Wireframe with Contours¶
1. Wireframe Surface¶
fig = plt.figure(figsize=(14, 5))
# Filled contour
ax1 = fig.add_subplot(1, 2, 1)
cf = ax1.contourf(X, Y, Z, levels=20, cmap='coolwarm')
ax1.set_title('Filled Contour')
ax1.set_aspect('equal')
plt.colorbar(cf, ax=ax1)
# Wireframe
ax2 = fig.add_subplot(1, 2, 2, projection='3d')
ax2.plot_wireframe(X, Y, Z, rstride=5, cstride=5, color='blue', linewidth=0.5)
ax2.set_title('Wireframe')
plt.tight_layout()
plt.show()
2. Wireframe with Contour Projection¶
fig, ax = plt.subplots(figsize=(10, 8), subplot_kw={'projection': '3d'})
# Wireframe
ax.plot_wireframe(X, Y, Z, rstride=5, cstride=5, color='navy', linewidth=0.5, alpha=0.7)
# Contour projection
ax.contour(X, Y, Z, zdir='z', offset=0, cmap='Blues', levels=10)
ax.set_zlim(0, 1.1)
ax.set_title('Wireframe with Contour Projection')
plt.show()
Mathematical Functions Gallery¶
1. Gaussian Function¶
Z_gauss = np.exp(-(X**2 + Y**2))
fig = plt.figure(figsize=(14, 5))
ax1 = fig.add_subplot(1, 2, 1)
cf = ax1.contourf(X, Y, Z_gauss, levels=20, cmap='viridis')
ax1.contour(X, Y, Z_gauss, levels=10, colors='white', linewidths=0.5)
ax1.set_title('Gaussian: $e^{-(x^2+y^2)}$')
ax1.set_aspect('equal')
plt.colorbar(cf, ax=ax1)
ax2 = fig.add_subplot(1, 2, 2, projection='3d')
ax2.plot_surface(X, Y, Z_gauss, cmap='viridis')
ax2.set_title('Gaussian Surface')
plt.tight_layout()
plt.show()
2. Saddle Function¶
Z_saddle = X**2 - Y**2
fig = plt.figure(figsize=(14, 5))
ax1 = fig.add_subplot(1, 2, 1)
cf = ax1.contourf(X, Y, Z_saddle, levels=20, cmap='coolwarm')
ax1.contour(X, Y, Z_saddle, levels=[0], colors='black', linewidths=2) # Zero contour
ax1.set_title('Saddle: $x^2 - y^2$')
ax1.set_aspect('equal')
plt.colorbar(cf, ax=ax1)
ax2 = fig.add_subplot(1, 2, 2, projection='3d')
ax2.plot_surface(X, Y, Z_saddle, cmap='coolwarm')
ax2.set_title('Saddle Surface')
plt.tight_layout()
plt.show()
3. Sinusoidal Function¶
Z_sin = np.sin(X) * np.cos(Y)
fig = plt.figure(figsize=(14, 5))
ax1 = fig.add_subplot(1, 2, 1)
cf = ax1.contourf(X, Y, Z_sin, levels=20, cmap='RdBu')
ax1.contour(X, Y, Z_sin, levels=[0], colors='black', linewidths=1)
ax1.set_title('$\\sin(x)\\cos(y)$')
ax1.set_aspect('equal')
plt.colorbar(cf, ax=ax1)
ax2 = fig.add_subplot(1, 2, 2, projection='3d')
ax2.plot_surface(X, Y, Z_sin, cmap='RdBu')
ax2.set_title('Sinusoidal Surface')
plt.tight_layout()
plt.show()
4. Function Gallery¶
functions = [
(np.exp(-(X**2 + Y**2)), 'Gaussian', 'viridis'),
(X**2 - Y**2, 'Saddle', 'coolwarm'),
(np.sin(X) * np.cos(Y), 'sin(x)cos(y)', 'RdBu'),
(np.sin(np.sqrt(X**2 + Y**2)), 'Ripple', 'plasma')
]
fig = plt.figure(figsize=(16, 12))
for idx, (Z_func, title, cmap) in enumerate(functions):
# Contour
ax1 = fig.add_subplot(4, 2, 2*idx + 1)
cf = ax1.contourf(X, Y, Z_func, levels=15, cmap=cmap)
ax1.contour(X, Y, Z_func, levels=8, colors='black', linewidths=0.3)
ax1.set_title(f'{title} (Contour)')
ax1.set_aspect('equal')
plt.colorbar(cf, ax=ax1)
# Surface
ax2 = fig.add_subplot(4, 2, 2*idx + 2, projection='3d')
ax2.plot_surface(X, Y, Z_func, cmap=cmap, linewidth=0)
ax2.set_title(f'{title} (Surface)')
plt.tight_layout()
plt.show()
Bivariate Normal Distribution¶
1. Standard Normal¶
from scipy import stats
x = np.linspace(-3, 3, 100)
y = np.linspace(-3, 3, 100)
X, Y = np.meshgrid(x, y)
pos = np.dstack((X, Y))
rv = stats.multivariate_normal([0, 0], [[1, 0], [0, 1]])
Z = rv.pdf(pos)
fig = plt.figure(figsize=(14, 5))
ax1 = fig.add_subplot(1, 2, 1)
cf = ax1.contourf(X, Y, Z, levels=20, cmap='Blues')
ax1.contour(X, Y, Z, levels=10, colors='navy', linewidths=0.5)
ax1.set_title('Standard Bivariate Normal')
ax1.set_aspect('equal')
plt.colorbar(cf, ax=ax1, label='Density')
ax2 = fig.add_subplot(1, 2, 2, projection='3d')
ax2.plot_surface(X, Y, Z, cmap='Blues')
ax2.set_title('PDF Surface')
ax2.set_zlabel('Density')
plt.tight_layout()
plt.show()
2. Correlation Comparison¶
rhos = [-0.7, 0, 0.7]
fig = plt.figure(figsize=(15, 8))
for idx, rho in enumerate(rhos):
rv = stats.multivariate_normal([0, 0], [[1, rho], [rho, 1]])
Z = rv.pdf(pos)
# Contour
ax1 = fig.add_subplot(2, 3, idx + 1)
cf = ax1.contourf(X, Y, Z, levels=15, cmap='Blues')
ax1.set_title(f'ρ = {rho}')
ax1.set_aspect('equal')
plt.colorbar(cf, ax=ax1)
# Surface
ax2 = fig.add_subplot(2, 3, idx + 4, projection='3d')
ax2.plot_surface(X, Y, Z, cmap='Blues')
ax2.set_title(f'Surface (ρ = {rho})')
ax2.view_init(30, -60)
plt.tight_layout()
plt.show()
Interactive-Style Dashboard¶
Z = np.exp(-(X**2 + Y**2) / 2) / (2 * np.pi)
fig = plt.figure(figsize=(16, 10))
# Main 3D surface (large)
ax_3d = fig.add_subplot(2, 2, 1, projection='3d')
surf = ax_3d.plot_surface(X, Y, Z, cmap='viridis', alpha=0.9)
ax_3d.contourf(X, Y, Z, zdir='z', offset=0, cmap='viridis', levels=10, alpha=0.5)
ax_3d.set_xlabel('x')
ax_3d.set_ylabel('y')
ax_3d.set_zlabel('f(x,y)')
ax_3d.set_title('3D Surface with Contour Projection', fontsize=12)
ax_3d.view_init(30, -60)
# Top-down contour view
ax_top = fig.add_subplot(2, 2, 2)
cf = ax_top.contourf(X, Y, Z, levels=20, cmap='viridis')
cs = ax_top.contour(X, Y, Z, levels=10, colors='white', linewidths=0.5)
ax_top.clabel(cs, inline=True, fontsize=7, fmt='%.3f')
ax_top.set_xlabel('x')
ax_top.set_ylabel('y')
ax_top.set_title('Contour Plot (Top View)', fontsize=12)
ax_top.set_aspect('equal')
plt.colorbar(cf, ax=ax_top)
# X cross-section
ax_xsec = fig.add_subplot(2, 2, 3)
mid_idx = len(y) // 2
ax_xsec.plot(x, Z[mid_idx, :], 'b-', linewidth=2)
ax_xsec.fill_between(x, Z[mid_idx, :], alpha=0.3)
ax_xsec.set_xlabel('x')
ax_xsec.set_ylabel('f(x, 0)')
ax_xsec.set_title('Cross-Section at y = 0', fontsize=12)
ax_xsec.grid(alpha=0.3)
# Y cross-section
ax_ysec = fig.add_subplot(2, 2, 4)
ax_ysec.plot(y, Z[:, mid_idx], 'r-', linewidth=2)
ax_ysec.fill_between(y, Z[:, mid_idx], alpha=0.3, color='red')
ax_ysec.set_xlabel('y')
ax_ysec.set_ylabel('f(0, y)')
ax_ysec.set_title('Cross-Section at x = 0', fontsize=12)
ax_ysec.grid(alpha=0.3)
plt.suptitle('Standard Bivariate Normal Distribution Analysis', fontsize=14, fontweight='bold')
plt.tight_layout()
plt.show()
Publication-Quality Figure¶
from scipy import stats
# Setup
x = np.linspace(-3, 3, 150)
y = np.linspace(-3, 3, 150)
X, Y = np.meshgrid(x, y)
pos = np.dstack((X, Y))
rho = 0.6
rv = stats.multivariate_normal([0, 0], [[1, rho], [rho, 1]])
Z = rv.pdf(pos)
fig = plt.figure(figsize=(14, 5))
# Contour plot
ax1 = fig.add_subplot(1, 2, 1)
cf = ax1.contourf(X, Y, Z, levels=15, cmap='Blues')
cs = ax1.contour(X, Y, Z, levels=8, colors='navy', linewidths=0.6, alpha=0.8)
ax1.clabel(cs, inline=True, fontsize=8, fmt='%.3f')
ax1.set_xlabel('$X$', fontsize=12)
ax1.set_ylabel('$Y$', fontsize=12)
ax1.set_title('Contour Plot', fontsize=13)
ax1.set_aspect('equal')
ax1.tick_params(labelsize=10)
cbar1 = plt.colorbar(cf, ax=ax1)
cbar1.set_label('Probability Density', fontsize=11)
# Surface plot
ax2 = fig.add_subplot(1, 2, 2, projection='3d')
surf = ax2.plot_surface(X, Y, Z, cmap='Blues', linewidth=0, antialiased=True)
ax2.contourf(X, Y, Z, zdir='z', offset=0, cmap='Blues', levels=10, alpha=0.5)
ax2.set_xlabel('$X$', fontsize=11, labelpad=10)
ax2.set_ylabel('$Y$', fontsize=11, labelpad=10)
ax2.set_zlabel('Density', fontsize=11, labelpad=10)
ax2.set_title('Surface Plot', fontsize=13)
ax2.view_init(25, -50)
ax2.tick_params(labelsize=9)
# White panes
ax2.w_xaxis.set_pane_color((0.98, 0.98, 0.98, 1.0))
ax2.w_yaxis.set_pane_color((0.98, 0.98, 0.98, 1.0))
ax2.w_zaxis.set_pane_color((0.98, 0.98, 0.98, 1.0))
plt.suptitle(f'Bivariate Normal Distribution ($\\rho = {rho}$)',
fontsize=14, fontweight='bold', y=1.02)
plt.tight_layout()
plt.show()
Summary¶
| Visualization | Best For |
|---|---|
| Contour only | 2D view, precise level reading |
| Surface only | Overall shape understanding |
| Contour + Surface side-by-side | Comprehensive analysis |
| Surface + Contour projection | 3D context with 2D precision |
| Wireframe + Contour | Structure visualization |
| Dashboard (multiple views) | Complete exploration |