Ответ 1
Чтобы добавить стрелки к 3D-графике, простым решением является использование класса FancyArrowPatch
, определенного в /matplotlib/patches.py
. Однако он работает только для 2D-графика (на момент написания), поскольку его posA
и posB
должны быть кортежами длины 2.
Поэтому мы создаем новый класс патчей для стрелок, назовите его Arrow3D
, который наследуется от FancyArrowPatch
. Единственное, что нам нужно, чтобы переопределить его posA
и posB
. Для этого мы начинаем Arrow3D
с posA
и posB
of (0,0)
s. 3D-координаты xs, ys, zs
затем проецировались из 3D в 2D с использованием proj3d.proj_transform()
, а полученные 2D-координаты присваиваются posA
и posB
с использованием метода .set_position()
, заменяя (0,0)
s. Таким образом, мы получаем 3D-стрелку для работы.
Шаги проектирования входят в метод .draw
, который переопределяет метод .draw
объекта FancyArrowPatch
.
Это может выглядеть как хак. Тем не менее, mplot3d
в настоящее время предоставляет только (просто, только) простую 3D-графическую емкость, поставляя 3D-2D проекции и, по существу, делает все графики в 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)
Фактический чертеж. Обратите внимание, что нам нужно только изменить одну строку вашего кода, которая добавит новый художник стрелки:
# 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()
Пожалуйста, проверьте этот пост, который вдохновил этот вопрос, для более подробной информации.