2017-01-17 26 views
5

Tôi muốn vẽ dữ liệu bằng cách sử dụng Matplotlib thông qua một bản đồ màu trên bề mặt của một hình cầu. Ngoài ra, tôi muốn thêm một âm mưu đường 3D. Mã tôi có cho đến thời điểm này là:Làm thế nào để che khuất một đường phía sau một âm mưu bề mặt trong matplotlib?

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() 

Mã này tạo ra một hình ảnh giống như sau: this, đó là những gì tôi muốn. Tuy nhiên, đường màu đen sẽ bị che khuất bởi ô bề mặt khi nó nằm trong nền và hiển thị khi nó ở phía trước. Nói cách khác, đường màu đen không nên "tỏa sáng" hình cầu.

Điều này có thể được thực hiện trong Matplotlib và không sử dụng Mayavi không?

+0

Nó không thực sự có thể trong matplotlib. Xem [câu hỏi này] (http://stackoverflow.com/questions/14824893/how-to-draw-intersecting-planes/14825951#14825951) hoặc thảo luận trong các bình luận [ở đây] (http: // stackoverflow.Ví dụ: com/questions/16960126/python-matplotlib-3d-line-xuất-qua-bề mặt). – tom

+0

@tom Theo quan điểm của các câu hỏi được liên kết và cũng là giải pháp của tôi bên dưới, tôi không đồng ý với tuyên bố rằng "nó không thực sự khả thi". Tôi thà nói "nó là có thể, nhưng - tùy thuộc vào trường hợp thực tế - có thể là rất nhiều công việc". – ImportanceOfBeingErnest

Trả lời

4

Vấn đề là matplotlib không có dấu vết tia và nó không thực sự được thiết kế để trở thành một thư viện có khả năng vẽ 3D. Như vậy nó làm việc với một hệ thống các lớp trong không gian 2D, và các đối tượng có thể ở trong một lớp nhiều hơn ở phía trước hoặc nhiều hơn ở phía sau. Điều này có thể được đặt bằng đối số từ khóa zorder cho hầu hết các chức năng vẽ đồ thị. Tuy nhiên không có nhận thức trong matplotlib về việc liệu một vật thể ở phía trước hay phía sau một vật thể khác trong không gian 3D. Vì vậy, bạn có thể có dòng hoàn toàn có thể nhìn thấy (ở phía trước của hình cầu) hoặc ẩn (đằng sau nó).

Giải pháp sẽ là tính toán các điểm sẽ được hiển thị một mình. Tôi đang nói về các điểm ở đây vì một đường thẳng sẽ kết nối các điểm nhìn thấy được "thông qua" hình cầu, không mong muốn. Do đó tôi hạn chế bản thân để vẽ điểm - nhưng nếu bạn có đủ điểm, chúng trông giống như một dòng :-).

Việc tính toán trong đó điểm nên được hiển thị không phải là quá khó khăn cho một lĩnh vực hoàn hảo, và ý tưởng là như sau:

  1. Lấy góc nhìn của cốt truyện 3D
  2. Từ đó, tính toán vector bình thường với mặt phẳng của tầm nhìn trong các tọa độ dữ liệu theo hướng của khung nhìn.
  3. Tính sản phẩm vô hướng giữa vectơ thông thường này (được gọi là X trong mã bên dưới) và các điểm dòng để sử dụng sản phẩm vô hướng này làm điều kiện để hiển thị điểm hay không. Nếu sản phẩm vô hướng nhỏ hơn 0 thì điểm tương ứng nằm ở phía bên kia của mặt phẳng xem như được nhìn từ người quan sát và do đó sẽ không được hiển thị.
  4. Lọc điểm theo điều kiện.

Một nhiệm vụ tùy chọn khác sau đó là điều chỉnh các điểm được hiển thị cho trường hợp khi người dùng xoay chế độ xem. Điều này được thực hiện bằng cách kết nối motion_notify_event với chức năng cập nhật dữ liệu bằng cách sử dụng quy trình từ trên, dựa trên góc nhìn mới được thiết lập.

Xem mã bên dưới về cách triển khai tính năng này.

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

Vào cuối người ta có thể phải chơi một chút với markersize, alpha và số lượng các điểm để có được kết quả trực quan hấp dẫn nhất trong số này.

+0

Ý tưởng rất thông minh, cảm ơn! Tuy nhiên, có vẻ như người ta phải chuyển sang Mayavi để có thể làm điều này cho các trường hợp tổng quát hơn (ví dụ: không phải là một hình cầu hoàn hảo). Tôi có nhận được quyền này không? – Ethunxxx

+2

Tôi nghĩ rằng sẽ luôn luôn có một giải pháp trong matplotlib, ngay cả đối với các trường hợp phức tạp hơn nhiều. Điều này sau đó sẽ đòi hỏi một chút toán học liên quan hơn. Việc đi theo hướng này hay sử dụng thư viện khác có thể là một câu hỏi về sở thích cá nhân. – ImportanceOfBeingErnest

Các vấn đề liên quan