python - ¿Cómo oscurecer una línea detrás de un diagrama de superficie en matplotlib?
mayavi (1)
El problema es que matplotlib no es un trazador de rayos y no está realmente diseñado para ser una biblioteca de trazado capaz de 3D.
Como tal, funciona con un sistema de capas en el espacio 2D, y los objetos pueden estar en una capa más adelante o más atrás.
Esto se puede configurar con el argumento de palabra clave
zorder
para la mayoría de las funciones de trazado.
Sin embargo, no hay conciencia en matplotlib sobre si un objeto está delante o detrás de otro objeto en el espacio 3D.
Por lo tanto, puede tener la línea completa visible (delante de la esfera) u oculta (detrás de ella).
La solución sería calcular los puntos que deberían ser visibles por usted mismo.
Estoy hablando de puntos aquí porque una línea estaría conectando puntos visibles "a través" de la esfera, lo cual no es deseado.
Por lo tanto, me limito a trazar puntos, pero si tiene suficientes, se ven como una línea :-).
Alternativamente, las líneas se pueden ocultar usando una coordenada
nan
adicional entre puntos que no se van a conectar;
Me estoy restringiendo a los puntos aquí para no hacer que la solución sea más complicada de lo necesario.
El cálculo de qué puntos deberían ser visibles no es demasiado difícil para una esfera perfecta, y la idea es la siguiente:
- Obtenga el ángulo de visión de la trama 3D
- A partir de eso, calcule el vector normal al plano de visión en coordenadas de datos en la dirección de la vista.
-
Calcule el producto escalar entre este vector normal (llamado
X
en el código a continuación) y los puntos de línea para usar este producto escalar como condición para mostrar los puntos o no. Si el producto escalar es menor que0
entonces el punto respectivo está en el otro lado del plano de visión como se ve desde el observador y, por lo tanto, no debe mostrarse. - Filtra los puntos por la condición.
Otra tarea opcional es adaptar los puntos mostrados para el caso cuando el usuario gira la vista.
Esto se logra conectando el
motion_notify_event
a una función que actualiza los datos usando el procedimiento de arriba, basado en el ángulo de visión recién establecido.
Vea el código a continuación sobre cómo implementar esto.
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()
Al final, es posible que tenga que jugar un poco con el
markersize
del
markersize
, el
alpha
y el número de puntos para obtener el resultado visualmente más atractivo de esto.
Quiero trazar datos usando Matplotlib a través de un mapa de colores en la superficie de una esfera. Además, me gustaría agregar un diagrama de línea 3D. El código que tengo hasta ahora es este:
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()
Este código produce una imagen que se ve así: que es casi lo que quiero Sin embargo, la línea negra debe estar oscurecida por el gráfico de superficie cuando está en el fondo y visible cuando está en primer plano. En otras palabras, la línea negra no debe "brillar a través de" la esfera.
¿Se puede hacer esto en Matplotlib y sin el uso de Mayavi?