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

时间:2017-01-17 14:19:11

标签: python matplotlib plot mayavi

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

getUTCDate

此代码生成的图像如下所示:this几乎是我想要的。但是,当黑色线条在背景中时,黑色线条应该被表面图块遮挡,而当它位于前景时,黑色线条应该是可见的。换句话说,黑线不应该“透过”球体。

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

1 个答案:

答案 0 :(得分:7)

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

解决方法是计算自己应该看到的点。我在这里谈论点,因为一条线将连接可见点"通过"球体,这是不需要的。因此,我限制自己绘制积分 - 但如果你有足够的积分,它们看起来像一条线:-)。或者,可以通过在不连接的点之间使用额外的nan坐标来隐藏线;我限制自己在这里指出的不是使解决方案比它需要的更复杂。

对于一个完美的球体,计算哪些点应该是可见的并不是很难,这个想法如下:

  1. 获取3D绘图的视角
  2. 由此,计算视图方向数据坐标中视平面的法向量。
  3. 计算此法线向量(在下面的代码中称为X)与线点之间的标量积,以便使用此标量积作为是否显示点的条件。如果标量乘积小于0,那么从观察者看,相应的点位于观察平面的另一侧,因此不应显示。
  4. 按条件过滤点数。
  5. 然后,另一个可选任务是在用户旋转视图时调整针对该情况示出的点。这是通过将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()
    

    enter image description here

    最后,可能需要使用markersizealpha和点数来玩一点,以便从中获得最具视觉吸引力的结果。