如何在 matplotlib 中隐藏曲面图后面的一条线?
How to obscure a line behind a surface plot in matplotlib?
我想使用 Matplotlib 通过球体表面的颜色图绘制数据。此外,我想添加一个 3D 线图。到目前为止我的代码是这样的:
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
NPoints_Phi = 30
NPoints_Theta = 30
radius = 1
pi = np.pi
cos = np.cos
sin = np.sin
phi_array = ((np.linspace(0, 1, NPoints_Phi))**1) * 2*pi
theta_array = (np.linspace(0, 1, NPoints_Theta) **1) * pi
phi, theta = np.meshgrid(phi_array, theta_array)
x_coord = radius*sin(theta)*cos(phi)
y_coord = radius*sin(theta)*sin(phi)
z_coord = radius*cos(theta)
#Make colormap the fourth dimension
color_dimension = x_coord
minn, maxx = color_dimension.min(), color_dimension.max()
norm = matplotlib.colors.Normalize(minn, maxx)
m = plt.cm.ScalarMappable(norm=norm, cmap='jet')
m.set_array([])
fcolors = m.to_rgba(color_dimension)
theta2 = np.linspace(-np.pi, 0, 1000)
phi2 = np.linspace( 0 , 5 * 2*np.pi , 1000)
x_coord_2 = radius * np.sin(theta2) * np.cos(phi2)
y_coord_2 = radius * np.sin(theta2) * np.sin(phi2)
z_coord_2 = radius * np.cos(theta2)
# plot
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot(x_coord_2, y_coord_2, z_coord_2,'k|-', linewidth=1 )
ax.plot_surface(x_coord,y_coord,z_coord, rstride=1, cstride=1, facecolors=fcolors, vmin=minn, vmax=maxx, shade=False)
fig.show()
此代码生成的图像如下所示: 这几乎是我想要的。但是,黑线在背景中时应被曲面图遮挡,而在前景中时应可见。换句话说,黑线不应该 "shine through" 球体。
这可以在 Matplotlib 中完成而不使用 Mayavi 吗?
问题是 matplotlib 不是光线追踪器,它并不是真正设计为具有 3D 功能的绘图库。因此,它与 2D space 中的层系统一起工作,并且对象可以在更前面或更后面的层中。这可以用大多数绘图函数的 zorder
关键字参数来设置。然而,matplotlib 并不知道一个对象在 3D 中是在另一个对象的前面还是后面 space。因此,您可以使完整的线可见(在球体前面)或隐藏(在球体后面)。
解决方案是自己计算应该可见的点。我在这里说的是点,因为一条线将连接可见点"through"球体,这是不需要的。因此,我将自己限制在绘制点上——但如果你有足够多的点,它们看起来就像一条线 :-)。或者,可以通过在不连接的点之间使用额外的 nan
坐标来隐藏线;我将自己限制在此处,以免使解决方案变得比需要的更复杂。
对于完美球体来说,计算哪些点应该可见并不难,思路如下:
- 获取3D图的视角
- 据此,在视图方向的数据坐标中计算视线平面的法向量。
- 计算此法向量(在下面的代码中称为
X
)与线点之间的标量积,以便使用此标量积作为是否显示点的条件。如果标量积小于 0
,则从观察者的角度来看,相应的点位于观察平面的另一侧,因此不应显示。
- 按条件筛选点。
然后,另一个可选任务是针对用户旋转视图的情况调整显示的点。这是通过将 motion_notify_event
连接到使用上述过程根据新设置的视角更新数据的函数来实现的。
请参阅下面的代码了解如何实现它。
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
NPoints_Phi = 30
NPoints_Theta = 30
phi_array = ((np.linspace(0, 1, NPoints_Phi))**1) * 2*np.pi
theta_array = (np.linspace(0, 1, NPoints_Theta) **1) * np.pi
radius=1
phi, theta = np.meshgrid(phi_array, theta_array)
x_coord = radius*np.sin(theta)*np.cos(phi)
y_coord = radius*np.sin(theta)*np.sin(phi)
z_coord = radius*np.cos(theta)
#Make colormap the fourth dimension
color_dimension = x_coord
minn, maxx = color_dimension.min(), color_dimension.max()
norm = matplotlib.colors.Normalize(minn, maxx)
m = plt.cm.ScalarMappable(norm=norm, cmap='jet')
m.set_array([])
fcolors = m.to_rgba(color_dimension)
theta2 = np.linspace(-np.pi, 0, 1000)
phi2 = np.linspace( 0, 5 * 2*np.pi , 1000)
x_coord_2 = radius * np.sin(theta2) * np.cos(phi2)
y_coord_2 = radius * np.sin(theta2) * np.sin(phi2)
z_coord_2 = radius * np.cos(theta2)
# plot
fig = plt.figure()
ax = fig.gca(projection='3d')
# plot empty plot, with points (without a line)
points, = ax.plot([],[],[],'k.', markersize=5, alpha=0.9)
#set initial viewing angles
azimuth, elev = 75, 21
ax.view_init(elev, azimuth )
def plot_visible(azimuth, elev):
#transform viewing angle to normal vector in data coordinates
a = azimuth*np.pi/180. -np.pi
e = elev*np.pi/180. - np.pi/2.
X = [ np.sin(e) * np.cos(a),np.sin(e) * np.sin(a),np.cos(e)]
# concatenate coordinates
Z = np.c_[x_coord_2, y_coord_2, z_coord_2]
# calculate dot product
# the points where this is positive are to be shown
cond = (np.dot(Z,X) >= 0)
# filter points by the above condition
x_c = x_coord_2[cond]
y_c = y_coord_2[cond]
z_c = z_coord_2[cond]
# set the new data points
points.set_data(x_c, y_c)
points.set_3d_properties(z_c, zdir="z")
fig.canvas.draw_idle()
plot_visible(azimuth, elev)
ax.plot_surface(x_coord,y_coord,z_coord, rstride=1, cstride=1,
facecolors=fcolors, vmin=minn, vmax=maxx, shade=False)
# in order to always show the correct points on the sphere,
# the points to be shown must be recalculated one the viewing angle changes
# when the user rotates the plot
def rotate(event):
if event.inaxes == ax:
plot_visible(ax.azim, ax.elev)
c1 = fig.canvas.mpl_connect('motion_notify_event', rotate)
plt.show()
最后可能需要稍微调整一下 markersize
、alpha
和点数,以便从中获得最具视觉吸引力的结果。
我想使用 Matplotlib 通过球体表面的颜色图绘制数据。此外,我想添加一个 3D 线图。到目前为止我的代码是这样的:
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
NPoints_Phi = 30
NPoints_Theta = 30
radius = 1
pi = np.pi
cos = np.cos
sin = np.sin
phi_array = ((np.linspace(0, 1, NPoints_Phi))**1) * 2*pi
theta_array = (np.linspace(0, 1, NPoints_Theta) **1) * pi
phi, theta = np.meshgrid(phi_array, theta_array)
x_coord = radius*sin(theta)*cos(phi)
y_coord = radius*sin(theta)*sin(phi)
z_coord = radius*cos(theta)
#Make colormap the fourth dimension
color_dimension = x_coord
minn, maxx = color_dimension.min(), color_dimension.max()
norm = matplotlib.colors.Normalize(minn, maxx)
m = plt.cm.ScalarMappable(norm=norm, cmap='jet')
m.set_array([])
fcolors = m.to_rgba(color_dimension)
theta2 = np.linspace(-np.pi, 0, 1000)
phi2 = np.linspace( 0 , 5 * 2*np.pi , 1000)
x_coord_2 = radius * np.sin(theta2) * np.cos(phi2)
y_coord_2 = radius * np.sin(theta2) * np.sin(phi2)
z_coord_2 = radius * np.cos(theta2)
# plot
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot(x_coord_2, y_coord_2, z_coord_2,'k|-', linewidth=1 )
ax.plot_surface(x_coord,y_coord,z_coord, rstride=1, cstride=1, facecolors=fcolors, vmin=minn, vmax=maxx, shade=False)
fig.show()
此代码生成的图像如下所示:
这可以在 Matplotlib 中完成而不使用 Mayavi 吗?
问题是 matplotlib 不是光线追踪器,它并不是真正设计为具有 3D 功能的绘图库。因此,它与 2D space 中的层系统一起工作,并且对象可以在更前面或更后面的层中。这可以用大多数绘图函数的 zorder
关键字参数来设置。然而,matplotlib 并不知道一个对象在 3D 中是在另一个对象的前面还是后面 space。因此,您可以使完整的线可见(在球体前面)或隐藏(在球体后面)。
解决方案是自己计算应该可见的点。我在这里说的是点,因为一条线将连接可见点"through"球体,这是不需要的。因此,我将自己限制在绘制点上——但如果你有足够多的点,它们看起来就像一条线 :-)。或者,可以通过在不连接的点之间使用额外的 nan
坐标来隐藏线;我将自己限制在此处,以免使解决方案变得比需要的更复杂。
对于完美球体来说,计算哪些点应该可见并不难,思路如下:
- 获取3D图的视角
- 据此,在视图方向的数据坐标中计算视线平面的法向量。
- 计算此法向量(在下面的代码中称为
X
)与线点之间的标量积,以便使用此标量积作为是否显示点的条件。如果标量积小于0
,则从观察者的角度来看,相应的点位于观察平面的另一侧,因此不应显示。 - 按条件筛选点。
然后,另一个可选任务是针对用户旋转视图的情况调整显示的点。这是通过将 motion_notify_event
连接到使用上述过程根据新设置的视角更新数据的函数来实现的。
请参阅下面的代码了解如何实现它。
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
NPoints_Phi = 30
NPoints_Theta = 30
phi_array = ((np.linspace(0, 1, NPoints_Phi))**1) * 2*np.pi
theta_array = (np.linspace(0, 1, NPoints_Theta) **1) * np.pi
radius=1
phi, theta = np.meshgrid(phi_array, theta_array)
x_coord = radius*np.sin(theta)*np.cos(phi)
y_coord = radius*np.sin(theta)*np.sin(phi)
z_coord = radius*np.cos(theta)
#Make colormap the fourth dimension
color_dimension = x_coord
minn, maxx = color_dimension.min(), color_dimension.max()
norm = matplotlib.colors.Normalize(minn, maxx)
m = plt.cm.ScalarMappable(norm=norm, cmap='jet')
m.set_array([])
fcolors = m.to_rgba(color_dimension)
theta2 = np.linspace(-np.pi, 0, 1000)
phi2 = np.linspace( 0, 5 * 2*np.pi , 1000)
x_coord_2 = radius * np.sin(theta2) * np.cos(phi2)
y_coord_2 = radius * np.sin(theta2) * np.sin(phi2)
z_coord_2 = radius * np.cos(theta2)
# plot
fig = plt.figure()
ax = fig.gca(projection='3d')
# plot empty plot, with points (without a line)
points, = ax.plot([],[],[],'k.', markersize=5, alpha=0.9)
#set initial viewing angles
azimuth, elev = 75, 21
ax.view_init(elev, azimuth )
def plot_visible(azimuth, elev):
#transform viewing angle to normal vector in data coordinates
a = azimuth*np.pi/180. -np.pi
e = elev*np.pi/180. - np.pi/2.
X = [ np.sin(e) * np.cos(a),np.sin(e) * np.sin(a),np.cos(e)]
# concatenate coordinates
Z = np.c_[x_coord_2, y_coord_2, z_coord_2]
# calculate dot product
# the points where this is positive are to be shown
cond = (np.dot(Z,X) >= 0)
# filter points by the above condition
x_c = x_coord_2[cond]
y_c = y_coord_2[cond]
z_c = z_coord_2[cond]
# set the new data points
points.set_data(x_c, y_c)
points.set_3d_properties(z_c, zdir="z")
fig.canvas.draw_idle()
plot_visible(azimuth, elev)
ax.plot_surface(x_coord,y_coord,z_coord, rstride=1, cstride=1,
facecolors=fcolors, vmin=minn, vmax=maxx, shade=False)
# in order to always show the correct points on the sphere,
# the points to be shown must be recalculated one the viewing angle changes
# when the user rotates the plot
def rotate(event):
if event.inaxes == ax:
plot_visible(ax.azim, ax.elev)
c1 = fig.canvas.mpl_connect('motion_notify_event', rotate)
plt.show()
最后可能需要稍微调整一下 markersize
、alpha
和点数,以便从中获得最具视觉吸引力的结果。