I am trying to plot a number of components in 3d space (in python), but am having issues with the rendering. As an example, the code below plots a sphere with some points on its surface, a ring around its equator, and a variety of arrows going radially out from the origin. However, matplotlib is rendering the arrows on top of all the points/sphere and does not shade them correctly based on being inside or outside the sphere. Is there a better way to do this? I am open to using other libraries if needed, but have not had much success getting anything to look good.
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import FancyArrowPatch
from mpl_toolkits.mplot3d import proj3d
class Arrow3D(FancyArrowPatch):
def __init__(self, xs, ys, zs, *args, **kwargs):
super().__init__((0,0), (0,0), *args, **kwargs)
self._verts3d = xs, ys, zs
def do_3d_projection(self, renderer=None):
xs3d, ys3d, zs3d = self._verts3d
xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.M)
self.set_positions((xs[0],ys[0]),(xs[1],ys[1]))
return np.min(zs)
def arrow3d(ax, length=1, width=0.05, head=0.2, headwidth=2,
theta_x=0, theta_z=0, offset=(0,0,0), **kw):
w = width
h = head
hw = headwidth
theta_x = np.deg2rad(theta_x)
theta_z = np.deg2rad(theta_z)
a = [[0,0],[w,0],[w,(1-h)*length],[hw*w,(1-h)*length],[0,length]]
a = np.array(a)
r, theta = np.meshgrid(a[:,0], np.linspace(0,2*np.pi,30))
z = np.tile(a[:,1],r.shape[0]).reshape(r.shape)
x = r*np.sin(theta)
y = r*np.cos(theta)
rot_x = np.array([[1,0,0],[0,np.cos(theta_x),-np.sin(theta_x) ],
[0,np.sin(theta_x) ,np.cos(theta_x) ]])
rot_z = np.array([[np.cos(theta_z),-np.sin(theta_z),0 ],
[np.sin(theta_z) ,np.cos(theta_z),0 ],[0,0,1]])
b1 = np.dot(rot_x, np.c_[x.flatten(),y.flatten(),z.flatten()].T)
b2 = np.dot(rot_z, b1)
b2 = b2.T+np.array(offset)
x = b2[:,0].reshape(r.shape);
y = b2[:,1].reshape(r.shape);
z = b2[:,2].reshape(r.shape);
ax.plot_surface(x,y,z, **kw)
# Create a sphere
r = 1
pi = np.pi
cos = np.cos
sin = np.sin
phi, theta = np.mgrid[0.0:pi:30j, 0.0:2.0*pi:30j]
x = r*sin(phi)*cos(theta)
y = r*sin(phi)*sin(theta)
z = r*cos(phi)
#Set colours and render
fig = plt.figure(figsize=(8,8))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(x, y, z, rstride=1, cstride=1, color='c', alpha=0.3, linewidth=0)
theta = np.linspace(0, 2*np.pi, 100)
ax.plot(np.cos(theta), np.sin(theta), 0, 'k', linewidth=4)
ax.scatter(x,y,z,color="k",s=20)
eig_vec = np.array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
for v in eig_vec:
#ax.plot([mean_x,v[0]], [mean_y,v[1]], [mean_z,v[2]], color='red', alpha=0.8, lw=3)
#I will replace this line with:
a = Arrow3D([0, v[0]], [0, v[1]],
[0, v[2]], mutation_scale=20,
lw=3, arrowstyle="-|>", color="r")
ax.add_artist(a)
arrow3d(ax, length=2.5, width=0.02, head=0.1, headwidth=1.5, offset=[0,0,0],
theta_x=40, color="crimson")
ax.set_xlim([-1,1])
ax.set_ylim([-1,1])
ax.set_zlim([-1,1])
ax.set_aspect("auto")
plt.tight_layout()
plt.show()
