如何在matplotlib中将一条线隐藏在表面图后面?

15

我想使用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()

这段代码生成的图片看起来像这样:this 这几乎是我想要的。但是,当黑线在背景中时,应该被表面图遮挡,并在前景中可见。换句话说,黑线不应该“透过”球体显示出来。

在Matplotlib中是否可以实现此功能,并且不使用Mayavi?


在matplotlib中这并不是真正可能的。例如,请参见此问题或评论区中的讨论此处 - tmdavison
1
@tom 鉴于相关问题和我下面的解决方案,我不同意“它实际上是不可能的”的说法。我更倾向于说“它是可能的,但具体情况取决于工作量可能会很大”。 - ImportanceOfBeingErnest
1个回答

14
问题在于Matplotlib不是一个光线追踪器,也不是一个真正设计成具备3D绘图功能的库。因此,它在2D空间中使用一种层次系统,并且对象可以位于更前面或更靠后的层次中。这可以通过对大多数绘图函数使用zorder关键字参数来设置。然而,在Matplotlib中没有关于物体在3D空间中是在另一个物体前面还是后面的意识。因此,您要么可以完全看到直线(在球体的前面),要么可以隐藏它(在球体的后面)。 解决方案是自己计算应该可见的点。我说的是点,因为线将连接通过球体的可见点,这是不需要的。因此,我限制自己只绘制点-但如果你有足够多的点,它们看起来像一条线:-)。或者可以通过在不需要连接的点之间使用附加的nan坐标来隐藏线;我在这里限制自己只用点,以使解决方案不必过于复杂。
对于一个完美的球体,计算哪些点应该可见并不太难,其思路如下:
1.获取3D图的视角 2.从视角中计算出沿着视线方向上的数据坐标系中视平面的法向量。 3.计算此法向量(在代码中称为X)与线点之间的数量积,以将该数量积用作显示点或不显示点的条件。如果数量积小于0,则相应点在观察者所看到的视平面的另一侧,因此不应显示。 4.通过条件过滤点。
其中一个进一步可选的任务是根据用户旋转视图来调整所显示的点。这可以通过将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()

最后,您可能需要稍微调整 markersizealpha 和点数,以获得最具视觉吸引力的结果。

输入图像描述


非常聪明的想法,谢谢!然而,似乎必须切换到Mayavi才能在更一般的情况下(例如不是一个完美的球体)进行操作。我理解得对吗? - Ethunxxx
2
我认为在matplotlib中总会有解决方案,即使是更加复杂的情况也是如此。这可能需要更深入的数学知识。选择是前往这个方向还是使用另一个库,则是个人品味的问题。 - ImportanceOfBeingErnest

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接