问题
I am preparing 3d plots with matplotlib and I am having a really weird behaviour with multiple datasets. I have two datasets that describe basically two shells in 3d: one inner shell and one outer shell. To plot them in 3d I do:
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(outer_z[:n], outer_x[:n], outer_y[:n], c='black', marker='.', lw=0)
ax.scatter(inner_z[:n], inner_x[:n], inner_y[:n], c='red', marker='.', lw=0)
ax.set_xlabel("Z")
ax.set_ylabel("X")
ax.set_zlabel("Y")
ax.set_xlim([-5,5])
ax.set_ylim([5,-5])
ax.set_zlim([-5,5])
(the order of the axes are just for perspective purposes). When I save the figure, however, I don't get two shells:
I get one layer over the other, with the points that are clearly in the back appearing in front. You can see on the pictures that some points of the outer shell that should be behind the inner shell are plotted in front of the inner shell. This is really annoying, because it does not pursue the "plot in 3d" purpose. Does any one have an idea on why is this happening and how could this be solved? Many thanks!
回答1:
I know that this isn't a solution to your problem, but perhaps an explanation for why it's behaving the way it is.
This has to do with the fact that Matplotlib does not actually have a 3D engine. Mplot3D takes your points and projects them to what it would look like on a 2D plot (for each object), and then Matplotlib draws each object one at a time; Matplotlib is a 2D drawing framework and Mplot3D is kind of a little hack to get some 3D functionality working without needing to write an full-blown 3D engine for Matplotlib.
This means the order in which you draw your different plots (in this case your red and black dots) matters, and if you draw your black dots after your red dots, they will appear to be in front of the red dots, regardless of their position.
Let me illustrate this with another example.
theta = np.linspace(0, 2*np.pi, 100, endpoint=True)
helix_x = np.cos(3*theta)
helix_y = np.sin(3*theta)
helix_z = theta
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
line_x = np.zeros(100)
line_y = np.zeros(100)
ax.plot(line_x, line_y, theta, lw='3', color='r')
ax.plot(helix_x, helix_y, helix_z, lw='2', color='k')
ax.set_xlabel("Z")
ax.set_ylabel("X")
ax.set_zlabel("Y")
ax.set_xlim([-1.5,1.5])
ax.set_ylim([-1.5,1.5])
ax.set_zlim([0,2*np.pi])
This gives:
But from the top view you can see that the line is inside the helix:
However if you swap the order in which you plot these lines:
ax.plot(line_x, line_y, theta, lw='3', color='r')
ax.plot(helix_x, helix_y, helix_z, lw='2', color='k')
You then see the line drawn after the helix:
Ultimately this means that you will have to manually determine which points will be in front of the other points. Then you can use the zorder argument to determine which objects will be in front of the others. But you would have to do this for each perspective (angle, elevation). In this case you would probably have to break up the inside line into "infront_of_helix" and "behind_helix" parts and then draw them in front and behind the helix respectively.
I hope someone comes along with more elaboration on the matter though, as I'm interested in the topic myself. I know that mplot3d has some elementary methods for making sure the front points show first, I believe, when it's using the shading algorithms but I'm not exactly sure.
回答2:
thanks you so much for your explanation :) I thought it could be something like that indeed. But I forgot to say in my question that the same thing happened no matter the order of the ax.scatter commands, what is pretty weird. I found out before reading your answer that that does not happen with the ax.plot command. Therefore, I replaced:
ax.scatter(outer_z[:n], outer_x[:n], outer_y[:n], c='black', marker='.', lw=0)
ax.scatter(inner_z[:n], inner_x[:n], inner_y[:n], c='red', marker='.', lw=0)
by
ax.plot(outer_z[:n], outer_x[:n], outer_y[:n], '.', markersize=1, color='black')
ax.plot(inner_z[:n], inner_x[:n], inner_y[:n], '.', markersize=1, color='red')
And I got the following picture:
which works for me. I know, however, that if I change the point of view I will have the red shell appearing on top of the black one. One problem I found later was that the .plot function does not have vmin and vmax arguments (as the .scatter one), which makes it harder to define the color as a gradient starting in vmin and vmax...
来源:https://stackoverflow.com/questions/33151163/pyplot-3d-scatter-points-at-the-back-overlap-points-at-the-front