Skip to content

Commit 133c916

Browse files
committed
Implement review suggestions
1 parent 077ba10 commit 133c916

File tree

3 files changed

+117
-63
lines changed

3 files changed

+117
-63
lines changed

doc/api/toolkits/mplot3d/view_angles.rst

Lines changed: 68 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ further documented in the `.mplot3d.axes3d.Axes3D.view_init` API.
4040
:align: center
4141

4242

43+
.. _toolkit_mouse-rotation:
44+
4345
Rotation with mouse
4446
===================
4547

@@ -48,99 +50,132 @@ There are various ways to accomplish this; the style of mouse rotation
4850
can be specified by setting ``rcParams.axes3d.mouserotationstyle``, see
4951
:doc:`/users/explain/customizing`.
5052

51-
Originally (with ``mouserotationstyle: azel``), the 2D mouse position
52-
corresponded directly to azimuth and elevation; this is also how it is done
53+
Originally (prior to v3.10), the 2D mouse position corresponded directly
54+
to azimuth and elevation; this is also how it is done
5355
in `MATLAB <https://www.mathworks.com/help/matlab/ref/view.html>`_.
56+
To keep it this way, set ``mouserotationstyle: azel``.
5457
This approach works fine for polar plots, where the *z* axis is special;
5558
however, it leads to a kind of 'gimbal lock' when looking down the *z* axis:
5659
the plot reacts differently to mouse movement, dependent on the particular
5760
orientation at hand. Also, 'roll' cannot be controlled.
5861

5962
As an alternative, there are various mouse rotation styles where the mouse
60-
manipulates a 'trackball'. In its simplest form (``mouserotationstyle: trackball``),
63+
manipulates a virtual 'trackball'. In its simplest form (``mouserotationstyle: trackball``),
6164
the trackball rotates around an in-plane axis perpendicular to the mouse motion
6265
(it is as if there is a plate laying on the trackball; the plate itself is fixed
6366
in orientation, but you can drag the plate with the mouse, thus rotating the ball).
6467
This is more natural to work with than the ``azel`` style; however,
6568
the plot cannot be easily rotated around the viewing direction - one has to
66-
drag the mouse in circles with a handedness opposite to the desired rotation.
69+
move the mouse in circles with a handedness opposite to the desired rotation,
70+
counterintuitively.
6771

6872
A different variety of trackball rotates along the shortest arc on the virtual
6973
sphere (``mouserotationstyle: arcball``); it is a variation on Ken Shoemake's
7074
ARCBALL [Shoemake1992]_. Rotating around the viewing direction is straightforward
71-
with it. Shoemake's original arcball is also available
72-
(``mouserotationstyle: Shoemake``); it is free of hysteresis, i.e.,
73-
returning mouse to the original position returns the figure to its original
74-
orientation, the rotation is independent of the details of the path the mouse
75-
took. However, Shoemake's arcball rotates at twice the angular rate of the
76-
mouse movement (it is quite noticeable, especially when adjusting roll).
75+
with it (grab the ball near its edge instead of near the center).
76+
77+
Shoemake's original arcball is also available (``mouserotationstyle: Shoemake``);
78+
it is free of hysteresis, i.e., returning mouse to the original position
79+
returns the figure to its original orientation, the rotation is independent
80+
of the details of the path the mouse took, which could be desirable.
81+
However, Shoemake's arcball rotates at twice the angular rate of the
82+
mouse movement (it is quite noticeable, especially when adjusting roll),
83+
and it lacks an obvious mechanical equivalent; arguably, the path-independent rotation is unnatural.
7784
So it is a trade-off.
7885

7986
Shoemake's arcball has an abrupt edge; this is remedied in Holroyd's arcball
8087
(``mouserotationstyle: Holroyd``).
8188

82-
Henriksen et al. [Henriksen2002]_ provide an overview.
83-
84-
In summary:
89+
Henriksen et al. [Henriksen2002]_ provide an overview. In summary:
8590

8691
.. list-table::
8792
:width: 100%
88-
:widths: 30 20 20 20 35
93+
:widths: 30 20 20 20 20 35
8994

9095
* - Style
9196
- traditional [1]_
9297
- incl. roll [2]_
9398
- uniform [3]_
9499
- path independent [4]_
100+
- mechanical counterpart [5]_
95101
* - azel
96102
- ✔️
97103
- ❌
98104
- ❌
99105
- ✔️
106+
- ✔️
100107
* - trackball
101108
- ❌
102-
-
109+
- ✓ [6]_
103110
- ✔️
104111
- ❌
112+
- ✔️
105113
* - arcball
106114
- ❌
107115
- ✔️
108116
- ✔️
109117
- ❌
118+
- ✔️
110119
* - Shoemake
111120
- ❌
112121
- ✔️
113122
- ✔️
114123
- ✔️
124+
- ❌
115125
* - Holroyd
116126
- ❌
117127
- ✔️
118128
- ✔️
119129
- ✔️
130+
- ❌
120131

121132

