[英]How to obscure a line behind a surface plot in matplotlib?
我想通過球體表面上的顏色圖使用 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()
此代碼生成的圖像如下所示: 這幾乎是我想要的。 但是,黑線在背景中時應被曲面圖遮擋,而在前景中時則可見。 換句話說,黑線不應“穿過”球體。
這可以在 Matplotlib 中完成而不使用 Mayavi 嗎?
問題是 matplotlib 不是光線追蹤器,它並不是真正設計為具有 3D 功能的繪圖庫。 因此,它適用於 2D 空間中的層系統,並且對象可以位於更靠前或靠后的層中。 這可以使用zorder
關鍵字參數設置為大多數繪圖函數。 然而,matplotlib 並不知道一個對象在 3D 空間中是在另一個對象的前面還是后面。 因此,您可以使整條線可見(在球體前面)或隱藏(在球體后面)。
解決方案是計算自己應該可見的點。 我在這里談論點是因為一條線將“通過”球體連接可見點,這是不需要的。 因此,我將自己限制在繪制點上——但如果你有足夠多的點,它們看起來就像一條線:-)。 或者,可以通過在不連接的點之間使用額外的nan
坐標來隱藏線; 我將自己限制在此處的要點上,以免使解決方案比需要的更復雜。
對於一個完美的球體來說,計算哪些點應該是可見的並不難,思路如下:
X
)與線點之間的標量積,以便將此標量積用作是否顯示點的條件。 如果標量積小於0
那么從觀察者的角度來看,相應的點位於觀察平面的另一側,因此不應顯示。 另一個可選任務是在用戶旋轉視圖時調整為案例顯示的點。 這是通過將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()
最后,可能需要稍微調整一下markersize
、 alpha
和點數,以便從中獲得最具視覺吸引力的結果。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.