使用 Matplotlib 绘制三维立体图形¶
Matplotlib早期版本无法绘制三维立体图形。在1.0版本之后,在二维图形基础上,增加了三维绘图功能,并不断完善。matplotlib有自带的mplot3d 工具包,该工具包在安装matplotlib时就存在。
from mpl_toolkits import mplot3d
一旦导入mplot3d子工具包,就可以在任意一个图形中,通过加入关键字 projection='3d' 建立一个三维坐标轴。
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.axes(projection='3d')
在这样一个三维图形框架下,现在我们可以绘制各种各样的三维图画类型。三维图形还可以交互,而不仅仅是静态图。当然,如果希望与三维图形互动,在IPython设置中,应该在初期设置时应用%matplotlib notebook 而不再是 %matplotlib inline 。
三维数据点与数据线¶
走基本的三维图是由(x,y,z)三维坐标点构成的线图或者散点图。与前面介绍的二维图类似,可以使用 ax.plot3D 或者 ax.scatter3D 函数来创建。三维图形绘制中的参数与二维图形绘制的设置基本相同,参考前面点和线绘制可以作为参考。下面我们绘制一个三角螺旋线,并在线条之上随机分布一些散点。
ax = plt.axes(projection='3d')
# 三维曲线的数据
zline = np.linspace(0, 15, 1000)
xline = np.sin(zline)
yline = np.cos(zline)
ax.plot3D(xline, yline, zline, 'gray')
# 三维散点的数据
zdata = 15 * np.random.random(100)
xdata = np.sin(zdata) + 0.1 * np.random.randn(100)
ydata = np.cos(zdata) + 0.1 * np.random.randn(100)
ax.scatter3D(xdata, ydata, zdata, c=zdata, cmap='Greens');
在默认情况下,散点会自动改变透明程度,以便给图像形成立体感。有时候在静态图形中观察三维效果会比较费劲,这时候使用交互式三维立体图可能更有帮助。
三维等高线图¶
与之前介绍的等高线(康托图)类似,mplot3d可以根据输入数据创建三维渲染图形。与二维等高线图 ax.contour 绘制类似,应用 ax.contour3D可以绘制三维图形。这时候需要输入二维网格数据,通过函数计算出每一个点在Z轴的对应值。下面时使用三维等高线图绘制的三维正弦曲面。
def f(x, y):
return np.sin(np.sqrt(x ** 2 + y ** 2))
x = np.linspace(-6, 6, 30)
y = np.linspace(-6, 6, 30)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.contour3D(X, Y, Z, 50, cmap='binary')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z');
很多时候默认的观察视角可能不是最优的,这是我们可以使用view_init方法来调整观察角度与方位角。在本例子中,我们把俯仰角调整为60度(x-y平面与观察视角的角度),把方位角调整为35度(将z轴逆时针旋转35度)。可以得到下面图形:
ax.view_init(60, 35)
fig
当然,如果使用Matplotlib交互式背景下,可以通过点击,拖拽等鼠标操作完成旋转和视角选择。
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.plot_wireframe(X, Y, Z, color='black')
ax.set_title('wireframe');
曲面图表达方式与线框图类似,但是需要增加一个配色方案来充填多边形,这样可以让用户感受到图形表面的拓扑结构。如下图:
ax = plt.axes(projection='3d')
ax.plot_surface(X, Y, Z, rstride=1, cstride=1,
cmap='viridis', edgecolor='none')
ax.set_title('surface');
虽然曲面图形需要二维数据,但数据不一定使用平面直角坐标。Matplotlib同样支持极坐标方式。在下面的例子中,我们创建一个极坐标的网格。当我们由此制作曲面图形时候,可以通过设定范围,获得切片效果:
r = np.linspace(0, 6, 20)
theta = np.linspace(-0.9 * np.pi, 0.8 * np.pi, 40)
r, theta = np.meshgrid(r, theta)
X = r * np.sin(theta)
Y = r * np.cos(theta)
Z = f(X, Y)
ax = plt.axes(projection='3d')
ax.plot_surface(X, Y, Z, rstride=1, cstride=1,
cmap='viridis', edgecolor='none');
表面三角刨分(Surface Triangulations)¶
在很多应用中,输入的均匀采样的网格数据有时候会比较严格,这时候我们可以使用表面三角刨分方式。如果没有笛卡尔坐标或者极坐标的均匀网格数据,只有一些随机数,那么该如何画图呢?
theta = 2 * np.pi * np.random.random(1000)
r = 6 * np.random.random(1000)
x = np.ravel(r * np.sin(theta))
y = np.ravel(r * np.cos(theta))
z = f(x, y)
我们先用散点图看看图形的基本样式。
ax = plt.axes(projection='3d')
ax.scatter(x, y, z, c=z, cmap='viridis', linewidth=0.5);
显然这个图形还是需要修补调整。我们可以使用 ax.plot_trisurf函数方法帮助我们完成。这就应用了三角刨分方法:首先找到一组所有点可以连接的三角形,然后用这个三角形创建曲面。(在这里x, y, z 都是一维数组)
ax = plt.axes(projection='3d')
ax.plot_trisurf(x, y, z,
cmap='viridis', edgecolor='none');
结果肯定没有均匀网格分布图绘制的完美,但这种方法非常灵活。可以创建很多有趣的三维图形。如下面的例子中,我们使用该方法,创建一个三维莫比乌斯环。
案例:莫比乌斯环的绘制¶
把一个纸条扭转180度后,把两头粘起来做成的纸袋圈,就是最常见的莫比乌斯环。在拓扑学角度上,莫比乌斯环很神奇,因为它总共只有一个面。我们使用matplotlib绘制该图形,关键需要找出绘图参数。由于它是一条二维带,因此需要两个内在维度(intrinsic dimensions),其中一个维度定义为$\theta$ ,其取值范围为0到$2\pi$,表示形成一个闭环;同时需要有另一个维度表示带宽,取值为从-1到1,表示扭转,参数记为 $w$。
theta = np.linspace(0, 2 * np.pi, 30)
w = np.linspace(-0.25, 0.25, 8)
w, theta = np.meshgrid(w, theta)
从上面的参数设置中,我们需要确定每个环上点的笛卡尔坐标(x, y, z)。
莫比乌斯环有两个旋转过程,一是以环的中心进行的360度旋转,另一个是莫比乌斯环上的点在自己坐标轴上的旋转,为180度,记为$\phi$。因此,存在另一个反映特征的公式成立: $$\Delta\phi = \Delta\theta/2$$
phi = 0.5 * theta
现在使用三角学的知识,将极坐标的点转化为直角坐标,这样,首先确定半径 $r$,表示从点到中心的位置,并应用它来计算出笛卡尔坐标表示的每个莫比乌斯环上的点。
# x-y 平面上的半径
r = 1 + w * np.cos(phi)
x = np.ravel(r * np.cos(theta))
y = np.ravel(r * np.sin(theta))
z = np.ravel(w * np.sin(phi))
最后,为了画出这些点,我们需要保证三角刨分方法可以正确运行。最后的方法是使用基本参数化方法来定义三角刨分。
# 首先一句基本参数方法定义三角刨分
from matplotlib.tri import Triangulation
tri = Triangulation(np.ravel(w), np.ravel(theta))
ax = plt.axes(projection='3d')
ax.plot_trisurf(x, y, z, triangles=tri.triangles,
cmap='viridis', linewidths=0.2);
ax.set_xlim(-1, 1); ax.set_ylim(-1, 1); ax.set_zlim(-1, 1);
本例子有不少内容具有较高的挑战性,但掌握了这些基本方法,可以帮助我们运用在更多场景中,并且可以创造更多丰富多彩的三维图形。