[英]How to change the mouse interaction style for Matplotlib 3d rotation (mplot3d)?
I plotted a 3D plot and used quiver
to plot x, y and z axis.我绘制了一个 3D 图并使用quiver
绘制了 x、y 和 z 轴。
In matplotlib's interactive plot, I can drag and rotate the 3D plot, but there is one issue:在 matplotlib 的交互式绘图中,我可以拖动和旋转 3D 绘图,但存在一个问题:
it seems that the Z-axis is restricted to a plane when I drag the plot.当我拖动绘图时,Z 轴似乎被限制在一个平面上。 No matter how I drag the plot, Z-axis can only rotate in a limited manner(in a plane), while X-axis and Y-axis can be rotated freely.无论我如何拖动绘图,Z轴都只能以有限的方式(在平面内)旋转,而X轴和Y轴可以自由旋转。
My question is: is this a limitation of matplotlib or is there any method that I can configure how x, y and z-axis can be rotated?我的问题是:这是 matplotlib 的限制,还是有任何方法可以配置如何旋转 x、y 和 z 轴?
Any suggestions are appreciated.任何建议表示赞赏。
A mininum reproducible example is attached for reference:附上一个最小的可重现示例以供参考:
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()
My first recommendation is this .我的第一个建议是这个。
But if that is not possible at all, I found a solution that could work.但如果这根本不可能,我找到了一个可行的解决方案。
The method _on_move
in Axes3D
responsible for processing the mouse events and rotating the plot. _on_move
中的Axes3D
方法负责处理鼠标事件并旋转绘图。
As you can see this function only thinks in azimuth and elevation.正如你所看到的,这个函数只考虑方位角和仰角。 That`s why it behaves the way it does.这就是为什么它的行为方式如此。
It is possible to re-bind the default _on_move
as seen in the method mouse_init()
which is called in the constructor of Axes3D
.可以重新绑定默认_on_move
如在mouse_init()
的构造函数中调用的方法mouse_init()
中Axes3D
。
Say our custom mouse interaction style is defined in假设我们的自定义鼠标交互样式定义在
def _my_on_move(self, event):
print('my custom mouse style', event)
This does not work:这不起作用:
ax._on_move = _my_on_move
because _my_on_move
is a function but we need it to be a bound method so the self
is available.因为_my_on_move
是一个函数,但我们需要它是一个绑定方法,以便self
可用。 The solution is to bind the function as method, this is described in detail here :该解决方案是结合该功能的方法,这将详细描述这里:
import types
ax._on_move = types.MethodType(_my_on_move, ax)
and re-run the mouse initialization:并重新运行鼠标初始化:
ax.mouse_init()
This part in the original _on_move
will set elev
and azim
which are then used by get_proj()
to set the transformation matrix used in figure.canvas.draw_idle()
:这部分在原_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()
Somehow we have to sneak in a modified transformation matrix.不知何故,我们必须潜入修改后的变换矩阵。 I am not sure if there is a better way, but we could just pass in modified values for elev
and azim
.我不知道是否有更好的办法,但我们可以只通过在修改的值elev
和azim
。
Since we want something smarter we should switch to quaternions.既然我们想要更智能的东西,我们应该切换到四元数。 I recommend using transformations.py but there is also a module called mathutils
from Blender with works fine.我建议使用transformations.py,但也有一个来自Blender的名为mathutils
的模块,工作正常。
Now to the fun part:现在到有趣的部分:
You have to get the current view (the current transformation matrix) and rotate it based on the mouse movement.您必须获取当前视图(当前转换矩阵)并根据鼠标移动对其进行旋转。 Then extract the equivalent elev
and azim
from the rotated matrix.然后提取相当于elev
和azim
从旋转矩阵。 Fun task, some math, but it should be possible.有趣的任务,一些数学,但应该是可能的。
But I will leave that for to someone else :)但我会把它留给别人:)
Maybe there is some inspitation found in VTK`s interactors or the ones from Blender.也许在 VTK 的 交互器或 Blender 的 交互器中发现了一些灵感。
If you want to try the interactors from Mayavi / VTK:如果您想尝试 Mayavi / VTK 的交互器:
pip install mayavi
(or pip3 install mayavi
depending on your version and your virtual environment). pip install mayavi
(或pip3 install mayavi
取决于您的版本和虚拟环境)。
Then run然后运行
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()
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.