如何在matplotlib中隐藏曲面图后面的线? [英] How to obscure a line behind a surface plot in matplotlib?

查看:38
本文介绍了如何在matplotlib中隐藏曲面图后面的线?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想通过球体表面上的颜色图使用 Matplotlib 绘制数据.另外,我想添加一个 3D 线图.我到目前为止的代码是这样的:

I want to plot data using Matplotlib via a colormap on the surface of a sphere. Additionally, I would like to add a 3D line plot. The code I have so far is this:

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 code produces an image that looks like this: which is ALMOST what I want. However, the black line should be obscured by the surface plot when it is in the background and visible when it is in the foreground. In other words, the black line should not "shine through" the sphere.

这可以在 Matplotlib 中完成而不使用 Mayavi 吗?

Can this be done in Matplotlib and without the use of Mayavi?

推荐答案

问题是 matplotlib 不是光线追踪器,它并不是真正设计为具有 3D 功能的绘图库.因此,它适用于 2D 空间中的层系统,并且对象可以位于更靠前或靠后的层中.这可以使用 zorder 关键字参数设置为大多数绘图函数.然而,在 matplotlib 中没有意识到一个对象在 3D 空间中是在另一个对象的前面还是后面.因此,您可以使整条线可见(在球体前面)或隐藏(在球体后面).

The problem is that matplotlib is no ray tracer and it's not really designed to be a 3D capable plotting library. As such it works with a system of layers in 2D space, and objects can be in a layer more in front or more to the back. This can be set with the zorder keyword argument to most plotting functions. However there is no awareness in matplotlib about whether an object is in front or behind another object in 3D space. Therefore you can either have the complete line visible (in front of the sphere) or hidden (behind it).

解决方案是计算自己应该可见的点.我在这里谈论点是因为一条线将连接穿过"球体的可见点,这是不需要的.因此,我将自己限制在绘制点上——但如果你有足够多的点,它们看起来就像一条线:-).或者,可以通过在不连接的点之间使用附加的 nan 坐标来隐藏线;我将自己限制在此处的要点上,以免使解决方案比实际需要的更复杂.

The solution would be to calculate the points that should be visible by yourself. I'm talking about points here because a line would be connecting visible points "through" the sphere, which is unwanted. I therefore restrict myself to plotting points - but if you have enough of them, they look like a line :-). Alternatively lines can be hidden by using an additional nan coordinate in between points that are not to be connected; I'm restricting myself to points here not to make the solution more complicated than it needs to be.

对于一个完美的球体来说,计算哪些点应该是可见的并不难,思路如下:

The calculation of which points should be visible is not too hard for a perfect sphere, and the idea is the following:

  1. 获取 3D 绘图的视角
  2. 由此,计算视野方向数据坐标中视野平面的法向量.
  3. 计算这个法向量(在下面的代码中称为X)和线点之间的标量积,以使用这个标量积作为是否显示点的条件.如果标量积小于 0,那么从观察者的角度来看,相应的点位于视图平面的另一侧,因此不应显示.
  4. 按条件过滤点.
  1. Obtain the viewing angle of the 3D plot
  2. From that, calculate the normal vector to the plane of vision in data coordinates in direction of the view.
  3. Calculate the scalar product between this normal vector (called X in the code below) and the line points in order to use this scalar product as a condition on whether to show the points or not. If the scalar product is smaller than 0 then the respective point is on the other side of the viewing plane as seen from the observer and should therefore not be shown.
  4. Filter the points by the condition.

另一个可选任务是在用户旋转视图时调整显示的点.这是通过将 motion_notify_event 连接到一个函数来实现的,该函数根据新设置的视角使用上述过程更新数据.

One further optional task is then to adapt the points shown for the case when the user rotates the view. This is accomplished by connecting the motion_notify_event to a function that updates the data using the procedure from above, based on the newly set viewing angle.

有关如何实现这一点,请参阅下面的代码.

See the code below on how to implement this.

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 和点数,以便从中获得最具视觉吸引力的结果.

At the end one may have to play a bit with the markersize, alpha and the number of points in order to get the most visually appealing result out of this.

这篇关于如何在matplotlib中隐藏曲面图后面的线?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