HOWTO · Matplotlib
如何在 Matplotlib 中创建曲面图
我们可以使用 plot_surface()方法在 Matplotlib 中创建表面图。
本页内容
要在 Matplotlib 中创建 3D 曲面图,请准备形状兼容的二维 X、Y 和 Z 数组,使用 projection="3d" 创建坐标轴,再将这些数组传给 Axes3D.plot_surface。下面的示例根据数学函数生成坐标网格,为坐标轴添加标签和颜色条,并保存图像。如果测量值的坐标和高度能够表示为矩形网格,同样可以使用这一流程。
配置环境
NumPy 用于数组和网格运算,Matplotlib 用于绘图。在终端中,针对你准备使用的 Python 环境运行 python -m pip install numpy matplotlib。如果你通过虚拟环境或笔记本管理软件包,请改为在相应环境中安装这两个软件包,确保运行脚本的解释器能够使用它们。
示例会导入 NumPy 和 matplotlib.pyplot。Matplotlib 自带 3D 绘图支持;使用 projection="3d" 即可创建 3D 坐标轴,无需单独导入 Axes3D。该坐标轴使用的方法是 Axes3D.plot_surface。
准备数据
曲面图会为二维坐标平面上的每个位置指定一个高度。对于等间距坐标,np.linspace() 可生成一维坐标值,np.meshgrid() 则将它们扩展为二维 X 和 Y 网格。你可以根据这些网格计算 Z,也可以提供与网格位置对应的测量高度。
三个数组的形状必须兼容:每个 Z[i, j] 都是坐标对 X[i, j]、Y[i, j] 处的高度。当前版本的 Matplotlib 也接受一维的 x、y 坐标向量,并会根据二维的 Z 将其展开;但高度数组本身仍必须与网格维度一致:如果 Z 是在与 X、Y 不同的网格上计算出来的,就会引发 ValueError,下方的故障排除一节会用实际运行结果来说明。应先创建坐标网格,再在相同的网格上计算或重塑高度数组。如果测量点是离散分布的,无法组成矩形网格,请先准备网格数据,或在使用 plot_surface 前对数据进行插值。
创建曲面图
这个完整示例使用平滑的径向函数 sin(sqrt(x² + y²))。它会创建网格、绘制曲面、标注三个维度、添加颜色刻度,并将结果保存到当前目录中的 surface-plot.png。请在已安装 NumPy 和 Matplotlib 的环境中运行。示例不会向控制台打印文本;它会保存图像,并在当前后端支持显示时打开 Matplotlib 图窗。示例中坐标轴、标题和颜色条上可见的标签均保持英文。
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
surface = ax.plot_surface(X, Y, Z, cmap="viridis")
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
ax.set_title("Surface Plot")
fig.colorbar(surface, ax=ax, label="Z value")
fig.savefig("surface-plot.png", dpi=150, bbox_inches="tight")
plt.show()
由于此脚本不会打印文本,因此没有控制台输出块。颜色条将曲面颜色与对应数值关联起来;网格决定几何形状,颜色映射则控制曲面的外观。在笔记本中,绘图环境通常会显示图像;在脚本中,plt.show() 会请求已配置的 Matplotlib 后端显示图像。
自定义曲面图
下面的示例将最常见的自定义方式应用到同一个曲面。它把颜色映射切换为 plasma、移除网格边线,并在添加颜色条、将图形保存为 customized-surface-plot.png 之前,将视角旋转到固定的仰角和方位角。
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
surface = ax.plot_surface(X, Y, Z, cmap="plasma", edgecolor="none")
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
ax.set_title("Customized Surface Plot")
ax.view_init(elev=30, azim=45)
fig.colorbar(surface, ax=ax, label="Z value")
fig.savefig("customized-surface-plot.png", dpi=150, bbox_inches="tight")
plt.show()
选择一种能清楚呈现 Z 变化的颜色映射。在此示例中,cmap="plasma" 取代了第一个示例的 viridis 渐变,颜色条可以帮助理解这种对应关系。edgecolor="none" 选项会移除绘制出的网格边线,这些边线在密集网格上可能干扰连续曲面的呈现;如果希望采样网格本身保持可见,请保留默认边线。
若要调整视角,请在保存或显示图像之前调用 ax.view_init(elev=30, azim=45)。elev 用于调整观察高度,azim 用于绕垂直轴旋转视角。调整这两个值,直到曲面形状和坐标轴清晰可见;合适的角度取决于数据以及你想要呈现的内容。与第一张图相比,第二张图旋转后的视角能看到更多中央高峰远侧的斜面。
对于密集网格,plot_surface 可以使用 rcount 和 ccount 等行列采样控制项。降低这些值会减少采样的行数和列数,可能加快大型曲面的渲染速度,但也会损失可见细节。应保留足够的采样点,以呈现数据中的重要变化。使用 fig.savefig("surface-plot.png", dpi=150, bbox_inches="tight") 保存图像以便分享;对于显示后会关闭图像的后端,应在调用 plt.show() 之前保存。若要保留多个版本,请使用不同的文件名或目录。
与线框图比较
线框图只绘制同一曲面的网格线,不填充表面,也不使用颜色映射。Axes3D.plot_wireframe 接受相同的 X、Y、Z 网格,因此不需要准备新数据就能在两种视图之间切换。当网格结构本身很重要、填充的曲面会遮住形状的远侧,或者需要在正式绘制曲面前快速检查陌生数据的结构时,线框图很有用。
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-5, 5, 50)
y = np.linspace(-5, 5, 50)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
ax.plot_wireframe(X, Y, Z, rstride=2, cstride=2, color="tab:blue")
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
ax.set_title("Wireframe Plot")
fig.savefig("wireframe-plot.png", dpi=150, bbox_inches="tight")
plt.show()
rstride 和 cstride 参数会跳过部分行和列,让线条保持易读;示例会绘制 50×50 网格中每隔一行和一列的线。与 plot_surface 不同,线框图没有表面颜色,因此没有需要颜色条说明的对象。对于最终呈现,填充的曲面通常能更好地传达高度差异,而线框图则更清楚地显示网格本身的采样方式。
修复形状不兼容的错误
plot_surface 最常见的失败,是 Z 数组的形状与坐标网格不匹配。下面的脚本在 50×50 的网格上计算 Z,却将它与 100×100 的 X、Y 网格一起传入:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
x_small = np.linspace(-5, 5, 50)
y_small = np.linspace(-5, 5, 50)
X_small, Y_small = np.meshgrid(x_small, y_small)
Z = np.sin(np.sqrt(X_small**2 + Y_small**2))
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
ax.plot_surface(X, Y, Z, cmap="viridis")
plt.show()
运行脚本时,会在 plot_surface 的调用处停止,并出现这个错误:
ValueError: shape mismatch: objects cannot be broadcast to a single shape. Mismatch is between arg 0 with shape (100, 100) and arg 2 with shape (50, 50).
修复方法是像第一个示例那样,从传给 plot_surface 的同一个网格计算 Z,让 Z.shape 等于 X.shape 和 Y.shape。如果不确定,请在绘图前打印三个形状并比较:Z 的每个元素都需要恰好一对坐标,不能多也不能少。
总结
关键步骤是创建坐标网格、使 Z 与网格形状匹配、添加 3D 坐标轴,然后调用 Axes3D.plot_surface。颜色映射和颜色条有助于说明高度值,视角和采样控制项则有助于清晰呈现曲面形状。当你需要检查网格本身时,plot_wireframe 会以线条绘制相同的数据。需要图像文件时请保存图像,并根据所绘制的数据调整网格分辨率或视角。
常见问题
-
曲面图可以使用什么数据?
使用由两个坐标定义的数值,并确保每个高度都对应矩形网格中的一个位置。用函数生成数据便于演示;测量值只要排列在兼容的坐标网格上也可以使用。离散点需要先转换为网格数据。 -
如何安装所需的库?
在运行脚本的环境中使用python -m pip install numpy matplotlib安装 NumPy 和 Matplotlib。 -
可以更改曲面颜色吗?
可以。设置plot_surface的cmap参数,再通过fig.colorbar(surface, ax=ax)添加颜色条,显示颜色与数值的对应关系。 -
如何调整视角?
使用ax.view_init(elev=..., azim=...)。仰角会改变相机高度,方位角会改变相机绕图形旋转的角度。 -
为什么
X、Y和Z必须具有兼容的形状?
Matplotlib 需要为网格中的每个位置提供一对坐标和一个高度。如果Z是在不同的网格上计算出来的,plot_surface就会引发关于无法一起广播的形状的ValueError。使用np.meshgrid()创建二维坐标网格,并根据相同的网格计算Z,使对应位置的数组元素彼此对齐。