Python 将箭头放在 matplotlib 的 3d 图中的向量上

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22867620/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 01:50:45  来源:igfitidea点击:

Putting arrowheads on vectors in matplotlib's 3d plot

pythonmatplotlibplot

提问by

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. enter image description here

我绘制了一些 3D 数据的特征向量,想知道目前(已经)是否有办法将箭头放在线上?如果有人给我小费,那就太棒了。在此处输入图片说明

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

采纳答案by CT Zhu

To add arrow patches to a 3D plot, the simple solution is to use FancyArrowPatchclass defined in /matplotlib/patches.py. However, it only works for 2D plot (at the time of writing), as its posAand posBare supposed to be tuples of length 2.

修补程序添加箭头,3D绘图,简单的解决方案是使用FancyArrowPatch中定义的类/matplotlib/patches.py。但是,它仅适用于 2D 绘图(在撰写本文时),因为它posAposB应该是长度为 2 的元组。

Therefore we create a new arrow patch class, name it Arrow3D, which inherits from FancyArrowPatch. The only thing we need to override its posAand posB. To do that, we initiate Arrow3dwith posAand posBof (0,0)s. The 3D coordinates xs, ys, zswas then projected from 3D to 2D using proj3d.proj_transform(), and the resultant 2D coordinates get assigned to posAand posBusing .set_position()method, replacing the (0,0)s. This way we get the 3D arrow to work.

因此我们创建了一个新的箭头补丁类,命名为Arrow3D,它继承自FancyArrowPatch. 我们唯一需要覆盖它的posAposB。为此,我们Arrow3d以sposAposBof开始(0,0)xs, ys, zs然后使用将 3D 坐标从 3D 投影到 2D proj3d.proj_transform(),并将生成的 2D 坐标分配给posAposB使用.set_position()方法,替换(0,0)s。这样我们就可以使 3D 箭头起作用。

The projection steps go into the .drawmethod, which overrides the .drawmethod of the FancyArrowPatchobject.

投影步骤进入.draw方法,该.draw方法覆盖FancyArrowPatch对象的方法。

This might appear like a hack. However, the mplot3dcurrently 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.

这可能看起来像一个黑客。但是,mplot3d目前仅通过提供 3D-2D 投影来提供(再次,仅)简单的 3D 绘图能力,并且基本上以 2D 进行所有绘图,这不是真正的 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()

final_output

最终输出

Please check this post, which inspired this question, for further details.

请查看这篇启发了这个问题的帖子,以获取更多详细信息。

回答by Matt W

Another option: you can also use the plt.quiverfunction, which allows you to produce arrow vectors pretty easily without any extra imports or classes.

另一种选择:您也可以使用该plt.quiver函数,它允许您非常轻松地生成箭头向量,而无需任何额外的导入或类。

To replicate your example, you would replace:

要复制您的示例,您将替换:

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)

with:

和:

for v in eig_vec:
    ax.quiver(
        mean_x, mean_y, mean_z, # <-- starting point of vector
        v[0] - mean_x, v[1] - mean_y, v[2] - mean_z, # <-- directions of vector
        color = 'red', alpha = .8, lw = 3,
    )