122-
.. [1] The way it was historically; this is also MATLAB's style
133+
.. [1] The way it was prior to v3.10; this is also MATLAB's style
123134
.. [2] Mouse controls roll too (not only azimuth and elevation)
124135
.. [3] Figure reacts the same way to mouse movements, regardless of orientation (no difference between 'poles' and 'equator')
125136
.. [4] Returning mouse to original position returns figure to original orientation (no hysteresis: rotation is independent of the details of the path the mouse took)
137+
.. [5] The style has a corresponding natural implementation as a mechanical device
138+
.. [6] While it is possible to control roll with the ``trackball`` style, this is not very intuitive (it requires moving the mouse in large circles) and the resulting roll is in the opposite direction
126139
127-
Try it out by adding a file ``matplotlibrc`` to folder ``matplotlib\galleries\examples\mplot3d``,
128-
with contents::
140+
You can try out one of the various mouse rotation styles using::
129141

130-
axes3d.mouserotationstyle: arcball
142+
.. code::
143+
144+
import matplotlib as mpl
145+
mpl.rcParams['axes3d.mouserotationstyle'] = 'trackball' # 'azel', 'trackball', 'arcball', 'Shoemake', or 'Holroyd'
131146
132-
(or any of the other styles), and run a suitable example, e.g.::
147+
import numpy as np
148+
import matplotlib.pyplot as plt
149+
from matplotlib import cm
150+
151+
ax = plt.figure().add_subplot(projection='3d')
152+
153+
X = np.arange(-5, 5, 0.25)
154+
Y = np.arange(-5, 5, 0.25)
155+
X, Y = np.meshgrid(X, Y)
156+
R = np.sqrt(X**2 + Y**2)
157+
Z = np.sin(R)
158+
159+
surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm,
160+
linewidth=0, antialiased=False)
133161
134-
python surfaced3d.py
162+
plt.show()
135163
136-
(If eternal compatibility with the horrors of the past is less of a consideration
137-
for you, then it is likely that you would want to go with ``arcball``, ``Shoemake``,
138-
or ``Holroyd``.)
164+
Alternatively, create a file ``matplotlibrc``, with contents::
139165

140-
The size of the trackball or arcball can be adjusted by setting
141-
``rcParams.axes3d.trackballsize``, in units of the Axes bounding box;
166+
axes3d.mouserotationstyle: arcball
167+
168+
(or any of the other styles, instead of ``arcball``), and then run any of
169+
the :ref:`mplot3d-examples-index` examples.
170+
171+
The size of the virtual trackball or arcball can be adjusted as well,
172+
by setting ``rcParams.axes3d.trackballsize``. This specifies how much
173+
mouse motion is needed to obtain a given rotation angle (when near the center),
174+
and it controls where the edge of the arcball is (how far from the center,
175+
how close to the plot edge).
176+
The size is specified in units of the Axes bounding box,
142177
i.e., to make the trackball span the whole bounding box, set it to 1.
143-
A size of ca. 2/3 appears to work reasonably well.
178+
A size of about 2/3 appears to work reasonably well; this is the default.
144179

145180
----
146181

@@ -149,8 +184,10 @@ A size of ca. 2/3 appears to work reasonably well.
149184
Interface '92, 1992, pp. 151-156, https://doi.org/10.20380/GI1992.18
150185
151186
.. [Henriksen2002] Knud Henriksen, Jon Sporring, Kasper Hornbæk,
152-
"Virtual Trackballs Revisited", in Proceedings of DSAGM'2002:
153-
http://www.diku.dk/~kash/papers/DSAGM2002_henriksen.pdf;
154-
and in IEEE Transactions on Visualization
155-
and Computer Graphics, Volume 10, Issue 2, March-April 2004, pp. 206-216,
187+
"Virtual Trackballs Revisited", in Proceedings of DSAGM'2002
188+
`[pdf]`__;
189+
and in IEEE Transactions on Visualization and Computer Graphics,
190+
Volume 10, Issue 2, March-April 2004, pp. 206-216,
156191
https://doi.org/10.1109/TVCG.2004.1260772
192+
193+
__ https://web.archive.org/web/20240607102518/http://hjemmesider.diku.dk/~kash/papers/DSAGM2002_henriksen.pdf

doc/users/next_whats_new/mouse_rotation.rst

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,37 @@ degrees of freedom (azimuth, elevation, and roll). By default,
88
it uses a variation on Ken Shoemake's ARCBALL [1]_.
99
The particular style of mouse rotation can be set via
1010
``rcParams.axes3d.mouserotationstyle``.
11-
See also :doc:`/api/toolkits/mplot3d/view_angles`.
11+
See also :ref:`toolkit_mouse-rotation`.
12+
13+
To revert to the original mouse rotation style,
14+
create a file ``matplotlibrc`` with contents::
15+
16+
axes3d.mouserotationstyle: azel
17+
18+
To try out one of the various mouse rotation styles:
19+
20+
.. code::
21+
22+
import matplotlib as mpl
23+
mpl.rcParams['axes3d.mouserotationstyle'] = 'trackball' # 'azel', 'trackball', 'arcball', 'Shoemake', or 'Holroyd'
24+
25+
import numpy as np
26+
import matplotlib.pyplot as plt
27+
from matplotlib import cm
28+
29+
ax = plt.figure().add_subplot(projection='3d')
30+
31+
X = np.arange(-5, 5, 0.25)
32+
Y = np.arange(-5, 5, 0.25)
33+
X, Y = np.meshgrid(X, Y)
34+
R = np.sqrt(X**2 + Y**2)
35+
Z = np.sin(R)
36+
37+
surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm,
38+
linewidth=0, antialiased=False)
39+
40+
plt.show()
41+
1242
1343
.. [1] Ken Shoemake, "ARCBALL: A user interface for specifying
1444
three-dimensional rotation using a mouse", in Proceedings of Graphics

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 18 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,10 +1508,11 @@ def _calc_coord(self, xv, yv, renderer=None):
15081508
p2 = p1 - scale*vec
15091509
return p2, pane_idx
15101510

