在Matplotlib的3D图中将箭头放在矢量上 [英] Putting arrowheads on vectors in matplotlib's 3d plot
问题描述
我绘制了一些3D数据的特征向量,想知道当前是否(已经)有一种方法可以将箭头放在直线上?如果有人给我小费,那会很棒.
I plotted the eigenvectors of some 3D-data and was wondering if there is currently (already) a way to put arrowheads on the lines? Would be awesome if someone has a tip for me.
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
####################################################
# This part is just for reference if
# you are interested where the data is
# coming from
# The plot is at the bottom
#####################################################
# Generate some example data
mu_vec1 = np.array([0,0,0])
cov_mat1 = np.array([[1,0,0],[0,1,0],[0,0,1]])
class1_sample = np.random.multivariate_normal(mu_vec1, cov_mat1, 20)
mu_vec2 = np.array([1,1,1])
cov_mat2 = np.array([[1,0,0],[0,1,0],[0,0,1]])
class2_sample = np.random.multivariate_normal(mu_vec2, cov_mat2, 20)
# concatenate data for PCA
samples = np.concatenate((class1_sample, class2_sample), axis=0)
# mean values
mean_x = mean(samples[:,0])
mean_y = mean(samples[:,1])
mean_z = mean(samples[:,2])
#eigenvectors and eigenvalues
eig_val, eig_vec = np.linalg.eig(cov_mat)
################################
#plotting eigenvectors
################################
fig = plt.figure(figsize=(15,15))
ax = fig.add_subplot(111, projection='3d')
ax.plot(samples[:,0], samples[:,1], samples[:,2], 'o', markersize=10, color='green', alpha=0.2)
ax.plot([mean_x], [mean_y], [mean_z], 'o', markersize=10, color='red', alpha=0.5)
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)
ax.set_xlabel('x_values')
ax.set_ylabel('y_values')
ax.set_zlabel('z_values')
plt.title('Eigenvectors')
plt.draw()
plt.show()
推荐答案
要将箭头补丁添加到3D图中,简单的解决方案是使用/matplotlib/patches.py
中定义的FancyArrowPatch
类.但是,它仅适用于2D图(在撰写本文时),因为其posA
和posB
应该是长度为2的元组.
To add arrow patches to a 3D plot, the simple solution is to use FancyArrowPatch
class defined in /matplotlib/patches.py
. However, it only works for 2D plot (at the time of writing), as its posA
and posB
are supposed to be tuples of length 2.
因此,我们创建了一个新的箭头补丁类,将其命名为Arrow3D
,该类继承自FancyArrowPatch
.我们唯一需要覆盖其posA
和posB
的东西.为此,我们用(0,0)
的posA
和posB
来启动Arrow3d
.然后使用proj3d.proj_transform()
将3D坐标xs, ys, zs
从3D投影到2D,并使用.set_position()
方法将所得的2D坐标分配给posA
和posB
,以替换(0,0)
s.这样我们就可以使用3D箭头了.
Therefore we create a new arrow patch class, name it Arrow3D
, which inherits from FancyArrowPatch
. The only thing we need to override its posA
and posB
. To do that, we initiate Arrow3d
with posA
and posB
of (0,0)
s. The 3D coordinates xs, ys, zs
was then projected from 3D to 2D using proj3d.proj_transform()
, and the resultant 2D coordinates get assigned to posA
and posB
using .set_position()
method, replacing the (0,0)
s. This way we get the 3D arrow to work.
投影步骤进入.draw
方法,该方法将覆盖FancyArrowPatch
对象的.draw
方法.
The projection steps go into the .draw
method, which overrides the .draw
method of the FancyArrowPatch
object.
这可能看起来像黑客一样.但是,mplot3d
当前仅通过提供3D-2D投影提供(再次)提供简单的3D绘图功能,并且基本上以2D进行所有绘图,而这并不是真正的3D.
This might appear like a hack. However, the mplot3d
currently only provides (again, only) simple 3D plotting capacity by supplying 3D-2D projections and essentially does all the plotting in 2D, which is not truly 3D.
import numpy as np
from numpy import *
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.patches import FancyArrowPatch
from mpl_toolkits.mplot3d import proj3d
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)
####################################################
# This part is just for reference if
# you are interested where the data is
# coming from
# The plot is at the bottom
#####################################################
# Generate some example data
mu_vec1 = np.array([0,0,0])
cov_mat1 = np.array([[1,0,0],[0,1,0],[0,0,1]])
class1_sample = np.random.multivariate_normal(mu_vec1, cov_mat1, 20)
mu_vec2 = np.array([1,1,1])
cov_mat2 = np.array([[1,0,0],[0,1,0],[0,0,1]])
class2_sample = np.random.multivariate_normal(mu_vec2, cov_mat2, 20)
实际图纸.请注意,我们只需要更改代码的一行即可添加新的箭头艺术家:
Actual drawing. Note that we only need to change one line of your code, which add an new arrow artist:
# concatenate data for PCA
samples = np.concatenate((class1_sample, class2_sample), axis=0)
# mean values
mean_x = mean(samples[:,0])
mean_y = mean(samples[:,1])
mean_z = mean(samples[:,2])
#eigenvectors and eigenvalues
eig_val, eig_vec = np.linalg.eig(cov_mat1)
################################
#plotting eigenvectors
################################
fig = plt.figure(figsize=(15,15))
ax = fig.add_subplot(111, projection='3d')
ax.plot(samples[:,0], samples[:,1], samples[:,2], 'o', markersize=10, color='g', alpha=0.2)
ax.plot([mean_x], [mean_y], [mean_z], 'o', markersize=10, color='red', alpha=0.5)
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([mean_x, v[0]], [mean_y, v[1]],
[mean_z, v[2]], mutation_scale=20,
lw=3, arrowstyle="-|>", color="r")
ax.add_artist(a)
ax.set_xlabel('x_values')
ax.set_ylabel('y_values')
ax.set_zlabel('z_values')
plt.title('Eigenvectors')
plt.draw()
plt.show()
请查看这篇文章,这激发了这个问题,以获取更多详细信息.
Please check this post, which inspired this question, for further details.
这篇关于在Matplotlib的3D图中将箭头放在矢量上的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!