I need to render some 3D vectors starting in (0,0,0) ending all on a sphere. Currently I do this with python and matplotlib:
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import proj3d
from matplotlib.patches import FancyArrowPatch
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_aspect("equal")
# render the sphere mesh
u, v = np.mgrid[0:2*np.pi:20j, 00:np.pi:10j]
x=np.cos(u)*np.sin(v)
y=np.sin(u)*np.sin(v)
z=np.cos(v)
ax.plot_wireframe(x, y, z, color="r")
plt.axis('off')
class Arrow3D(FancyArrowPatch):
def __init__(self, xs, ys, zs, *args, **kwargs):
FancyArrowPatch.__init__(self, (0,0), (0,0), *args, **kwargs)
self._verts3d = xs, ys, zs
def draw(self, renderer):
xs3d, ys3d, zs3d = self._verts3d
xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, renderer.M)
self.set_positions((xs[0],ys[0]),(xs[1],ys[1]))
FancyArrowPatch.draw(self, renderer)
gradients = [[ 0.57735027, -0.57735027, 0.57735027], [ 0.32103634, -0.15367924, -0.93451506], [ 0.41923004, -0.40336058, 0.81335509], [ 0.49175236, -0.7972576 , -0.35008559], [-0.77544582, -0.60942662, 0.16517574], [ 0.49080035, -0.37864643, -0.78469223], [-0.9688558 , -0.14921457, -0.19761968], [-0.55381966, -0.41340241, -0.72276008], [-0.51651138, -0.82581121, 0.22638878], [ 0.51285124, -0.09350922, 0.85336959], [-0.02404559, -0.99431705, -0.10370783], [-0.45752126, -0.15967038, 0.87474549], [-0.11906644, -0.66461051, 0.73764223], [-0.69553846, -0.26904184, 0.66621518], [ 0.84464198, -0.38759068, 0.3692607 ], [ 0.01218729, -0.86413401, 0.5031141 ], [-0.70542359, -0.10816363, -0.70048422], [-0.64579558, -0.75719821, -0.09797404], [ 0.22307518, -0.66173422, 0.715783 ], [ 0.35672677, -0.81123132, 0.46330312], [-0.64859039, -0.59819001, 0.47063699], [-0.9860335 , -0.04110239, 0.16139577], [ 0.78665483, -0.298026 , -0.54069835], [-0.22515292, -0.39356178, 0.89129978], [-0.03115982, -0.20828982, -0.97757065], [ 0.1714755 , -0.88971114, -0.42309603], [-0.1948448 , -0.48424336, -0.85296184], [-0.46394941, -0.51801461, 0.71861798], [-0.45273268, -0.81954175, -0.35126126], [-0.82887799, -0.29984823, -0.47228414], [ 0.21121763, -0.09845807, 0.97246754], [-0.11702234, -0.10076906, 0.98800373], [ 0.31119478, -0.68396461, -0.65981078], [ 0.65285712, -0.07680383, -0.75357729], [ 0.71209121, -0.26699486, 0.64933801], [ 0.97685069, -0.17835225, 0.11812361], [-0.34381351, -0.93718553, -0.05895334], [ 0.68615079, -0.67022949, 0.2828241 ], [-0.6588605 , -0.59295917, -0.46292791], [ 0.15754543, -0.46902952, -0.86901712], [ 0.89081973, -0.0551779 , 0.45099396], [ 0.09939465, -0.40802586, 0.90754372], [ 0.93604255, -0.26702261, -0.22917943], [-0.93707269, -0.34256476, 0.06741077], [-0.19623031, -0.95031369, 0.24165596], [ 0.45153776, -0.87650371, 0.16689767], [-0.0195914 , -0.72917062, -0.68405145], [ 0.65216494, -0.75583613, -0.05824633], [ 0.13336134, -0.97014433, 0.20257032], [ 0.86954373, -0.49204889, 0.04220884], [ 0.60317737, -0.57162017, -0.55626202], [-0.34353963, -0.78447777, 0.51630914], [-0.35424244, -0.68266952, -0.63912022], [-0.84101379, -0.50332689, -0.19838808], [-0.85885382, -0.33475134, 0.38770047], [ 0.30507854, -0.94518369, -0.11642545], [ 0.7822451 , -0.55481136, -0.28333196], [-0.15034926, -0.90096235, -0.4070158 ], [-0.37529847, -0.20644124, -0.90362221], [-0.87414831, -0.01486413, 0.48543158]]
for i in gradients:
ax.add_artist( Arrow3D([0,-i[0]],[0,-i[1]],[0,-i[2]], mutation_scale=20, lw=1, arrowstyle="-|>", color="k") )
ax.add_artist( Arrow3D([0,i[0]],[0,i[1]],[0,i[2]], mutation_scale=20, lw=1, arrowstyle="-|>", color="k") )
plt.show()
Which results in such images:
However, this is very bad, as no spatial depth is visible. How can I improve the result?
Edit: Their endings have no special meaning, but the reader of the image should see, that these directions are almost uniformly distributed.
The apparent lack of depth in your current rendering is mostly due to the lack of shading.
matplotlib
isn't set up to handle "true" 3D rendering (i.e. occlusion, shading, etc), and you're going to need a lot of the subtle visual hints that this gives to get the impression of depth.
mayavi.mlab
is a good choice for a simple python API to a very full-featured 3D visualization package (VTK).
As an example based on yours:
import numpy as np
from mayavi import mlab
# render the sphere mesh
u, v = np.mgrid[0:2*np.pi:20j, 00:np.pi:10j]
x=np.cos(u)*np.sin(v)
y=np.sin(u)*np.sin(v)
z=np.cos(v)
for a,b,c in zip(x.T, y.T, z.T) + zip(x, y, z):
mlab.plot3d(a, b, c, color=(1,0,0), tube_radius=0.01)
gradients = np.array([[ 0.57735027, -0.57735027, 0.57735027], [ 0.32103634, -0.15367924, -0.93451506], [ 0.41923004, -0.40336058, 0.81335509], [ 0.49175236, -0.7972576 , -0.35008559], [-0.77544582, -0.60942662, 0.16517574], [ 0.49080035, -0.37864643, -0.78469223], [-0.9688558 , -0.14921457, -0.19761968], [-0.55381966, -0.41340241, -0.72276008], [-0.51651138, -0.82581121, 0.22638878], [ 0.51285124, -0.09350922, 0.85336959], [-0.02404559, -0.99431705, -0.10370783], [-0.45752126, -0.15967038, 0.87474549], [-0.11906644, -0.66461051, 0.73764223], [-0.69553846, -0.26904184, 0.66621518], [ 0.84464198, -0.38759068, 0.3692607 ], [ 0.01218729, -0.86413401, 0.5031141 ], [-0.70542359, -0.10816363, -0.70048422], [-0.64579558, -0.75719821, -0.09797404], [ 0.22307518, -0.66173422, 0.715783 ], [ 0.35672677, -0.81123132, 0.46330312], [-0.64859039, -0.59819001, 0.47063699], [-0.9860335 , -0.04110239, 0.16139577], [ 0.78665483, -0.298026 , -0.54069835], [-0.22515292, -0.39356178, 0.89129978], [-0.03115982, -0.20828982, -0.97757065], [ 0.1714755 , -0.88971114, -0.42309603], [-0.1948448 , -0.48424336, -0.85296184], [-0.46394941, -0.51801461, 0.71861798], [-0.45273268, -0.81954175, -0.35126126], [-0.82887799, -0.29984823, -0.47228414], [ 0.21121763, -0.09845807, 0.97246754], [-0.11702234, -0.10076906, 0.98800373], [ 0.31119478, -0.68396461, -0.65981078], [ 0.65285712, -0.07680383, -0.75357729], [ 0.71209121, -0.26699486, 0.64933801], [ 0.97685069, -0.17835225, 0.11812361], [-0.34381351, -0.93718553, -0.05895334], [ 0.68615079, -0.67022949, 0.2828241 ], [-0.6588605 , -0.59295917, -0.46292791], [ 0.15754543, -0.46902952, -0.86901712], [ 0.89081973, -0.0551779 , 0.45099396], [ 0.09939465, -0.40802586, 0.90754372], [ 0.93604255, -0.26702261, -0.22917943], [-0.93707269, -0.34256476, 0.06741077], [-0.19623031, -0.95031369, 0.24165596], [ 0.45153776, -0.87650371, 0.16689767], [-0.0195914 , -0.72917062, -0.68405145], [ 0.65216494, -0.75583613, -0.05824633], [ 0.13336134, -0.97014433, 0.20257032], [ 0.86954373, -0.49204889, 0.04220884], [ 0.60317737, -0.57162017, -0.55626202], [-0.34353963, -0.78447777, 0.51630914], [-0.35424244, -0.68266952, -0.63912022], [-0.84101379, -0.50332689, -0.19838808], [-0.85885382, -0.33475134, 0.38770047], [ 0.30507854, -0.94518369, -0.11642545], [ 0.7822451 , -0.55481136, -0.28333196], [-0.15034926, -0.90096235, -0.4070158 ], [-0.37529847, -0.20644124, -0.90362221], [-0.87414831, -0.01486413, 0.48543158]])
u, v, w = gradients.T
zero = np.zeros_like(u)
kwargs = dict(color=(.2,.2,.2), mode='arrow', resolution=20)
mlab.quiver3d(zero, zero, zero, u, v, w, **kwargs)
mlab.quiver3d(zero, zero, zero, -u, -v, -w, **kwargs)
mlab.show()
The only downside to this is that the relative arrow heads are fixed (they're just a static glyph). It's probably possible to change that (i.e. use a custom glyph), but I don't know how offhand.
You can work around that by drawing the arrow head and body separately. This will be slow to render, but it illustrates the idea:
import numpy as np
from mayavi import mlab
# render the sphere mesh
u, v = np.mgrid[0:2*np.pi:20j, 00:np.pi:10j]
x=np.cos(u)*np.sin(v)
y=np.sin(u)*np.sin(v)
z=np.cos(v)
# White background and turn on antialiasing
fig = mlab.figure(bgcolor=(1,1,1))
fig.scene.render_window.aa_frames = 8
# Plot the sphere's mesh
for a,b,c in zip(x.T, y.T, z.T) + zip(x, y, z):
mlab.plot3d(a, b, c, color=(1,0,0), tube_radius=0.01)
gradients = np.array([[ 0.57735027, -0.57735027, 0.57735027], [ 0.32103634, -0.15367924, -0.93451506], [ 0.41923004, -0.40336058, 0.81335509], [ 0.49175236, -0.7972576 , -0.35008559], [-0.77544582, -0.60942662, 0.16517574], [ 0.49080035, -0.37864643, -0.78469223], [-0.9688558 , -0.14921457, -0.19761968], [-0.55381966, -0.41340241, -0.72276008], [-0.51651138, -0.82581121, 0.22638878], [ 0.51285124, -0.09350922, 0.85336959], [-0.02404559, -0.99431705, -0.10370783], [-0.45752126, -0.15967038, 0.87474549], [-0.11906644, -0.66461051, 0.73764223], [-0.69553846, -0.26904184, 0.66621518], [ 0.84464198, -0.38759068, 0.3692607 ], [ 0.01218729, -0.86413401, 0.5031141 ], [-0.70542359, -0.10816363, -0.70048422], [-0.64579558, -0.75719821, -0.09797404], [ 0.22307518, -0.66173422, 0.715783 ], [ 0.35672677, -0.81123132, 0.46330312], [-0.64859039, -0.59819001, 0.47063699], [-0.9860335 , -0.04110239, 0.16139577], [ 0.78665483, -0.298026 , -0.54069835], [-0.22515292, -0.39356178, 0.89129978], [-0.03115982, -0.20828982, -0.97757065], [ 0.1714755 , -0.88971114, -0.42309603], [-0.1948448 , -0.48424336, -0.85296184], [-0.46394941, -0.51801461, 0.71861798], [-0.45273268, -0.81954175, -0.35126126], [-0.82887799, -0.29984823, -0.47228414], [ 0.21121763, -0.09845807, 0.97246754], [-0.11702234, -0.10076906, 0.98800373], [ 0.31119478, -0.68396461, -0.65981078], [ 0.65285712, -0.07680383, -0.75357729], [ 0.71209121, -0.26699486, 0.64933801], [ 0.97685069, -0.17835225, 0.11812361], [-0.34381351, -0.93718553, -0.05895334], [ 0.68615079, -0.67022949, 0.2828241 ], [-0.6588605 , -0.59295917, -0.46292791], [ 0.15754543, -0.46902952, -0.86901712], [ 0.89081973, -0.0551779 , 0.45099396], [ 0.09939465, -0.40802586, 0.90754372], [ 0.93604255, -0.26702261, -0.22917943], [-0.93707269, -0.34256476, 0.06741077], [-0.19623031, -0.95031369, 0.24165596], [ 0.45153776, -0.87650371, 0.16689767], [-0.0195914 , -0.72917062, -0.68405145], [ 0.65216494, -0.75583613, -0.05824633], [ 0.13336134, -0.97014433, 0.20257032], [ 0.86954373, -0.49204889, 0.04220884], [ 0.60317737, -0.57162017, -0.55626202], [-0.34353963, -0.78447777, 0.51630914], [-0.35424244, -0.68266952, -0.63912022], [-0.84101379, -0.50332689, -0.19838808], [-0.85885382, -0.33475134, 0.38770047], [ 0.30507854, -0.94518369, -0.11642545], [ 0.7822451 , -0.55481136, -0.28333196], [-0.15034926, -0.90096235, -0.4070158 ], [-0.37529847, -0.20644124, -0.90362221], [-0.87414831, -0.01486413, 0.48543158]])
u, v, w = gradients.T
zero = np.zeros_like(u)
color = (0.2, 0.2, 0.2)
head_length = 0.2
for direct in [-1, 1]:
h, b = direct * 0.2, direct * (1 - head_length)
mlab.quiver3d(b*u, b*v, b*w, h*u, h*v, h*w,
mode='cone', scale_factor=1, color=color, resolution=20)
for i, j, k in zip(u, v, w):
mlab.plot3d([0, b*i], [0, b*j], [0, b*k], color=color, tube_radius=0.01)
mlab.show()
You could also change things so that the cones are on the outside of the sphere:
Taking that one step farther, you can improve the rendering speed a bit and get a less confusing figure by doing:
import numpy as np
from mayavi import mlab
# White background and turn on antialiasing
fig = mlab.figure(bgcolor=(1,1,1))
fig.scene.render_window.aa_frames = 8
# Sphere at origin with a diameter of 2 (radius of 1)
ltgray = (0.9, 0.9, 0.9)
mlab.points3d([0], [0], [0], [2], resolution=20, scale_factor=1, color=ltgray)
gradients = np.array([[ 0.57735027, -0.57735027, 0.57735027], [ 0.32103634, -0.15367924, -0.93451506], [ 0.41923004, -0.40336058, 0.81335509], [ 0.49175236, -0.7972576 , -0.35008559], [-0.77544582, -0.60942662, 0.16517574], [ 0.49080035, -0.37864643, -0.78469223], [-0.9688558 , -0.14921457, -0.19761968], [-0.55381966, -0.41340241, -0.72276008], [-0.51651138, -0.82581121, 0.22638878], [ 0.51285124, -0.09350922, 0.85336959], [-0.02404559, -0.99431705, -0.10370783], [-0.45752126, -0.15967038, 0.87474549], [-0.11906644, -0.66461051, 0.73764223], [-0.69553846, -0.26904184, 0.66621518], [ 0.84464198, -0.38759068, 0.3692607 ], [ 0.01218729, -0.86413401, 0.5031141 ], [-0.70542359, -0.10816363, -0.70048422], [-0.64579558, -0.75719821, -0.09797404], [ 0.22307518, -0.66173422, 0.715783 ], [ 0.35672677, -0.81123132, 0.46330312], [-0.64859039, -0.59819001, 0.47063699], [-0.9860335 , -0.04110239, 0.16139577], [ 0.78665483, -0.298026 , -0.54069835], [-0.22515292, -0.39356178, 0.89129978], [-0.03115982, -0.20828982, -0.97757065], [ 0.1714755 , -0.88971114, -0.42309603], [-0.1948448 , -0.48424336, -0.85296184], [-0.46394941, -0.51801461, 0.71861798], [-0.45273268, -0.81954175, -0.35126126], [-0.82887799, -0.29984823, -0.47228414], [ 0.21121763, -0.09845807, 0.97246754], [-0.11702234, -0.10076906, 0.98800373], [ 0.31119478, -0.68396461, -0.65981078], [ 0.65285712, -0.07680383, -0.75357729], [ 0.71209121, -0.26699486, 0.64933801], [ 0.97685069, -0.17835225, 0.11812361], [-0.34381351, -0.93718553, -0.05895334], [ 0.68615079, -0.67022949, 0.2828241 ], [-0.6588605 , -0.59295917, -0.46292791], [ 0.15754543, -0.46902952, -0.86901712], [ 0.89081973, -0.0551779 , 0.45099396], [ 0.09939465, -0.40802586, 0.90754372], [ 0.93604255, -0.26702261, -0.22917943], [-0.93707269, -0.34256476, 0.06741077], [-0.19623031, -0.95031369, 0.24165596], [ 0.45153776, -0.87650371, 0.16689767], [-0.0195914 , -0.72917062, -0.68405145], [ 0.65216494, -0.75583613, -0.05824633], [ 0.13336134, -0.97014433, 0.20257032], [ 0.86954373, -0.49204889, 0.04220884], [ 0.60317737, -0.57162017, -0.55626202], [-0.34353963, -0.78447777, 0.51630914], [-0.35424244, -0.68266952, -0.63912022], [-0.84101379, -0.50332689, -0.19838808], [-0.85885382, -0.33475134, 0.38770047], [ 0.30507854, -0.94518369, -0.11642545], [ 0.7822451 , -0.55481136, -0.28333196], [-0.15034926, -0.90096235, -0.4070158 ], [-0.37529847, -0.20644124, -0.90362221], [-0.87414831, -0.01486413, 0.48543158]])
u, v, w = gradients.T
# Put cones on the outside of the sphere
for direct in [-1, 1]:
h = direct * 0.2 # Head length
mlab.quiver3d(direct*u, direct*v, direct*w, h*u, h*v, h*w, color=(.2,.2,.2),
mode='cone', scale_factor=1, resolution=20)
mlab.show()
You could also make the sphere slightly larger as a fudge factor to get around the flat sides not exactly intersecting the base of some cones (e.g. points3d([0],[0],[0],[2.05],...)
):
Another way to show this is a stereonet. Stereonets are a bit obscure, but they're a very good way to show distribution of orientations in 3D using a 2D plot.
import numpy as np
import matplotlib.pyplot as plt
import mplstereonet
gradients = np.array([[ 0.57735027, -0.57735027, 0.57735027], [ 0.32103634, -0.15367924, -0.93451506], [ 0.41923004, -0.40336058, 0.81335509], [ 0.49175236, -0.7972576 , -0.35008559], [-0.77544582, -0.60942662, 0.16517574], [ 0.49080035, -0.37864643, -0.78469223], [-0.9688558 , -0.14921457, -0.19761968], [-0.55381966, -0.41340241, -0.72276008], [-0.51651138, -0.82581121, 0.22638878], [ 0.51285124, -0.09350922, 0.85336959], [-0.02404559, -0.99431705, -0.10370783], [-0.45752126, -0.15967038, 0.87474549], [-0.11906644, -0.66461051, 0.73764223], [-0.69553846, -0.26904184, 0.66621518], [ 0.84464198, -0.38759068, 0.3692607 ], [ 0.01218729, -0.86413401, 0.5031141 ], [-0.70542359, -0.10816363, -0.70048422], [-0.64579558, -0.75719821, -0.09797404], [ 0.22307518, -0.66173422, 0.715783 ], [ 0.35672677, -0.81123132, 0.46330312], [-0.64859039, -0.59819001, 0.47063699], [-0.9860335 , -0.04110239, 0.16139577], [ 0.78665483, -0.298026 , -0.54069835], [-0.22515292, -0.39356178, 0.89129978], [-0.03115982, -0.20828982, -0.97757065], [ 0.1714755 , -0.88971114, -0.42309603], [-0.1948448 , -0.48424336, -0.85296184], [-0.46394941, -0.51801461, 0.71861798], [-0.45273268, -0.81954175, -0.35126126], [-0.82887799, -0.29984823, -0.47228414], [ 0.21121763, -0.09845807, 0.97246754], [-0.11702234, -0.10076906, 0.98800373], [ 0.31119478, -0.68396461, -0.65981078], [ 0.65285712, -0.07680383, -0.75357729], [ 0.71209121, -0.26699486, 0.64933801], [ 0.97685069, -0.17835225, 0.11812361], [-0.34381351, -0.93718553, -0.05895334], [ 0.68615079, -0.67022949, 0.2828241 ], [-0.6588605 , -0.59295917, -0.46292791], [ 0.15754543, -0.46902952, -0.86901712], [ 0.89081973, -0.0551779 , 0.45099396], [ 0.09939465, -0.40802586, 0.90754372], [ 0.93604255, -0.26702261, -0.22917943], [-0.93707269, -0.34256476, 0.06741077], [-0.19623031, -0.95031369, 0.24165596], [ 0.45153776, -0.87650371, 0.16689767], [-0.0195914 , -0.72917062, -0.68405145], [ 0.65216494, -0.75583613, -0.05824633], [ 0.13336134, -0.97014433, 0.20257032], [ 0.86954373, -0.49204889, 0.04220884], [ 0.60317737, -0.57162017, -0.55626202], [-0.34353963, -0.78447777, 0.51630914], [-0.35424244, -0.68266952, -0.63912022], [-0.84101379, -0.50332689, -0.19838808], [-0.85885382, -0.33475134, 0.38770047], [ 0.30507854, -0.94518369, -0.11642545], [ 0.7822451 , -0.55481136, -0.28333196], [-0.15034926, -0.90096235, -0.4070158 ], [-0.37529847, -0.20644124, -0.90362221], [-0.87414831, -0.01486413, 0.48543158]])
x, y, z = gradients.T
fig, ax = mplstereonet.subplots(projection='equal_area')
ax.grid(True)
ax.pole(*mplstereonet.vector2pole(x,y,z))
plt.show()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With