简体   繁体   English

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

[英]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将设置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()

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 .我不知道是否有更好的办法,但我们可以只通过在修改的值elevazim

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.然后提取相当于elevazim从旋转矩阵。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM