如何更改Matplotlib 3d旋转(mplot3d)的鼠标交互样式?

wwodge7n  于 2023-10-24  发布在  其他
关注(0)|答案(2)|浏览(164)

我绘制了一个3D图,并使用quiver绘制x,y和z轴。
在matplotlib的交互式绘图中,我可以拖动和旋转3D绘图,但有一个问题:
拖动绘图时,Z轴似乎被限制在一个平面内,无论我如何拖动绘图,Z轴只能以有限的方式旋转(在平面内),而X轴和Y轴可以自由旋转。
我的问题是:这是matplotlib的一个限制,还是有什么方法可以配置x,y和z轴如何旋转?
任何建议都很感激。
附上一个最小可重复的示例以供参考:

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np

n_radii = 8
n_angles = 36

# Make radii and angles spaces (radius r=0 omitted to eliminate duplication).
radii = np.linspace(0.125, 1.0, n_radii)
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)

# Repeat all angles for each radius.
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)

# Convert polar (radii, angles) coords to cartesian (x, y) coords.
# (0, 0) is manually added at this stage,  so there will be no duplicate
# points in the (x, y) plane.
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())

# Compute z to make the pringle surface.
z = np.sin(-x*y)

fig = plt.figure()
ax = fig.gca(projection='3d')

ax.plot_trisurf(x, y, z, linewidth=0.2, antialiased=True)

steps = 100
theta = np.linspace(0, 2 * np.pi, steps)
r_max = 1.2
x = np.zeros_like(theta)
y = r_max * np.cos(theta)
z = r_max * np.sin(theta)
ax.plot(x, y, z, 'r')
ax.plot(y, x, z, 'g')
ax.plot(z, y, x, 'b')

scale = 1.08
ax.quiver((0,), (0), (0), 
          (0), (0), (r_max), color=('c'))
ax.text(0, 0, r_max * scale, 'Z Theta', weight='bold')

ax.quiver((0), (0), (0), 
          (0), (r_max), (0), color=('m'))
ax.text(0, r_max * scale, 0, 'Y', weight='bold')

ax.quiver((0), (0), (0), 
          (r_max), (0), (0), color=('y'))
ax.text(r_max * scale, 0, 0, 'X', weight='bold')

plt.show()

9gm1akwq

9gm1akwq1#

我的第一个建议是这样的。
但如果这根本不可能,我找到了一个可行的解决方案。
Axes3D中的方法_on_move负责处理鼠标事件和旋转绘图。
正如你所看到的,这个函数只考虑方位角和仰角,这就是为什么它的行为方式。
可以重新绑定默认的_on_move,如在Axes3D的构造函数中调用的方法mouse_init()中所示。
假设我们的自定义鼠标交互样式定义在

def _my_on_move(self, event):
    print('my custom mouse style', event)

这不起作用:

ax._on_move = _my_on_move

因为_my_on_move是一个函数,但我们需要它是一个绑定方法,这样self才可用。解决方案是将函数绑定为方法,这在here中有详细描述:

import types
ax._on_move = types.MethodType(_my_on_move, ax)

然后重新运行鼠标初始化:

ax.mouse_init()

原始_on_move中的这一部分将设置elevazim,然后get_proj()使用它们来设置figure.canvas.draw_idle()中使用的变换矩阵:

self.elev = art3d._norm_angle(self.elev - (dy/h)*180)
self.azim = art3d._norm_angle(self.azim - (dx/w)*180)
self.get_proj() 
self.figure.canvas.draw_idle()

不知何故,我们必须偷偷地修改转换矩阵。我不确定是否有更好的方法,但我们可以只传入elevazim的修改值。
既然我们想要更聪明的东西,我们应该切换到四元数。我建议使用transformations.py,但也有一个模块称为mathutils从Blender与工程罚款。
现在到有趣的部分:
你必须得到当前视图(当前变换矩阵),并根据鼠标的移动旋转它。然后从旋转的矩阵中提取等价的elevazim。有趣的任务,一些数学,但它应该是可能的。
但我会把它留给别人:)
也许在VTK的interactors或Blender中发现了一些错误。
如果你想尝试Mayavi / VTK的交互器:
pip install mayavi(或pip3 install mayavi,取决于您的版本和虚拟环境)。
然后运行

from mayavi import mlab
from tvtk.api import tvtk

for i in [tvtk.InteractorStyleTerrain(),
          tvtk.InteractorStyleJoystickActor(),
          tvtk.InteractorStyleTrackballActor(),
          tvtk.InteractorStyleTrackball()]:

    mlab.test_surf()
    fig = mlab.gcf()
    fig.scene.interactor.interactor_style = i
    mlab.show()
lvjbypge

lvjbypge2#

从Matplotlib 3.6.0开始,通过添加第三个“滚动”Angular ,可以通过编程方式更改此视图Angular ,请参见:https://matplotlib.org/stable/users/prev_whats_new/whats_new_3.6.0.html#d-plots-gained-a-3rd-roll-viewing-angle
然而,使用鼠标进行交互式旋转仍然仅限于改变方位角和仰角。这里有一个问题要讨论其他方法:https://github.com/matplotlib/matplotlib/issues/26492

相关问题