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.