From 077ba10c5427354c6cbe456dd92617323f2c0d31 Mon Sep 17 00:00:00 2001 From: MischaMegens2 <122418839+MischaMegens2@users.noreply.github.com> Date: Wed, 10 Jul 2024 10:04:12 -0700 Subject: [PATCH 01/14] Make mplot3d mouse rotation style adjustable Addresses Issue #28408 - matplotlibrc: add axes3d.mouserotationstyle and axes3d.trackballsize - lib/matplotlib/rcsetup.py: add validation for axes3d.mouserotationstyle and axes3d.trackballsize - axes3d.py: implement various mouse rotation styles - update test_axes3d.py::test_rotate() - view_angles.rst: add documentation for the mouse rotation styles - update next_whats_new/mouse_rotation.rst --- doc/api/toolkits/mplot3d/view_angles.rst | 116 ++++++++++++++++++ doc/users/next_whats_new/mouse_rotation.rst | 11 +- lib/matplotlib/mpl-data/matplotlibrc | 4 + lib/matplotlib/rcsetup.py | 4 + lib/mpl_toolkits/mplot3d/axes3d.py | 84 +++++++++---- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 104 +++++++++++----- 6 files changed, 266 insertions(+), 57 deletions(-) diff --git a/doc/api/toolkits/mplot3d/view_angles.rst b/doc/api/toolkits/mplot3d/view_angles.rst index ce2c5f5698a5..377e1452911a 100644 --- a/doc/api/toolkits/mplot3d/view_angles.rst +++ b/doc/api/toolkits/mplot3d/view_angles.rst @@ -38,3 +38,119 @@ further documented in the `.mplot3d.axes3d.Axes3D.view_init` API. .. plot:: gallery/mplot3d/view_planes_3d.py :align: center + + +Rotation with mouse +=================== + +3D plots can be reoriented by dragging the mouse. +There are various ways to accomplish this; the style of mouse rotation +can be specified by setting ``rcParams.axes3d.mouserotationstyle``, see +:doc:`/users/explain/customizing`. + +Originally (with ``mouserotationstyle: azel``), the 2D mouse position +corresponded directly to azimuth and elevation; this is also how it is done +in `MATLAB `_. +This approach works fine for polar plots, where the *z* axis is special; +however, it leads to a kind of 'gimbal lock' when looking down the *z* axis: +the plot reacts differently to mouse movement, dependent on the particular +orientation at hand. Also, 'roll' cannot be controlled. + +As an alternative, there are various mouse rotation styles where the mouse +manipulates a 'trackball'. In its simplest form (``mouserotationstyle: trackball``), +the trackball rotates around an in-plane axis perpendicular to the mouse motion +(it is as if there is a plate laying on the trackball; the plate itself is fixed +in orientation, but you can drag the plate with the mouse, thus rotating the ball). +This is more natural to work with than the ``azel`` style; however, +the plot cannot be easily rotated around the viewing direction - one has to +drag the mouse in circles with a handedness opposite to the desired rotation. + +A different variety of trackball rotates along the shortest arc on the virtual +sphere (``mouserotationstyle: arcball``); it is a variation on Ken Shoemake's +ARCBALL [Shoemake1992]_. Rotating around the viewing direction is straightforward +with it. Shoemake's original arcball is also available +(``mouserotationstyle: Shoemake``); it is free of hysteresis, i.e., +returning mouse to the original position returns the figure to its original +orientation, the rotation is independent of the details of the path the mouse +took. However, Shoemake's arcball rotates at twice the angular rate of the +mouse movement (it is quite noticeable, especially when adjusting roll). +So it is a trade-off. + +Shoemake's arcball has an abrupt edge; this is remedied in Holroyd's arcball +(``mouserotationstyle: Holroyd``). + +Henriksen et al. [Henriksen2002]_ provide an overview. + +In summary: + +.. list-table:: + :width: 100% + :widths: 30 20 20 20 35 + + * - Style + - traditional [1]_ + - incl. roll [2]_ + - uniform [3]_ + - path independent [4]_ + * - azel + - ✔️ + - ❌ + - ❌ + - ✔️ + * - trackball + - ❌ + - ~ + - ✔️ + - ❌ + * - arcball + - ❌ + - ✔️ + - ✔️ + - ❌ + * - Shoemake + - ❌ + - ✔️ + - ✔️ + - ✔️ + * - Holroyd + - ❌ + - ✔️ + - ✔️ + - ✔️ + + +.. [1] The way it was historically; this is also MATLAB's style +.. [2] Mouse controls roll too (not only azimuth and elevation) +.. [3] Figure reacts the same way to mouse movements, regardless of orientation (no difference between 'poles' and 'equator') +.. [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) + +Try it out by adding a file ``matplotlibrc`` to folder ``matplotlib\galleries\examples\mplot3d``, +with contents:: + + axes3d.mouserotationstyle: arcball + +(or any of the other styles), and run a suitable example, e.g.:: + + python surfaced3d.py + +(If eternal compatibility with the horrors of the past is less of a consideration +for you, then it is likely that you would want to go with ``arcball``, ``Shoemake``, +or ``Holroyd``.) + +The size of the trackball or arcball can be adjusted by setting +``rcParams.axes3d.trackballsize``, in units of the Axes bounding box; +i.e., to make the trackball span the whole bounding box, set it to 1. +A size of ca. 2/3 appears to work reasonably well. + +---- + +.. [Shoemake1992] Ken Shoemake, "ARCBALL: A user interface for specifying + three-dimensional rotation using a mouse", in Proceedings of Graphics + Interface '92, 1992, pp. 151-156, https://doi.org/10.20380/GI1992.18 + +.. [Henriksen2002] Knud Henriksen, Jon Sporring, Kasper Hornbæk, + "Virtual Trackballs Revisited", in Proceedings of DSAGM'2002: + http://www.diku.dk/~kash/papers/DSAGM2002_henriksen.pdf; + and in IEEE Transactions on Visualization + and Computer Graphics, Volume 10, Issue 2, March-April 2004, pp. 206-216, + https://doi.org/10.1109/TVCG.2004.1260772 diff --git a/doc/users/next_whats_new/mouse_rotation.rst b/doc/users/next_whats_new/mouse_rotation.rst index 64fca63ec472..00198565c54e 100644 --- a/doc/users/next_whats_new/mouse_rotation.rst +++ b/doc/users/next_whats_new/mouse_rotation.rst @@ -4,9 +4,12 @@ Rotating 3d plots with the mouse Rotating three-dimensional plots with the mouse has been made more intuitive. The plot now reacts the same way to mouse movement, independent of the particular orientation at hand; and it is possible to control all 3 rotational -degrees of freedom (azimuth, elevation, and roll). It uses a variation on -Ken Shoemake's ARCBALL [Shoemake1992]_. +degrees of freedom (azimuth, elevation, and roll). By default, +it uses a variation on Ken Shoemake's ARCBALL [1]_. +The particular style of mouse rotation can be set via +``rcParams.axes3d.mouserotationstyle``. +See also :doc:`/api/toolkits/mplot3d/view_angles`. -.. [Shoemake1992] Ken Shoemake, "ARCBALL: A user interface for specifying - three-dimensional rotation using a mouse." in Proceedings of Graphics +.. [1] Ken Shoemake, "ARCBALL: A user interface for specifying + three-dimensional rotation using a mouse", in Proceedings of Graphics Interface '92, 1992, pp. 151-156, https://doi.org/10.20380/GI1992.18 diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index d419ed6e5af7..d56043d5581c 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -433,6 +433,10 @@ #axes3d.yaxis.panecolor: (0.90, 0.90, 0.90, 0.5) # background pane on 3D axes #axes3d.zaxis.panecolor: (0.925, 0.925, 0.925, 0.5) # background pane on 3D axes +#axes3d.mouserotationstyle: arcball # {azel, trackball, arcball, Shoemake, Holroyd} + # See also https://matplotlib.org/stable/api/toolkits/mplot3d/view_angles.html#rotation-with-mouse +#axes3d.trackballsize: 0.667 # trackball diameter, in units of the Axes bbox + ## *************************************************************************** ## * AXIS * ## *************************************************************************** diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index e84b0539385b..e9395207fb99 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -1132,6 +1132,10 @@ def _convert_validator_spec(key, conv): "axes3d.yaxis.panecolor": validate_color, # 3d background pane "axes3d.zaxis.panecolor": validate_color, # 3d background pane + "axes3d.mouserotationstyle": ["azel", "trackball", "arcball", + "Shoemake", "Holroyd"], + "axes3d.trackballsize": validate_float, + # scatter props "scatter.marker": _validate_marker, "scatter.edgecolors": validate_string, diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 5d522cd0988a..3d108420422e 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1508,7 +1508,7 @@ def _calc_coord(self, xv, yv, renderer=None): p2 = p1 - scale*vec return p2, pane_idx - def _arcball(self, x: float, y: float) -> np.ndarray: + def _arcball(self, x: float, y: float, Holroyd: bool) -> np.ndarray: """ Convert a point (x, y) to a point on a virtual trackball This is Ken Shoemake's arcball @@ -1517,13 +1517,20 @@ def _arcball(self, x: float, y: float) -> np.ndarray: Proceedings of Graphics Interface '92, 1992, pp. 151-156, https://doi.org/10.20380/GI1992.18 """ - x *= 2 - y *= 2 + s = mpl.rcParams['axes3d.trackballsize'] / 2 + x /= s + y /= s r2 = x*x + y*y - if r2 > 1: - p = np.array([0, x/math.sqrt(r2), y/math.sqrt(r2)]) - else: - p = np.array([math.sqrt(1-r2), x, y]) + if Holroyd: + if r2 > 0.5: + p = np.array([1/(2*math.sqrt(r2)), x, y])/math.sqrt(1/(4*r2)+r2) + else: + p = np.array([math.sqrt(1-r2), x, y]) + else: # Shoemake + if r2 > 1: + p = np.array([0, x/math.sqrt(r2), y/math.sqrt(r2)]) + else: + p = np.array([math.sqrt(1-r2), x, y]) return p def _on_move(self, event): @@ -1561,23 +1568,49 @@ def _on_move(self, event): if dx == 0 and dy == 0: return - # Convert to quaternion - elev = np.deg2rad(self.elev) - azim = np.deg2rad(self.azim) - roll = np.deg2rad(self.roll) - q = _Quaternion.from_cardan_angles(elev, azim, roll) - - # Update quaternion - a variation on Ken Shoemake's ARCBALL - current_vec = self._arcball(self._sx/w, self._sy/h) - new_vec = self._arcball(x/w, y/h) - dq = _Quaternion.rotate_from_to(current_vec, new_vec) - q = dq * q - - # Convert to elev, azim, roll - elev, azim, roll = q.as_cardan_angles() - azim = np.rad2deg(azim) - elev = np.rad2deg(elev) - roll = np.rad2deg(roll) + style = mpl.rcParams['axes3d.mouserotationstyle'] + if style == 'azel': + roll = np.deg2rad(self.roll) + delev = -(dy/h)*180*np.cos(roll) + (dx/w)*180*np.sin(roll) + dazim = -(dy/h)*180*np.sin(roll) - (dx/w)*180*np.cos(roll) + elev = self.elev + delev + azim = self.azim + dazim + roll = self.roll + else: + # Convert to quaternion + elev = np.deg2rad(self.elev) + azim = np.deg2rad(self.azim) + roll = np.deg2rad(self.roll) + q = _Quaternion.from_cardan_angles(elev, azim, roll) + + if style in ['arcball', 'Shoemake', 'Holroyd']: + # Update quaternion + is_Holroyd = (style == 'Holroyd') + current_vec = self._arcball(self._sx/w, self._sy/h, is_Holroyd) + new_vec = self._arcball(x/w, y/h, is_Holroyd) + if style == 'arcball': + dq = _Quaternion.rotate_from_to(current_vec, new_vec) + else: # 'Shoemake', 'Holroyd' + dq = _Quaternion(0, new_vec) * _Quaternion(0, -current_vec) + q = dq * q + elif style == 'trackball': + s = mpl.rcParams['axes3d.trackballsize'] / 2 + k = np.array([0, -(y-self._sy)/h, (x-self._sx)/w]) / s + nk = np.linalg.norm(k) + th = nk / 2 + dq = _Quaternion(math.cos(th), k*math.sin(th)/nk) + q = dq * q + else: + warnings.warn("Mouse rotation style (axes3d.mouserotationstyle: " + + style + ") not recognized.") + + # Convert to elev, azim, roll + elev, azim, roll = q.as_cardan_angles() + elev = np.rad2deg(elev) + azim = np.rad2deg(azim) + roll = np.rad2deg(roll) + + # update view vertical_axis = self._axis_names[self._vertical_axis] self.view_init( elev=elev, @@ -3984,7 +4017,7 @@ def rotate_from_to(cls, r1, r2): k = np.cross(r1, r2) nk = np.linalg.norm(k) th = np.arctan2(nk, np.dot(r1, r2)) - th = th/2 + th /= 2 if nk == 0: # r1 and r2 are parallel or anti-parallel if np.dot(r1, r2) < 0: warnings.warn("Rotation defined by anti-parallel vectors is ambiguous") @@ -4021,6 +4054,7 @@ def as_cardan_angles(self): """ The inverse of `from_cardan_angles()`. Note that the angles returned are in radians, not degrees. + The angles are not sensitive to the quaternion's norm(). """ qw = self.scalar qx, qy, qz = self.vector[..., :] diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 0afcae99c980..02a58eadff1a 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -1939,37 +1939,85 @@ def test_quaternion(): np.deg2rad(elev), np.deg2rad(azim), np.deg2rad(roll)) assert np.isclose(q.norm, 1) q = Quaternion(mag * q.scalar, mag * q.vector) - e, a, r = np.rad2deg(Quaternion.as_cardan_angles(q)) - assert np.isclose(e, elev) - assert np.isclose(a, azim) - assert np.isclose(r, roll) + np.testing.assert_allclose(np.rad2deg(Quaternion.as_cardan_angles(q)), + (elev, azim, roll), atol=1e-6) -def test_rotate(): +@pytest.mark.parametrize('style', + ('azel', 'trackball', 'arcball', 'Shoemake', 'Holroyd')) +def test_rotate(style): """Test rotating using the left mouse button.""" - for roll, dx, dy, new_elev, new_azim, new_roll in [ - [0, 0.5, 0, 0, -90, 0], - [30, 0.5, 0, 30, -90, 0], - [0, 0, 0.5, -90, 0, 0], - [30, 0, 0.5, -60, -90, 90], - [0, 0.5, 0.5, -45, -90, 45], - [30, 0.5, 0.5, -15, -90, 45]]: - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1, projection='3d') - ax.view_init(0, 0, roll) - fig.canvas.draw() - - # drag mouse to change orientation - ax._button_press( - mock_event(ax, button=MouseButton.LEFT, xdata=0, ydata=0)) - ax._on_move( - mock_event(ax, button=MouseButton.LEFT, - xdata=dx*ax._pseudo_w, ydata=dy*ax._pseudo_h)) - fig.canvas.draw() - - assert np.isclose(ax.elev, new_elev) - assert np.isclose(ax.azim, new_azim) - assert np.isclose(ax.roll, new_roll) + if style == 'azel': + s = 0.5 + else: + s = mpl.rcParams['axes3d.trackballsize'] / 2 + s *= 0.5 + with mpl.rc_context({'axes3d.mouserotationstyle': style}): + for roll, dx, dy in [ + [0, 1, 0], + [30, 1, 0], + [0, 0, 1], + [30, 0, 1], + [0, 0.5, np.sqrt(3)/2], + [30, 0.5, np.sqrt(3)/2], + [0, 2, 0]]: + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1, projection='3d') + ax.view_init(0, 0, roll) + ax.figure.canvas.draw() + + # drag mouse to change orientation + ax._button_press( + mock_event(ax, button=MouseButton.LEFT, xdata=0, ydata=0)) + ax._on_move( + mock_event(ax, button=MouseButton.LEFT, + xdata=s*dx*ax._pseudo_w, ydata=s*dy*ax._pseudo_h)) + ax.figure.canvas.draw() + + c = np.sqrt(3)/2 + expectations = { + ('azel', 0, 1, 0): (0, -45, 0), + ('azel', 0, 0, 1): (-45, 0, 0), + ('azel', 0, 0.5, c): (-38.971143, -22.5, 0), + ('azel', 0, 2, 0): (0, -90, 0), + ('azel', 30, 1, 0): (22.5, -38.971143, 30), + ('azel', 30, 0, 1): (-38.971143, -22.5, 30), + ('azel', 30, 0.5, c): (-22.5, -38.971143, 30), + + ('trackball', 0, 1, 0): (0, -28.64789, 0), + ('trackball', 0, 0, 1): (-28.64789, 0, 0), + ('trackball', 0, 0.5, c): (-24.531578, -15.277726, 3.340403), + ('trackball', 0, 2, 0): (0, -180/np.pi, 0), + ('trackball', 30, 1, 0): (13.869588, -25.319385, 26.87008), + ('trackball', 30, 0, 1): (-24.531578, -15.277726, 33.340403), + ('trackball', 30, 0.5, c): (-13.869588, -25.319385, 33.129920), + + ('arcball', 0, 1, 0): (0, -30, 0), + ('arcball', 0, 0, 1): (-30, 0, 0), + ('arcball', 0, 0.5, c): (-25.658906, -16.102114, 3.690068), + ('arcball', 0, 2, 0): (0, -90, 0), + ('arcball', 30, 1, 0): (14.477512, -26.565051, 26.565051), + ('arcball', 30, 0, 1): (-25.658906, -16.102114, 33.690068), + ('arcball', 30, 0.5, c): (-14.477512, -26.565051, 33.434949), + + ('Shoemake', 0, 1, 0): (0, -60, 0), + ('Shoemake', 0, 0, 1): (-60, 0, 0), + ('Shoemake', 0, 0.5, c): (-48.590378, -40.893395, 19.106605), + ('Shoemake', 0, 2, 0): (0, 180, 0), + ('Shoemake', 30, 1, 0): (25.658906, -56.309932, 16.102114), + ('Shoemake', 30, 0, 1): (-48.590378, -40.893395, 49.106605), + ('Shoemake', 30, 0.5, c): (-25.658906, -56.309932, 43.897886), + + ('Holroyd', 0, 1, 0): (0, -60, 0), + ('Holroyd', 0, 0, 1): (-60, 0, 0), + ('Holroyd', 0, 0.5, c): (-48.590378, -40.893395, 19.106605), + ('Holroyd', 0, 2, 0): (0, -126.869898, 0), + ('Holroyd', 30, 1, 0): (25.658906, -56.309932, 16.102114), + ('Holroyd', 30, 0, 1): (-48.590378, -40.893395, 49.106605), + ('Holroyd', 30, 0.5, c): (-25.658906, -56.309932, 43.897886)} + new_elev, new_azim, new_roll = expectations[(style, roll, dx, dy)] + np.testing.assert_allclose((ax.elev, ax.azim, ax.roll), + (new_elev, new_azim, new_roll), atol=1e-6) def test_pan(): From 133c916b83a0354c5a2279c698110c981217295e Mon Sep 17 00:00:00 2001 From: MischaMegens2 <122418839+MischaMegens2@users.noreply.github.com> Date: Fri, 27 Sep 2024 01:25:11 -0700 Subject: [PATCH 02/14] Implement review suggestions --- doc/api/toolkits/mplot3d/view_angles.rst | 99 ++++++++++++++------- doc/users/next_whats_new/mouse_rotation.rst | 32 ++++++- lib/mpl_toolkits/mplot3d/axes3d.py | 49 ++++------ 3 files changed, 117 insertions(+), 63 deletions(-) diff --git a/doc/api/toolkits/mplot3d/view_angles.rst b/doc/api/toolkits/mplot3d/view_angles.rst index 377e1452911a..6ddb757f44d3 100644 --- a/doc/api/toolkits/mplot3d/view_angles.rst +++ b/doc/api/toolkits/mplot3d/view_angles.rst @@ -40,6 +40,8 @@ further documented in the `.mplot3d.axes3d.Axes3D.view_init` API. :align: center +.. _toolkit_mouse-rotation: + Rotation with mouse =================== @@ -48,99 +50,132 @@ There are various ways to accomplish this; the style of mouse rotation can be specified by setting ``rcParams.axes3d.mouserotationstyle``, see :doc:`/users/explain/customizing`. -Originally (with ``mouserotationstyle: azel``), the 2D mouse position -corresponded directly to azimuth and elevation; this is also how it is done +Originally (prior to v3.10), the 2D mouse position corresponded directly +to azimuth and elevation; this is also how it is done in `MATLAB `_. +To keep it this way, set ``mouserotationstyle: azel``. This approach works fine for polar plots, where the *z* axis is special; however, it leads to a kind of 'gimbal lock' when looking down the *z* axis: the plot reacts differently to mouse movement, dependent on the particular orientation at hand. Also, 'roll' cannot be controlled. As an alternative, there are various mouse rotation styles where the mouse -manipulates a 'trackball'. In its simplest form (``mouserotationstyle: trackball``), +manipulates a virtual 'trackball'. In its simplest form (``mouserotationstyle: trackball``), the trackball rotates around an in-plane axis perpendicular to the mouse motion (it is as if there is a plate laying on the trackball; the plate itself is fixed in orientation, but you can drag the plate with the mouse, thus rotating the ball). This is more natural to work with than the ``azel`` style; however, the plot cannot be easily rotated around the viewing direction - one has to -drag the mouse in circles with a handedness opposite to the desired rotation. +move the mouse in circles with a handedness opposite to the desired rotation, +counterintuitively. A different variety of trackball rotates along the shortest arc on the virtual sphere (``mouserotationstyle: arcball``); it is a variation on Ken Shoemake's ARCBALL [Shoemake1992]_. Rotating around the viewing direction is straightforward -with it. Shoemake's original arcball is also available -(``mouserotationstyle: Shoemake``); it is free of hysteresis, i.e., -returning mouse to the original position returns the figure to its original -orientation, the rotation is independent of the details of the path the mouse -took. However, Shoemake's arcball rotates at twice the angular rate of the -mouse movement (it is quite noticeable, especially when adjusting roll). +with it (grab the ball near its edge instead of near the center). + +Shoemake's original arcball is also available (``mouserotationstyle: Shoemake``); +it is free of hysteresis, i.e., returning mouse to the original position +returns the figure to its original orientation, the rotation is independent +of the details of the path the mouse took, which could be desirable. +However, Shoemake's arcball rotates at twice the angular rate of the +mouse movement (it is quite noticeable, especially when adjusting roll), +and it lacks an obvious mechanical equivalent; arguably, the path-independent rotation is unnatural. So it is a trade-off. Shoemake's arcball has an abrupt edge; this is remedied in Holroyd's arcball (``mouserotationstyle: Holroyd``). -Henriksen et al. [Henriksen2002]_ provide an overview. - -In summary: +Henriksen et al. [Henriksen2002]_ provide an overview. In summary: .. list-table:: :width: 100% - :widths: 30 20 20 20 35 + :widths: 30 20 20 20 20 35 * - Style - traditional [1]_ - incl. roll [2]_ - uniform [3]_ - path independent [4]_ + - mechanical counterpart [5]_ * - azel - ✔️ - ❌ - ❌ - ✔️ + - ✔️ * - trackball - ❌ - - ~ + - ✓ [6]_ - ✔️ - ❌ + - ✔️ * - arcball - ❌ - ✔️ - ✔️ - ❌ + - ✔️ * - Shoemake - ❌ - ✔️ - ✔️ - ✔️ + - ❌ * - Holroyd - ❌ - ✔️ - ✔️ - ✔️ + - ❌ -.. [1] The way it was historically; this is also MATLAB's style +.. [1] The way it was prior to v3.10; this is also MATLAB's style .. [2] Mouse controls roll too (not only azimuth and elevation) .. [3] Figure reacts the same way to mouse movements, regardless of orientation (no difference between 'poles' and 'equator') .. [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) +.. [5] The style has a corresponding natural implementation as a mechanical device +.. [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 -Try it out by adding a file ``matplotlibrc`` to folder ``matplotlib\galleries\examples\mplot3d``, -with contents:: +You can try out one of the various mouse rotation styles using:: - axes3d.mouserotationstyle: arcball +.. code:: + + import matplotlib as mpl + mpl.rcParams['axes3d.mouserotationstyle'] = 'trackball' # 'azel', 'trackball', 'arcball', 'Shoemake', or 'Holroyd' -(or any of the other styles), and run a suitable example, e.g.:: + import numpy as np + import matplotlib.pyplot as plt + from matplotlib import cm + + ax = plt.figure().add_subplot(projection='3d') + + X = np.arange(-5, 5, 0.25) + Y = np.arange(-5, 5, 0.25) + X, Y = np.meshgrid(X, Y) + R = np.sqrt(X**2 + Y**2) + Z = np.sin(R) + + surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm, + linewidth=0, antialiased=False) - python surfaced3d.py + plt.show() -(If eternal compatibility with the horrors of the past is less of a consideration -for you, then it is likely that you would want to go with ``arcball``, ``Shoemake``, -or ``Holroyd``.) +Alternatively, create a file ``matplotlibrc``, with contents:: -The size of the trackball or arcball can be adjusted by setting -``rcParams.axes3d.trackballsize``, in units of the Axes bounding box; + axes3d.mouserotationstyle: arcball + +(or any of the other styles, instead of ``arcball``), and then run any of +the :ref:`mplot3d-examples-index` examples. + +The size of the virtual trackball or arcball can be adjusted as well, +by setting ``rcParams.axes3d.trackballsize``. This specifies how much +mouse motion is needed to obtain a given rotation angle (when near the center), +and it controls where the edge of the arcball is (how far from the center, +how close to the plot edge). +The size is specified in units of the Axes bounding box, i.e., to make the trackball span the whole bounding box, set it to 1. -A size of ca. 2/3 appears to work reasonably well. +A size of about 2/3 appears to work reasonably well; this is the default. ---- @@ -149,8 +184,10 @@ A size of ca. 2/3 appears to work reasonably well. Interface '92, 1992, pp. 151-156, https://doi.org/10.20380/GI1992.18 .. [Henriksen2002] Knud Henriksen, Jon Sporring, Kasper Hornbæk, - "Virtual Trackballs Revisited", in Proceedings of DSAGM'2002: - http://www.diku.dk/~kash/papers/DSAGM2002_henriksen.pdf; - and in IEEE Transactions on Visualization - and Computer Graphics, Volume 10, Issue 2, March-April 2004, pp. 206-216, + "Virtual Trackballs Revisited", in Proceedings of DSAGM'2002 + `[pdf]`__; + and in IEEE Transactions on Visualization and Computer Graphics, + Volume 10, Issue 2, March-April 2004, pp. 206-216, https://doi.org/10.1109/TVCG.2004.1260772 + +__ https://web.archive.org/web/20240607102518/http://hjemmesider.diku.dk/~kash/papers/DSAGM2002_henriksen.pdf diff --git a/doc/users/next_whats_new/mouse_rotation.rst b/doc/users/next_whats_new/mouse_rotation.rst index 00198565c54e..4d8257ff1182 100644 --- a/doc/users/next_whats_new/mouse_rotation.rst +++ b/doc/users/next_whats_new/mouse_rotation.rst @@ -8,7 +8,37 @@ degrees of freedom (azimuth, elevation, and roll). By default, it uses a variation on Ken Shoemake's ARCBALL [1]_. The particular style of mouse rotation can be set via ``rcParams.axes3d.mouserotationstyle``. -See also :doc:`/api/toolkits/mplot3d/view_angles`. +See also :ref:`toolkit_mouse-rotation`. + +To revert to the original mouse rotation style, +create a file ``matplotlibrc`` with contents:: + + axes3d.mouserotationstyle: azel + +To try out one of the various mouse rotation styles: + +.. code:: + + import matplotlib as mpl + mpl.rcParams['axes3d.mouserotationstyle'] = 'trackball' # 'azel', 'trackball', 'arcball', 'Shoemake', or 'Holroyd' + + import numpy as np + import matplotlib.pyplot as plt + from matplotlib import cm + + ax = plt.figure().add_subplot(projection='3d') + + X = np.arange(-5, 5, 0.25) + Y = np.arange(-5, 5, 0.25) + X, Y = np.meshgrid(X, Y) + R = np.sqrt(X**2 + Y**2) + Z = np.sin(R) + + surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm, + linewidth=0, antialiased=False) + + plt.show() + .. [1] Ken Shoemake, "ARCBALL: A user interface for specifying three-dimensional rotation using a mouse", in Proceedings of Graphics diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 3d108420422e..c3c9c6f88156 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1508,10 +1508,11 @@ def _calc_coord(self, xv, yv, renderer=None): p2 = p1 - scale*vec return p2, pane_idx - def _arcball(self, x: float, y: float, Holroyd: bool) -> np.ndarray: + def _arcball(self, x: float, y: float, style: str) -> np.ndarray: """ Convert a point (x, y) to a point on a virtual trackball - This is Ken Shoemake's arcball + either Ken Shoemake's arcball (a sphere) or + Tom Holroyd's (a sphere combined with a hyperbola). See: Ken Shoemake, "ARCBALL: A user interface for specifying three-dimensional rotation using a mouse." in Proceedings of Graphics Interface '92, 1992, pp. 151-156, @@ -1521,12 +1522,12 @@ def _arcball(self, x: float, y: float, Holroyd: bool) -> np.ndarray: x /= s y /= s r2 = x*x + y*y - if Holroyd: + if style == 'Holroyd': if r2 > 0.5: p = np.array([1/(2*math.sqrt(r2)), x, y])/math.sqrt(1/(4*r2)+r2) else: p = np.array([math.sqrt(1-r2), x, y]) - else: # Shoemake + else: # 'arcball', 'Shoemake' if r2 > 1: p = np.array([0, x/math.sqrt(r2), y/math.sqrt(r2)]) else: @@ -1577,38 +1578,24 @@ def _on_move(self, event): azim = self.azim + dazim roll = self.roll else: - # Convert to quaternion - elev = np.deg2rad(self.elev) - azim = np.deg2rad(self.azim) - roll = np.deg2rad(self.roll) - q = _Quaternion.from_cardan_angles(elev, azim, roll) + q = _Quaternion.from_cardan_angles( + *np.deg2rad((self.elev, self.azim, self.roll))) - if style in ['arcball', 'Shoemake', 'Holroyd']: - # Update quaternion - is_Holroyd = (style == 'Holroyd') - current_vec = self._arcball(self._sx/w, self._sy/h, is_Holroyd) - new_vec = self._arcball(x/w, y/h, is_Holroyd) + if style == 'trackball': + k = np.array([0, -dy/h, dx/w]) + nk = np.linalg.norm(k) + th = nk / mpl.rcParams['axes3d.trackballsize'] + dq = _Quaternion(np.cos(th), k*np.sin(th)/nk) + else: # 'arcball', 'Shoemake', 'Holroyd' + current_vec = self._arcball(self._sx/w, self._sy/h, style) + new_vec = self._arcball(x/w, y/h, style) if style == 'arcball': dq = _Quaternion.rotate_from_to(current_vec, new_vec) else: # 'Shoemake', 'Holroyd' dq = _Quaternion(0, new_vec) * _Quaternion(0, -current_vec) - q = dq * q - elif style == 'trackball': - s = mpl.rcParams['axes3d.trackballsize'] / 2 - k = np.array([0, -(y-self._sy)/h, (x-self._sx)/w]) / s - nk = np.linalg.norm(k) - th = nk / 2 - dq = _Quaternion(math.cos(th), k*math.sin(th)/nk) - q = dq * q - else: - warnings.warn("Mouse rotation style (axes3d.mouserotationstyle: " + - style + ") not recognized.") - - # Convert to elev, azim, roll - elev, azim, roll = q.as_cardan_angles() - elev = np.rad2deg(elev) - azim = np.rad2deg(azim) - roll = np.rad2deg(roll) + + q = dq * q + elev, azim, roll = np.rad2deg(q.as_cardan_angles()) # update view vertical_axis = self._axis_names[self._vertical_axis] From 6ffebd8e3a9ccea5fc104b86166dd4c926d556d1 Mon Sep 17 00:00:00 2001 From: MischaMegens2 <122418839+MischaMegens2@users.noreply.github.com> Date: Mon, 30 Sep 2024 22:22:52 -0700 Subject: [PATCH 03/14] Update view_angles.rst: polar plot -> spherical coordinate plot --- doc/api/toolkits/mplot3d/view_angles.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/toolkits/mplot3d/view_angles.rst b/doc/api/toolkits/mplot3d/view_angles.rst index 6ddb757f44d3..d5ea17bc7f27 100644 --- a/doc/api/toolkits/mplot3d/view_angles.rst +++ b/doc/api/toolkits/mplot3d/view_angles.rst @@ -54,7 +54,7 @@ Originally (prior to v3.10), the 2D mouse position corresponded directly to azimuth and elevation; this is also how it is done in `MATLAB `_. To keep it this way, set ``mouserotationstyle: azel``. -This approach works fine for polar plots, where the *z* axis is special; +This approach works fine for spherical coordinate plots, where the *z* axis is special; however, it leads to a kind of 'gimbal lock' when looking down the *z* axis: the plot reacts differently to mouse movement, dependent on the particular orientation at hand. Also, 'roll' cannot be controlled. From d35e0cf33e578488ac05535ce99363c7ec19ba65 Mon Sep 17 00:00:00 2001 From: MischaMegens2 <122418839+MischaMegens2@users.noreply.github.com> Date: Mon, 30 Sep 2024 22:25:30 -0700 Subject: [PATCH 04/14] Update lib/mpl_toolkits/mplot3d/tests/test_axes3d.py Co-authored-by: Elliott Sales de Andrade --- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 02a58eadff1a..0a5c0f116e8a 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -1971,7 +1971,7 @@ def test_rotate(style): mock_event(ax, button=MouseButton.LEFT, xdata=0, ydata=0)) ax._on_move( mock_event(ax, button=MouseButton.LEFT, - xdata=s*dx*ax._pseudo_w, ydata=s*dy*ax._pseudo_h)) + xdata=s*dx*ax._pseudo_w, ydata=s*dy*ax._pseudo_h)) ax.figure.canvas.draw() c = np.sqrt(3)/2 From 9421ad06ce4d3e95cb779be2f0f840e30b502c23 Mon Sep 17 00:00:00 2001 From: MischaMegens2 <122418839+MischaMegens2@users.noreply.github.com> Date: Mon, 30 Sep 2024 22:28:22 -0700 Subject: [PATCH 05/14] Update doc/api/toolkits/mplot3d/view_angles.rst Co-authored-by: Elliott Sales de Andrade --- doc/api/toolkits/mplot3d/view_angles.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/toolkits/mplot3d/view_angles.rst b/doc/api/toolkits/mplot3d/view_angles.rst index d5ea17bc7f27..85eceac32b85 100644 --- a/doc/api/toolkits/mplot3d/view_angles.rst +++ b/doc/api/toolkits/mplot3d/view_angles.rst @@ -50,7 +50,7 @@ There are various ways to accomplish this; the style of mouse rotation can be specified by setting ``rcParams.axes3d.mouserotationstyle``, see :doc:`/users/explain/customizing`. -Originally (prior to v3.10), the 2D mouse position corresponded directly +Prior to v3.10, the 2D mouse position corresponded directly to azimuth and elevation; this is also how it is done in `MATLAB `_. To keep it this way, set ``mouserotationstyle: azel``. From e7665b7296c7ea3b59becd6017333a242daebacf Mon Sep 17 00:00:00 2001 From: MischaMegens2 <122418839+MischaMegens2@users.noreply.github.com> Date: Mon, 30 Sep 2024 22:28:57 -0700 Subject: [PATCH 06/14] Update doc/users/next_whats_new/mouse_rotation.rst Co-authored-by: Elliott Sales de Andrade --- doc/users/next_whats_new/mouse_rotation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/users/next_whats_new/mouse_rotation.rst b/doc/users/next_whats_new/mouse_rotation.rst index 4d8257ff1182..5c1b1480c595 100644 --- a/doc/users/next_whats_new/mouse_rotation.rst +++ b/doc/users/next_whats_new/mouse_rotation.rst @@ -7,7 +7,7 @@ particular orientation at hand; and it is possible to control all 3 rotational degrees of freedom (azimuth, elevation, and roll). By default, it uses a variation on Ken Shoemake's ARCBALL [1]_. The particular style of mouse rotation can be set via -``rcParams.axes3d.mouserotationstyle``. +:rc:`axes3d.mouserotationstyle`. See also :ref:`toolkit_mouse-rotation`. To revert to the original mouse rotation style, From 686f0ca93ce918a6b50d0883ab2efc3fc86b630f Mon Sep 17 00:00:00 2001 From: MischaMegens2 <122418839+MischaMegens2@users.noreply.github.com> Date: Mon, 30 Sep 2024 23:09:11 -0700 Subject: [PATCH 07/14] Suggestions from the reviewer --- doc/api/toolkits/mplot3d/view_angles.rst | 4 ++-- lib/mpl_toolkits/mplot3d/axes3d.py | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/api/toolkits/mplot3d/view_angles.rst b/doc/api/toolkits/mplot3d/view_angles.rst index 85eceac32b85..b7ac360b499b 100644 --- a/doc/api/toolkits/mplot3d/view_angles.rst +++ b/doc/api/toolkits/mplot3d/view_angles.rst @@ -47,7 +47,7 @@ Rotation with mouse 3D plots can be reoriented by dragging the mouse. There are various ways to accomplish this; the style of mouse rotation -can be specified by setting ``rcParams.axes3d.mouserotationstyle``, see +can be specified by setting :rc:`axes3d.mouserotationstyle`, see :doc:`/users/explain/customizing`. Prior to v3.10, the 2D mouse position corresponded directly @@ -169,7 +169,7 @@ Alternatively, create a file ``matplotlibrc``, with contents:: the :ref:`mplot3d-examples-index` examples. The size of the virtual trackball or arcball can be adjusted as well, -by setting ``rcParams.axes3d.trackballsize``. This specifies how much +by setting :rc:`axes3d.trackballsize`. This specifies how much mouse motion is needed to obtain a given rotation angle (when near the center), and it controls where the edge of the arcball is (how far from the center, how close to the plot edge). diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index c3c9c6f88156..8a61e793f1d2 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1510,8 +1510,9 @@ def _calc_coord(self, xv, yv, renderer=None): def _arcball(self, x: float, y: float, style: str) -> np.ndarray: """ - Convert a point (x, y) to a point on a virtual trackball - either Ken Shoemake's arcball (a sphere) or + Convert a point (x, y) to a point on a virtual trackball. + + This is either Ken Shoemake's arcball (a sphere) or Tom Holroyd's (a sphere combined with a hyperbola). See: Ken Shoemake, "ARCBALL: A user interface for specifying three-dimensional rotation using a mouse." in From a293e31bea61607be481d8889ad84db2a88ca113 Mon Sep 17 00:00:00 2001 From: MischaMegens2 <122418839+MischaMegens2@users.noreply.github.com> Date: Fri, 4 Oct 2024 10:08:15 -0700 Subject: [PATCH 08/14] Replace Holroyd by Bell Gavin Bell appears to be the accurate reference/attribution, not Holroyd, replace it. --- doc/api/toolkits/mplot3d/view_angles.rst | 24 +++++++++++-------- doc/users/next_whats_new/mouse_rotation.rst | 2 +- lib/matplotlib/mpl-data/matplotlibrc | 2 +- lib/matplotlib/rcsetup.py | 2 +- lib/mpl_toolkits/mplot3d/axes3d.py | 8 +++---- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 16 ++++++------- 6 files changed, 29 insertions(+), 25 deletions(-) diff --git a/doc/api/toolkits/mplot3d/view_angles.rst b/doc/api/toolkits/mplot3d/view_angles.rst index b7ac360b499b..841cc8bb0163 100644 --- a/doc/api/toolkits/mplot3d/view_angles.rst +++ b/doc/api/toolkits/mplot3d/view_angles.rst @@ -83,8 +83,9 @@ mouse movement (it is quite noticeable, especially when adjusting roll), and it lacks an obvious mechanical equivalent; arguably, the path-independent rotation is unnatural. So it is a trade-off. -Shoemake's arcball has an abrupt edge; this is remedied in Holroyd's arcball -(``mouserotationstyle: Holroyd``). +Shoemake's arcball has an abrupt edge; this is remedied in Gavin Bell's arcball +(``mouserotationstyle: Bell``), originally written for OpenGL [Bell1988]_. It is used +in Blender and Meshlab. Henriksen et al. [Henriksen2002]_ provide an overview. In summary: @@ -122,7 +123,7 @@ Henriksen et al. [Henriksen2002]_ provide an overview. In summary: - ✔️ - ✔️ - ❌ - * - Holroyd + * - Bell - ❌ - ✔️ - ✔️ @@ -142,7 +143,7 @@ You can try out one of the various mouse rotation styles using:: .. code:: import matplotlib as mpl - mpl.rcParams['axes3d.mouserotationstyle'] = 'trackball' # 'azel', 'trackball', 'arcball', 'Shoemake', or 'Holroyd' + mpl.rcParams['axes3d.mouserotationstyle'] = 'trackball' # 'azel', 'trackball', 'arcball', 'Shoemake', or 'Bell' import numpy as np import matplotlib.pyplot as plt @@ -183,11 +184,14 @@ A size of about 2/3 appears to work reasonably well; this is the default. three-dimensional rotation using a mouse", in Proceedings of Graphics Interface '92, 1992, pp. 151-156, https://doi.org/10.20380/GI1992.18 + +.. [Bell1988] Gavin Bell, in the examples included with the GLUT (OpenGL + Utility Toolkit) library, + https://github.com/markkilgard/glut/blob/master/progs/examples/trackball.h + .. [Henriksen2002] Knud Henriksen, Jon Sporring, Kasper Hornbæk, - "Virtual Trackballs Revisited", in Proceedings of DSAGM'2002 - `[pdf]`__; - and in IEEE Transactions on Visualization and Computer Graphics, - Volume 10, Issue 2, March-April 2004, pp. 206-216, - https://doi.org/10.1109/TVCG.2004.1260772 + "Virtual Trackballs Revisited", in IEEE Transactions on Visualization + and Computer Graphics, Volume 10, Issue 2, March-April 2004, pp. 206-216, + https://doi.org/10.1109/TVCG.2004.1260772 `[full-text]`__; -__ https://web.archive.org/web/20240607102518/http://hjemmesider.diku.dk/~kash/papers/DSAGM2002_henriksen.pdf +__ https://www.researchgate.net/publication/8329656_Virtual_Trackballs_Revisited#fullTextFileContent diff --git a/doc/users/next_whats_new/mouse_rotation.rst b/doc/users/next_whats_new/mouse_rotation.rst index 5c1b1480c595..cdd8efb5494d 100644 --- a/doc/users/next_whats_new/mouse_rotation.rst +++ b/doc/users/next_whats_new/mouse_rotation.rst @@ -20,7 +20,7 @@ To try out one of the various mouse rotation styles: .. code:: import matplotlib as mpl - mpl.rcParams['axes3d.mouserotationstyle'] = 'trackball' # 'azel', 'trackball', 'arcball', 'Shoemake', or 'Holroyd' + mpl.rcParams['axes3d.mouserotationstyle'] = 'trackball' # 'azel', 'trackball', 'arcball', 'Shoemake', or 'Bell' import numpy as np import matplotlib.pyplot as plt diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index d56043d5581c..0818ba8694dc 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -433,7 +433,7 @@ #axes3d.yaxis.panecolor: (0.90, 0.90, 0.90, 0.5) # background pane on 3D axes #axes3d.zaxis.panecolor: (0.925, 0.925, 0.925, 0.5) # background pane on 3D axes -#axes3d.mouserotationstyle: arcball # {azel, trackball, arcball, Shoemake, Holroyd} +#axes3d.mouserotationstyle: arcball # {azel, trackball, arcball, Shoemake, Bell} # See also https://matplotlib.org/stable/api/toolkits/mplot3d/view_angles.html#rotation-with-mouse #axes3d.trackballsize: 0.667 # trackball diameter, in units of the Axes bbox diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index e9395207fb99..1cfa613df0e0 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -1133,7 +1133,7 @@ def _convert_validator_spec(key, conv): "axes3d.zaxis.panecolor": validate_color, # 3d background pane "axes3d.mouserotationstyle": ["azel", "trackball", "arcball", - "Shoemake", "Holroyd"], + "Shoemake", "Bell"], "axes3d.trackballsize": validate_float, # scatter props diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 8a61e793f1d2..a3071b32acf3 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1513,7 +1513,7 @@ def _arcball(self, x: float, y: float, style: str) -> np.ndarray: Convert a point (x, y) to a point on a virtual trackball. This is either Ken Shoemake's arcball (a sphere) or - Tom Holroyd's (a sphere combined with a hyperbola). + Gavin Bell's (a sphere combined with a hyperbola). See: Ken Shoemake, "ARCBALL: A user interface for specifying three-dimensional rotation using a mouse." in Proceedings of Graphics Interface '92, 1992, pp. 151-156, @@ -1523,7 +1523,7 @@ def _arcball(self, x: float, y: float, style: str) -> np.ndarray: x /= s y /= s r2 = x*x + y*y - if style == 'Holroyd': + if style == 'Bell': if r2 > 0.5: p = np.array([1/(2*math.sqrt(r2)), x, y])/math.sqrt(1/(4*r2)+r2) else: @@ -1587,12 +1587,12 @@ def _on_move(self, event): nk = np.linalg.norm(k) th = nk / mpl.rcParams['axes3d.trackballsize'] dq = _Quaternion(np.cos(th), k*np.sin(th)/nk) - else: # 'arcball', 'Shoemake', 'Holroyd' + else: # 'arcball', 'Shoemake', 'Bell' current_vec = self._arcball(self._sx/w, self._sy/h, style) new_vec = self._arcball(x/w, y/h, style) if style == 'arcball': dq = _Quaternion.rotate_from_to(current_vec, new_vec) - else: # 'Shoemake', 'Holroyd' + else: # 'Shoemake', 'Bell' dq = _Quaternion(0, new_vec) * _Quaternion(0, -current_vec) q = dq * q diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 0a5c0f116e8a..f88053e04e4d 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -1944,7 +1944,7 @@ def test_quaternion(): @pytest.mark.parametrize('style', - ('azel', 'trackball', 'arcball', 'Shoemake', 'Holroyd')) + ('azel', 'trackball', 'arcball', 'Shoemake', 'Bell')) def test_rotate(style): """Test rotating using the left mouse button.""" if style == 'azel': @@ -2008,13 +2008,13 @@ def test_rotate(style): ('Shoemake', 30, 0, 1): (-48.590378, -40.893395, 49.106605), ('Shoemake', 30, 0.5, c): (-25.658906, -56.309932, 43.897886), - ('Holroyd', 0, 1, 0): (0, -60, 0), - ('Holroyd', 0, 0, 1): (-60, 0, 0), - ('Holroyd', 0, 0.5, c): (-48.590378, -40.893395, 19.106605), - ('Holroyd', 0, 2, 0): (0, -126.869898, 0), - ('Holroyd', 30, 1, 0): (25.658906, -56.309932, 16.102114), - ('Holroyd', 30, 0, 1): (-48.590378, -40.893395, 49.106605), - ('Holroyd', 30, 0.5, c): (-25.658906, -56.309932, 43.897886)} + ('Bell', 0, 1, 0): (0, -60, 0), + ('Bell', 0, 0, 1): (-60, 0, 0), + ('Bell', 0, 0.5, c): (-48.590378, -40.893395, 19.106605), + ('Bell', 0, 2, 0): (0, -126.869898, 0), + ('Bell', 30, 1, 0): (25.658906, -56.309932, 16.102114), + ('Bell', 30, 0, 1): (-48.590378, -40.893395, 49.106605), + ('Bell', 30, 0.5, c): (-25.658906, -56.309932, 43.897886)} new_elev, new_azim, new_roll = expectations[(style, roll, dx, dy)] np.testing.assert_allclose((ax.elev, ax.azim, ax.roll), (new_elev, new_azim, new_roll), atol=1e-6) From 89701eb7ec502733b7a04aedcc55e656b4d84ef5 Mon Sep 17 00:00:00 2001 From: MischaMegens2 <122418839+MischaMegens2@users.noreply.github.com> Date: Wed, 9 Oct 2024 23:30:50 -0700 Subject: [PATCH 09/14] Soften the arcball edge using a 'border' parameter - revise _arcball() to soften the edge, according to border width parameter - drop _arcball() 'style' function parameter - add trackballborder rcParam - _arcball() 'border' case: normalize, so result vector is on unit sphere - math.sin/cos/sqrt -> np.sin/cos/sqrt: use numpy instead of math for consistency - remove 'Bell' style - rename arcball -> sphere, Shoemake -> arcball - update documentation - update test --- doc/api/toolkits/mplot3d/view_angles.rst | 62 ++++++++++--------- doc/users/next_whats_new/mouse_rotation.rst | 2 +- lib/matplotlib/mpl-data/matplotlibrc | 3 +- lib/matplotlib/rcsetup.py | 4 +- lib/mpl_toolkits/mplot3d/axes3d.py | 44 +++++++------ lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 41 +++++------- 6 files changed, 81 insertions(+), 75 deletions(-) diff --git a/doc/api/toolkits/mplot3d/view_angles.rst b/doc/api/toolkits/mplot3d/view_angles.rst index 841cc8bb0163..62d0c51c2d4a 100644 --- a/doc/api/toolkits/mplot3d/view_angles.rst +++ b/doc/api/toolkits/mplot3d/view_angles.rst @@ -70,23 +70,20 @@ move the mouse in circles with a handedness opposite to the desired rotation, counterintuitively. A different variety of trackball rotates along the shortest arc on the virtual -sphere (``mouserotationstyle: arcball``); it is a variation on Ken Shoemake's -ARCBALL [Shoemake1992]_. Rotating around the viewing direction is straightforward -with it (grab the ball near its edge instead of near the center). +sphere (``mouserotationstyle: sphere``). Rotating around the viewing direction +is straightforward with it: grab the ball near its edge instead of near the center. -Shoemake's original arcball is also available (``mouserotationstyle: Shoemake``); -it is free of hysteresis, i.e., returning mouse to the original position -returns the figure to its original orientation, the rotation is independent +Ken Shoemake's ARCBALL [Shoemake1992]_ is also available (``mouserotationstyle: Shoemake``); +it resembles the ``sphere`` style, but is free of hysteresis, +i.e., returning mouse to the original position +returns the figure to its original orientation; the rotation is independent of the details of the path the mouse took, which could be desirable. However, Shoemake's arcball rotates at twice the angular rate of the mouse movement (it is quite noticeable, especially when adjusting roll), -and it lacks an obvious mechanical equivalent; arguably, the path-independent rotation is unnatural. +and it lacks an obvious mechanical equivalent; arguably, the path-independent +rotation is not natural (however convenient), it could take some getting used to. So it is a trade-off. -Shoemake's arcball has an abrupt edge; this is remedied in Gavin Bell's arcball -(``mouserotationstyle: Bell``), originally written for OpenGL [Bell1988]_. It is used -in Blender and Meshlab. - Henriksen et al. [Henriksen2002]_ provide an overview. In summary: .. list-table:: @@ -111,19 +108,13 @@ Henriksen et al. [Henriksen2002]_ provide an overview. In summary: - ✔️ - ❌ - ✔️ - * - arcball + * - sphere - ❌ - ✔️ - ✔️ - ❌ - ✔️ - * - Shoemake - - ❌ - - ✔️ - - ✔️ - - ✔️ - - ❌ - * - Bell + * - arcball - ❌ - ✔️ - ✔️ @@ -134,16 +125,16 @@ Henriksen et al. [Henriksen2002]_ provide an overview. In summary: .. [1] The way it was prior to v3.10; this is also MATLAB's style .. [2] Mouse controls roll too (not only azimuth and elevation) .. [3] Figure reacts the same way to mouse movements, regardless of orientation (no difference between 'poles' and 'equator') -.. [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) +.. [4] Returning mouse to original position returns figure to original orientation (rotation is independent of the details of the path the mouse took) .. [5] The style has a corresponding natural implementation as a mechanical device -.. [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 +.. [6] While it is possible to control roll with the ``trackball`` style, this is not immediately obvious (it requires moving the mouse in large circles) and a bit counterintuitive (the resulting roll is in the opposite direction) You can try out one of the various mouse rotation styles using:: .. code:: import matplotlib as mpl - mpl.rcParams['axes3d.mouserotationstyle'] = 'trackball' # 'azel', 'trackball', 'arcball', 'Shoemake', or 'Bell' + mpl.rcParams['axes3d.mouserotationstyle'] = 'trackball' # 'azel', 'trackball', 'sphere', or 'arcball' import numpy as np import matplotlib.pyplot as plt @@ -169,22 +160,37 @@ Alternatively, create a file ``matplotlibrc``, with contents:: (or any of the other styles, instead of ``arcball``), and then run any of the :ref:`mplot3d-examples-index` examples. -The size of the virtual trackball or arcball can be adjusted as well, +The size of the virtual trackball, sphere, or arcball can be adjusted by setting :rc:`axes3d.trackballsize`. This specifies how much mouse motion is needed to obtain a given rotation angle (when near the center), -and it controls where the edge of the arcball is (how far from the center, -how close to the plot edge). +and it controls where the edge of the sphere or arcball is (how far from +the center, hence how close to the plot edge). The size is specified in units of the Axes bounding box, -i.e., to make the trackball span the whole bounding box, set it to 1. +i.e., to make the arcball span the whole bounding box, set it to 1. A size of about 2/3 appears to work reasonably well; this is the default. ----- +Both arcballs (``mouserotationstyle: sphere`` and +``mouserotationstyle: arcball``) have a noticeable edge; the edge can be made +less abrupt by specifying a border width, :rc:`axes3d.trackballborder`. +This works somewhat like Gavin Bell's arcball, which was +originally written for OpenGL [Bell1988]_, and is used in Blender and Meshlab. +Bell's arcball extends the arcball's spherical control surface with a hyperbola; +the two are smoothly joined. However, the hyperbola extends all the way beyond +the edge of the plot. In the mplot3d sphere and arcball style, the border extends +to a radius :rc:`axes3d.trackballsize`/2 + :rc:`axes3d.trackballborder`. +Beyond the border, the style works like the original: it controls roll only. +A border width of about 0.2 appears to work well; this is the default. +To obtain the original Shoemake's arcball with a sharp border, +set the border width to 0. +For an extended border similar to Bell's arcball, where the transition from +the arcball to the border occurs at 45°, set the border width to +$\sqrt 2 \approx 1.414$. + .. [Shoemake1992] Ken Shoemake, "ARCBALL: A user interface for specifying three-dimensional rotation using a mouse", in Proceedings of Graphics Interface '92, 1992, pp. 151-156, https://doi.org/10.20380/GI1992.18 - .. [Bell1988] Gavin Bell, in the examples included with the GLUT (OpenGL Utility Toolkit) library, https://github.com/markkilgard/glut/blob/master/progs/examples/trackball.h diff --git a/doc/users/next_whats_new/mouse_rotation.rst b/doc/users/next_whats_new/mouse_rotation.rst index cdd8efb5494d..c4eeab591da3 100644 --- a/doc/users/next_whats_new/mouse_rotation.rst +++ b/doc/users/next_whats_new/mouse_rotation.rst @@ -20,7 +20,7 @@ To try out one of the various mouse rotation styles: .. code:: import matplotlib as mpl - mpl.rcParams['axes3d.mouserotationstyle'] = 'trackball' # 'azel', 'trackball', 'arcball', 'Shoemake', or 'Bell' + mpl.rcParams['axes3d.mouserotationstyle'] = 'trackball' # 'azel', 'trackball', 'sphere', or 'arcball' import numpy as np import matplotlib.pyplot as plt diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index 0818ba8694dc..092b7a25a7d7 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -433,9 +433,10 @@ #axes3d.yaxis.panecolor: (0.90, 0.90, 0.90, 0.5) # background pane on 3D axes #axes3d.zaxis.panecolor: (0.925, 0.925, 0.925, 0.5) # background pane on 3D axes -#axes3d.mouserotationstyle: arcball # {azel, trackball, arcball, Shoemake, Bell} +#axes3d.mouserotationstyle: arcball # {azel, trackball, sphere, arcball} # See also https://matplotlib.org/stable/api/toolkits/mplot3d/view_angles.html#rotation-with-mouse #axes3d.trackballsize: 0.667 # trackball diameter, in units of the Axes bbox +#axes3d.trackballborder: 0.2 # trackball border width, in units of the Axes bbox (only for 'sphere' and 'arcball' style) ## *************************************************************************** ## * AXIS * diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 1cfa613df0e0..4f7c0f8a241d 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -1132,9 +1132,9 @@ def _convert_validator_spec(key, conv): "axes3d.yaxis.panecolor": validate_color, # 3d background pane "axes3d.zaxis.panecolor": validate_color, # 3d background pane - "axes3d.mouserotationstyle": ["azel", "trackball", "arcball", - "Shoemake", "Bell"], + "axes3d.mouserotationstyle": ["azel", "trackball", "sphere", "arcball"], "axes3d.trackballsize": validate_float, + "axes3d.trackballborder": validate_float, # scatter props "scatter.marker": _validate_marker, diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index a3071b32acf3..10f00fc3fbe0 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1508,31 +1508,37 @@ def _calc_coord(self, xv, yv, renderer=None): p2 = p1 - scale*vec return p2, pane_idx - def _arcball(self, x: float, y: float, style: str) -> np.ndarray: + def _arcball(self, x: float, y: float) -> np.ndarray: """ Convert a point (x, y) to a point on a virtual trackball. - This is either Ken Shoemake's arcball (a sphere) or - Gavin Bell's (a sphere combined with a hyperbola). + This is Ken Shoemake's arcball (a sphere), modified + to soften the abrupt edge (optionally). See: Ken Shoemake, "ARCBALL: A user interface for specifying three-dimensional rotation using a mouse." in Proceedings of Graphics Interface '92, 1992, pp. 151-156, https://doi.org/10.20380/GI1992.18 + The smoothing of the edge is inspired by Gavin Bell's arcball + (a sphere combined with a hyperbola), but here, the sphere + is combined with a section of a cylinder, so it has finite support. """ s = mpl.rcParams['axes3d.trackballsize'] / 2 + b = mpl.rcParams['axes3d.trackballborder'] / s x /= s y /= s r2 = x*x + y*y - if style == 'Bell': - if r2 > 0.5: - p = np.array([1/(2*math.sqrt(r2)), x, y])/math.sqrt(1/(4*r2)+r2) - else: - p = np.array([math.sqrt(1-r2), x, y]) - else: # 'arcball', 'Shoemake' - if r2 > 1: - p = np.array([0, x/math.sqrt(r2), y/math.sqrt(r2)]) - else: - p = np.array([math.sqrt(1-r2), x, y]) + r = np.sqrt(r2) + ra = 1 + b + a = b * (1 + b/2) + ri = 2/(ra + 1/ra) + if r < ri: + p = np.array([np.sqrt(1 - r2), x, y]) + elif r < ra: + dr = ra - r + p = np.array([a - np.sqrt((a + dr) * (a - dr)), x, y]) + p /= np.linalg.norm(p) + else: + p = np.array([0, x/r, y/r]) return p def _on_move(self, event): @@ -1587,12 +1593,12 @@ def _on_move(self, event): nk = np.linalg.norm(k) th = nk / mpl.rcParams['axes3d.trackballsize'] dq = _Quaternion(np.cos(th), k*np.sin(th)/nk) - else: # 'arcball', 'Shoemake', 'Bell' - current_vec = self._arcball(self._sx/w, self._sy/h, style) - new_vec = self._arcball(x/w, y/h, style) - if style == 'arcball': + else: # 'sphere', 'arcball' + current_vec = self._arcball(self._sx/w, self._sy/h) + new_vec = self._arcball(x/w, y/h) + if style == 'sphere': dq = _Quaternion.rotate_from_to(current_vec, new_vec) - else: # 'Shoemake', 'Bell' + else: # 'arcball' dq = _Quaternion(0, new_vec) * _Quaternion(0, -current_vec) q = dq * q @@ -4017,7 +4023,7 @@ def rotate_from_to(cls, r1, r2): else: q = cls(1, [0, 0, 0]) # = 1, no rotation else: - q = cls(math.cos(th), k*math.sin(th)/nk) + q = cls(np.cos(th), k*np.sin(th)/nk) return q @classmethod diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index f88053e04e4d..b8a2cf76394a 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -1944,7 +1944,7 @@ def test_quaternion(): @pytest.mark.parametrize('style', - ('azel', 'trackball', 'arcball', 'Shoemake', 'Bell')) + ('azel', 'trackball', 'sphere', 'arcball')) def test_rotate(style): """Test rotating using the left mouse button.""" if style == 'azel': @@ -1952,6 +1952,7 @@ def test_rotate(style): else: s = mpl.rcParams['axes3d.trackballsize'] / 2 s *= 0.5 + mpl.rcParams['axes3d.trackballborder'] = 0 with mpl.rc_context({'axes3d.mouserotationstyle': style}): for roll, dx, dy in [ [0, 1, 0], @@ -1992,29 +1993,21 @@ def test_rotate(style): ('trackball', 30, 0, 1): (-24.531578, -15.277726, 33.340403), ('trackball', 30, 0.5, c): (-13.869588, -25.319385, 33.129920), - ('arcball', 0, 1, 0): (0, -30, 0), - ('arcball', 0, 0, 1): (-30, 0, 0), - ('arcball', 0, 0.5, c): (-25.658906, -16.102114, 3.690068), - ('arcball', 0, 2, 0): (0, -90, 0), - ('arcball', 30, 1, 0): (14.477512, -26.565051, 26.565051), - ('arcball', 30, 0, 1): (-25.658906, -16.102114, 33.690068), - ('arcball', 30, 0.5, c): (-14.477512, -26.565051, 33.434949), - - ('Shoemake', 0, 1, 0): (0, -60, 0), - ('Shoemake', 0, 0, 1): (-60, 0, 0), - ('Shoemake', 0, 0.5, c): (-48.590378, -40.893395, 19.106605), - ('Shoemake', 0, 2, 0): (0, 180, 0), - ('Shoemake', 30, 1, 0): (25.658906, -56.309932, 16.102114), - ('Shoemake', 30, 0, 1): (-48.590378, -40.893395, 49.106605), - ('Shoemake', 30, 0.5, c): (-25.658906, -56.309932, 43.897886), - - ('Bell', 0, 1, 0): (0, -60, 0), - ('Bell', 0, 0, 1): (-60, 0, 0), - ('Bell', 0, 0.5, c): (-48.590378, -40.893395, 19.106605), - ('Bell', 0, 2, 0): (0, -126.869898, 0), - ('Bell', 30, 1, 0): (25.658906, -56.309932, 16.102114), - ('Bell', 30, 0, 1): (-48.590378, -40.893395, 49.106605), - ('Bell', 30, 0.5, c): (-25.658906, -56.309932, 43.897886)} + ('sphere', 0, 1, 0): (0, -30, 0), + ('sphere', 0, 0, 1): (-30, 0, 0), + ('sphere', 0, 0.5, c): (-25.658906, -16.102114, 3.690068), + ('sphere', 0, 2, 0): (0, -90, 0), + ('sphere', 30, 1, 0): (14.477512, -26.565051, 26.565051), + ('sphere', 30, 0, 1): (-25.658906, -16.102114, 33.690068), + ('sphere', 30, 0.5, c): (-14.477512, -26.565051, 33.434949), + + ('arcball', 0, 1, 0): (0, -60, 0), + ('arcball', 0, 0, 1): (-60, 0, 0), + ('arcball', 0, 0.5, c): (-48.590378, -40.893395, 19.106605), + ('arcball', 0, 2, 0): (0, 180, 0), + ('arcball', 30, 1, 0): (25.658906, -56.309932, 16.102114), + ('arcball', 30, 0, 1): (-48.590378, -40.893395, 49.106605), + ('arcball', 30, 0.5, c): (-25.658906, -56.309932, 43.897886)} new_elev, new_azim, new_roll = expectations[(style, roll, dx, dy)] np.testing.assert_allclose((ax.elev, ax.azim, ax.roll), (new_elev, new_azim, new_roll), atol=1e-6) From 6b156630a789afa31015701966c770b29fbe1841 Mon Sep 17 00:00:00 2001 From: MischaMegens2 <122418839+MischaMegens2@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:11:59 -0700 Subject: [PATCH 10/14] np.clip() to avoid floating point round-off errors Add np.clip() to avoid floating point round-off errors on the macos github CI runners, see also https://github.com/matplotlib/matplotlib/pull/28823#discussion_r1760498906 --- lib/mpl_toolkits/mplot3d/axes3d.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 10f00fc3fbe0..848299fe63dd 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -4053,6 +4053,6 @@ def as_cardan_angles(self): qw = self.scalar qx, qy, qz = self.vector[..., :] azim = np.arctan2(2*(-qw*qz+qx*qy), qw*qw+qx*qx-qy*qy-qz*qz) - elev = np.arcsin( 2*( qw*qy+qz*qx)/(qw*qw+qx*qx+qy*qy+qz*qz)) # noqa E201 - roll = np.arctan2(2*( qw*qx-qy*qz), qw*qw-qx*qx-qy*qy+qz*qz) # noqa E201 + elev = np.arcsin(np.clip(2*(qw*qy+qz*qx)/(qw*qw+qx*qx+qy*qy+qz*qz)), -1, 1) + roll = np.arctan2(2*(qw*qx-qy*qz), qw*qw-qx*qx-qy*qy+qz*qz) return elev, azim, roll From eaa51a47fa97f344c1aa2934559c889b895e48f7 Mon Sep 17 00:00:00 2001 From: MischaMegens2 <122418839+MischaMegens2@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:10:18 -0700 Subject: [PATCH 11/14] Add np.clip() to avoid floating point round-off errors With proper parentheses --- lib/mpl_toolkits/mplot3d/axes3d.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 848299fe63dd..f0e7346abee3 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -4053,6 +4053,6 @@ def as_cardan_angles(self): qw = self.scalar qx, qy, qz = self.vector[..., :] azim = np.arctan2(2*(-qw*qz+qx*qy), qw*qw+qx*qx-qy*qy-qz*qz) - elev = np.arcsin(np.clip(2*(qw*qy+qz*qx)/(qw*qw+qx*qx+qy*qy+qz*qz)), -1, 1) + elev = np.arcsin(np.clip(2*(qw*qy+qz*qx)/(qw*qw+qx*qx+qy*qy+qz*qz), -1, 1)) roll = np.arctan2(2*(qw*qx-qy*qz), qw*qw-qx*qx-qy*qy+qz*qz) return elev, azim, roll From b62ee9996b4e1c1a77dfc13aa0846ce55a525c43 Mon Sep 17 00:00:00 2001 From: MischaMegens2 <122418839+MischaMegens2@users.noreply.github.com> Date: Sat, 12 Oct 2024 22:38:27 -0700 Subject: [PATCH 12/14] Fix documentation: view_angles.rst, change default style Documentation (view_angles.rst): - Fix: only one of :: and .. code:: is needed - Fix: :rc:`axes3d.trackballsize`/2 + :rc:`axes3d.trackballborder` does not work as a formula, use `trackballsize/2 + trackballborder` instead -Fix: $\sqrt 2 \approx 1.414$ now reads :math:`\sqrt 2 \approx 1.414` matplotlibrc: axes3d.mouserotationstyle: trackball The style 'trackball' as default is likely to make life easier for 99% of the users who don't touch this setting (it is easiest to understand, no surprises, it works the same in the middle and at the edge of the screen, no guesswork as to how it works/what it does). --- doc/api/toolkits/mplot3d/view_angles.rst | 10 +++++----- lib/matplotlib/mpl-data/matplotlibrc | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/api/toolkits/mplot3d/view_angles.rst b/doc/api/toolkits/mplot3d/view_angles.rst index 62d0c51c2d4a..baad2b775980 100644 --- a/doc/api/toolkits/mplot3d/view_angles.rst +++ b/doc/api/toolkits/mplot3d/view_angles.rst @@ -129,7 +129,7 @@ Henriksen et al. [Henriksen2002]_ provide an overview. In summary: .. [5] The style has a corresponding natural implementation as a mechanical device .. [6] While it is possible to control roll with the ``trackball`` style, this is not immediately obvious (it requires moving the mouse in large circles) and a bit counterintuitive (the resulting roll is in the opposite direction) -You can try out one of the various mouse rotation styles using:: +You can try out one of the various mouse rotation styles using: .. code:: @@ -155,9 +155,9 @@ You can try out one of the various mouse rotation styles using:: Alternatively, create a file ``matplotlibrc``, with contents:: - axes3d.mouserotationstyle: arcball + axes3d.mouserotationstyle: trackball -(or any of the other styles, instead of ``arcball``), and then run any of +(or any of the other styles, instead of ``trackball``), and then run any of the :ref:`mplot3d-examples-index` examples. The size of the virtual trackball, sphere, or arcball can be adjusted @@ -177,14 +177,14 @@ originally written for OpenGL [Bell1988]_, and is used in Blender and Meshlab. Bell's arcball extends the arcball's spherical control surface with a hyperbola; the two are smoothly joined. However, the hyperbola extends all the way beyond the edge of the plot. In the mplot3d sphere and arcball style, the border extends -to a radius :rc:`axes3d.trackballsize`/2 + :rc:`axes3d.trackballborder`. +to a radius `trackballsize/2 + trackballborder`. Beyond the border, the style works like the original: it controls roll only. A border width of about 0.2 appears to work well; this is the default. To obtain the original Shoemake's arcball with a sharp border, set the border width to 0. For an extended border similar to Bell's arcball, where the transition from the arcball to the border occurs at 45°, set the border width to -$\sqrt 2 \approx 1.414$. +:math:`\sqrt 2 \approx 1.414`. .. [Shoemake1992] Ken Shoemake, "ARCBALL: A user interface for specifying diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index 092b7a25a7d7..a51b7ab3f6a8 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -433,7 +433,7 @@ #axes3d.yaxis.panecolor: (0.90, 0.90, 0.90, 0.5) # background pane on 3D axes #axes3d.zaxis.panecolor: (0.925, 0.925, 0.925, 0.5) # background pane on 3D axes -#axes3d.mouserotationstyle: arcball # {azel, trackball, sphere, arcball} +#axes3d.mouserotationstyle: trackball # {azel, trackball, sphere, arcball} # See also https://matplotlib.org/stable/api/toolkits/mplot3d/view_angles.html#rotation-with-mouse #axes3d.trackballsize: 0.667 # trackball diameter, in units of the Axes bbox #axes3d.trackballborder: 0.2 # trackball border width, in units of the Axes bbox (only for 'sphere' and 'arcball' style) From c71ab5852d76da612171e725d260ad0494d5e44e Mon Sep 17 00:00:00 2001 From: MischaMegens2 <122418839+MischaMegens2@users.noreply.github.com> Date: Sun, 13 Oct 2024 09:59:44 -0700 Subject: [PATCH 13/14] Revert to arcball style Change default mouse rotation style to arcball --- lib/matplotlib/mpl-data/matplotlibrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/mpl-data/matplotlibrc b/lib/matplotlib/mpl-data/matplotlibrc index a51b7ab3f6a8..092b7a25a7d7 100644 --- a/lib/matplotlib/mpl-data/matplotlibrc +++ b/lib/matplotlib/mpl-data/matplotlibrc @@ -433,7 +433,7 @@ #axes3d.yaxis.panecolor: (0.90, 0.90, 0.90, 0.5) # background pane on 3D axes #axes3d.zaxis.panecolor: (0.925, 0.925, 0.925, 0.5) # background pane on 3D axes -#axes3d.mouserotationstyle: trackball # {azel, trackball, sphere, arcball} +#axes3d.mouserotationstyle: arcball # {azel, trackball, sphere, arcball} # See also https://matplotlib.org/stable/api/toolkits/mplot3d/view_angles.html#rotation-with-mouse #axes3d.trackballsize: 0.667 # trackball diameter, in units of the Axes bbox #axes3d.trackballborder: 0.2 # trackball border width, in units of the Axes bbox (only for 'sphere' and 'arcball' style) From bcffb924359b6836a3093fc92ecad2b87279614a Mon Sep 17 00:00:00 2001 From: MischaMegens2 <122418839+MischaMegens2@users.noreply.github.com> Date: Mon, 14 Oct 2024 08:38:36 -0700 Subject: [PATCH 14/14] Fix doc build error in view_angles.rst; add description of border - single ` -> double `` - add description of the border (circular arc instead of hyperbola) --- doc/api/toolkits/mplot3d/view_angles.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/api/toolkits/mplot3d/view_angles.rst b/doc/api/toolkits/mplot3d/view_angles.rst index baad2b775980..75b24ba9c7b0 100644 --- a/doc/api/toolkits/mplot3d/view_angles.rst +++ b/doc/api/toolkits/mplot3d/view_angles.rst @@ -177,7 +177,7 @@ originally written for OpenGL [Bell1988]_, and is used in Blender and Meshlab. Bell's arcball extends the arcball's spherical control surface with a hyperbola; the two are smoothly joined. However, the hyperbola extends all the way beyond the edge of the plot. In the mplot3d sphere and arcball style, the border extends -to a radius `trackballsize/2 + trackballborder`. +to a radius ``trackballsize/2 + trackballborder``. Beyond the border, the style works like the original: it controls roll only. A border width of about 0.2 appears to work well; this is the default. To obtain the original Shoemake's arcball with a sharp border, @@ -185,6 +185,8 @@ set the border width to 0. For an extended border similar to Bell's arcball, where the transition from the arcball to the border occurs at 45°, set the border width to :math:`\sqrt 2 \approx 1.414`. +The border is a circular arc, wrapped around the arcball sphere cylindrically +(like a doughnut), joined smoothly to the sphere, much like Bell's hyperbola. .. [Shoemake1992] Ken Shoemake, "ARCBALL: A user interface for specifying