1511-
def _arcball(self, x: float, y: float, Holroyd: bool) -> np.ndarray:
1511+
def _arcball(self, x: float, y: float, style: str) -> np.ndarray:
15121512
"""
15131513
Convert a point (x, y) to a point on a virtual trackball
1514-
This is Ken Shoemake's arcball
1514+
either Ken Shoemake's arcball (a sphere) or
1515+
Tom Holroyd's (a sphere combined with a hyperbola).
15151516
See: Ken Shoemake, "ARCBALL: A user interface for specifying
15161517
three-dimensional rotation using a mouse." in
15171518
Proceedings of Graphics Interface '92, 1992, pp. 151-156,
@@ -1521,12 +1522,12 @@ def _arcball(self, x: float, y: float, Holroyd: bool) -> np.ndarray:
15211522
x /= s
15221523
y /= s
15231524
r2 = x*x + y*y
1524-
if Holroyd:
1525+
if style == 'Holroyd':
15251526
if r2 > 0.5:
15261527
p = np.array([1/(2*math.sqrt(r2)), x, y])/math.sqrt(1/(4*r2)+r2)
15271528
else:
15281529
p = np.array([math.sqrt(1-r2), x, y])
1529-
else: # Shoemake
1530+
else: # 'arcball', 'Shoemake'
15301531
if r2 > 1:
15311532
p = np.array([0, x/math.sqrt(r2), y/math.sqrt(r2)])
15321533
else:
@@ -1577,38 +1578,24 @@ def _on_move(self, event):
15771578
azim = self.azim + dazim
15781579
roll = self.roll
15791580
else:
1580-
# Convert to quaternion
1581-
elev = np.deg2rad(self.elev)
1582-
azim = np.deg2rad(self.azim)
1583-
roll = np.deg2rad(self.roll)
1584-
q = _Quaternion.from_cardan_angles(elev, azim, roll)
1581+
q = _Quaternion.from_cardan_angles(
1582+
*np.deg2rad((self.elev, self.azim, self.roll)))
15851583

1586-
if style in ['arcball', 'Shoemake', 'Holroyd']:
1587-
# Update quaternion
1588-
is_Holroyd = (style == 'Holroyd')
1589-
current_vec = self._arcball(self._sx/w, self._sy/h, is_Holroyd)
1590-
new_vec = self._arcball(x/w, y/h, is_Holroyd)
1584+
if style == 'trackball':
1585+
k = np.array([0, -dy/h, dx/w])
1586+
nk = np.linalg.norm(k)
1587+
th = nk / mpl.rcParams['axes3d.trackballsize']
1588+
dq = _Quaternion(np.cos(th), k*np.sin(th)/nk)
1589+
else: # 'arcball', 'Shoemake', 'Holroyd'
1590+
current_vec = self._arcball(self._sx/w, self._sy/h, style)
1591+
new_vec = self._arcball(x/w, y/h, style)
15911592
if style == 'arcball':
15921593
dq = _Quaternion.rotate_from_to(current_vec, new_vec)
15931594
else: # 'Shoemake', 'Holroyd'
15941595
dq = _Quaternion(0, new_vec) * _Quaternion(0, -current_vec)
1595-
q = dq * q
1596-
elif style == 'trackball':
1597-
s = mpl.rcParams['axes3d.trackballsize'] / 2
1598-
k = np.array([0, -(y-self._sy)/h, (x-self._sx)/w]) / s
1599-
nk = np.linalg.norm(k)
1600-
th = nk / 2
1601-
dq = _Quaternion(math.cos(th), k*math.sin(th)/nk)
1602-
q = dq * q
1603-
else:
1604-
warnings.warn("Mouse rotation style (axes3d.mouserotationstyle: " +
1605-
style + ") not recognized.")
1606-
1607-
# Convert to elev, azim, roll
1608-
elev, azim, roll = q.as_cardan_angles()
1609-
elev = np.rad2deg(elev)
1610-
azim = np.rad2deg(azim)
1611-
roll = np.rad2deg(roll)
1596+
1597+
q = dq * q
1598+
elev, azim, roll = np.rad2deg(q.as_cardan_angles())
16121599

16131600
# update view
16141601
vertical_axis = self._axis_names[self._vertical_axis]

0 commit comments

Comments
 (0)