How to mask line after surface in matplotlib?

I want to build data using Matplotlib using a color map on the surface of a sphere. In addition, I would like to add a 3D graph. The code I have is the following:

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 creates an image that looks like this: <w640 which is ALMOST what I want. However, the black line should be obscured by the surface graph when it is in the background and visible when it is in the foreground. In other words, the black line should not โ€œglanceโ€ over the sphere.

Can this be done in Matplotlib without using Mayavi?

+5
source share
1 answer

The problem is that matplotlib is not rays and is not designed to create 3D graphics. As such, it works with a layer system in 2D space, and objects can be larger in the layer in front or behind. This can be set using the zorder keyword argument for most zorder functions. However, matplotlib lacks an understanding of whether the object is in front of or behind another object in three-dimensional space. Therefore, you can either have a full line visible (in front of the sphere) or hidden (behind it).

The solution is to calculate the points that should be visible by yourself. I am talking about points here because the line will connect the visible points "through" a sphere that is undesirable. Therefore, I limit myself to building points, but if you have enough of them, they look like a string :-).

Calculating which points should be visible is not too difficult for an ideal sphere, and the idea is this:

  • Get the viewing angle of 3D graphics
  • From this we calculate the normal vector to the plane of view in the data coordinates in the direction of the presentation.
  • Compute the scalar product between this normal vector (called X in the code below) and the points of the line to use this scalar product as a condition for showing points or not. If the scalar product is less than 0 , then the corresponding point is on the other side of the viewing plane, as seen from the observer, and therefore should not be displayed.
  • Filter points by condition.

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

See the code below for 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() 

enter image description here

In the end, you need to play a little with markersize , alpha and the number of points to get the most attractive result from this.

+4
source

Source: https://habr.com/ru/post/1014733/


All Articles