python - 如何更改 Matplotlib 3d 旋转(mplot3d)的鼠标交互方式?
问题描述
我绘制了一个 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()
解决方案
我的第一个建议是这个。
但如果这根本不可能,我找到了一个可行的解决方案。
负责处理鼠标事件和旋转绘图的方法_on_move
。Axes3D
如您所见,此功能仅考虑方位角和仰角。这就是为什么它的行为方式。
可以重新绑定在构造函数中调用_on_move
的方法中看到的默认值。mouse_init()
Axes3D
假设我们的自定义鼠标交互样式定义在
def _my_on_move(self, event):
print('my custom mouse style', event)
这不起作用:
ax._on_move = _my_on_move
因为_my_on_move
是一个函数,但我们需要它是一个绑定方法,所以它self
是可用的。解决方案是将函数绑定为方法,此处详细描述:
import types
ax._on_move = types.MethodType(_my_on_move, ax)
并重新运行鼠标初始化:
ax.mouse_init()
原始中的这部分_on_move
将设置elev
,azim
然后用于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()
不知何故,我们不得不潜入一个修改过的变换矩阵。我不确定是否有更好的方法,但我们可以将修改后的值传递给elev
and azim
。
由于我们想要更智能的东西,我们应该切换到四元数。我建议使用transformations.py
,但也有一个mathutils
从 Blender 调用的模块可以正常工作。
现在到有趣的部分:
您必须获取当前视图(当前变换矩阵)并根据鼠标移动对其进行旋转。
然后从旋转矩阵中提取等价的elev
和。azim
有趣的任务,一些数学,但它应该是可能的。
但我会把它留给别人:)
也许在 VTK的交互器或 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()
推荐阅读
- r - 将图例和标签放入 dtwclust 图
- php - Nginx+php-fpm。静态网站 + WordPress
- javascript - 你如何将类映射到之前是 HTMLcollection 的数组
- python-3.x - 如何在树莓派 3 上制作 HC-SR04 超声波测距传感器?
- java - 无法连接到本地火花会话
- excel - 导入具有重复元素但属性名称不同的 XML 模式
- java - Spring Boot 启动失败
- python-3.x - 初始化属性上的函数
- laravel-5 - 为什么在引导程序 4.1 中左侧菜单图标下拉项目不在有效位置?
- assembly - 为什么 push EBX 会导致 pop ESI