简体   繁体   中英

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.

In matplotlib's interactive plot, I can drag and rotate the 3D plot, but there is one issue:

it seems that the Z-axis is restricted to a plane when I drag the plot. 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.

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?

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.

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 .

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. 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() :

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 .

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.

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. 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.

If you want to try the interactors from Mayavi / VTK:

pip install mayavi (or pip3 install mayavi depending on your version and your virtual environment).

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()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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