From f315cac278f0696fb28f205792ef666d6cb00fac Mon Sep 17 00:00:00 2001 From: MischaMegens2 <122418839+MischaMegens2@users.noreply.github.com> Date: Fri, 8 Nov 2024 09:53:11 -0800 Subject: [PATCH] Add documentation for view_init() angles - Clarify the relation between the view_init() angles and the rotation of the scene, and add recommendation to use keyword arguments - Change axes.view_init() arguments to keywords if the arguments are numbers - Label angles as elev, azim, or roll as appropriate, when it is not evident already - Avoid unnecessary argument repetition (elev=elev, azim=azim, roll=roll) - Stick with the original order: elev, azim, roll, in view_init() keyword arguments --- galleries/examples/mplot3d/2dcollections3d.py | 2 +- galleries/examples/mplot3d/box3d.py | 2 +- lib/mpl_toolkits/mplot3d/axes3d.py | 48 +++++++++++++++---- lib/mpl_toolkits/mplot3d/tests/test_art3d.py | 2 +- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 12 ++--- 5 files changed, 47 insertions(+), 19 deletions(-) diff --git a/galleries/examples/mplot3d/2dcollections3d.py b/galleries/examples/mplot3d/2dcollections3d.py index ae88776d133e..6229a87d6b67 100644 --- a/galleries/examples/mplot3d/2dcollections3d.py +++ b/galleries/examples/mplot3d/2dcollections3d.py @@ -43,7 +43,7 @@ # Customize the view angle so it's easier to see that the scatter points lie # on the plane y=0 -ax.view_init(elev=20., azim=-35, roll=0) +ax.view_init(elev=20, azim=-35, roll=0) plt.show() diff --git a/galleries/examples/mplot3d/box3d.py b/galleries/examples/mplot3d/box3d.py index 807e3d496ec6..3343c9038261 100644 --- a/galleries/examples/mplot3d/box3d.py +++ b/galleries/examples/mplot3d/box3d.py @@ -68,7 +68,7 @@ ) # Set zoom and angle view -ax.view_init(40, -30, 0) +ax.view_init(elev=40, azim=-30, roll=0) ax.set_box_aspect(None, zoom=0.9) # Colorbar diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index d0ba360c314b..73d933519e1f 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1095,13 +1095,12 @@ def clabel(self, *args, **kwargs): def view_init(self, elev=None, azim=None, roll=None, vertical_axis="z", share=False): """ - Set the elevation and azimuth of the Axes in degrees (not radians). + Set the elevation, azimuth, and roll of the Axes, in degrees (not radians). This can be used to rotate the Axes programmatically. To look normal to the primary planes, the following elevation and - azimuth angles can be used. A roll angle of 0, 90, 180, or 270 deg - will rotate these views while keeping the axes at right angles. + azimuth angles can be used: ========== ==== ==== view plane elev azim @@ -1114,6 +1113,40 @@ def view_init(self, elev=None, azim=None, roll=None, vertical_axis="z", -YZ 0 180 ========== ==== ==== + A roll angle of 0, 90, 180, or 270 degrees will rotate these views + while keeping the axes horizontal or vertical. + + The *azim*, *elev*, *roll* angles correspond to rotations of the scene + observed by a stationary camera, as follows (assuming a default vertical + axis of 'z'). First, a left-handed rotation about the z axis is applied + (*azim*), then a right-handed rotation about the (camera) y axis (*elev*), + then a right-handed rotation about the (camera) x axis (*roll*). Here, + the z, y, and x axis are fixed axes (not the axes that rotate together + with the original scene). + + If you would like to make the connection with quaternions (because + `Euler angles are horrible + `_): + the *azim*, *elev*, *roll* angles relate to the (intrinsic) rotation of + the plot via: + + *q* = exp(+roll **x̂** / 2) exp(+elev **ŷ** / 2) exp(−azim **ẑ** / 2) + + (with angles given in radians instead of degrees). That is, the angles + are a kind of `Tait-Bryan angles + `_: + −z, +y', +x", rather than classic `Euler angles + `_. + + To avoid confusion, provide the view angles as keyword + arguments: ``.view_init(elev=30, azim=-60, roll=0, ...)``. + This specific order is consistent with the order of positional arguments. + Unfortunately, this order does not match the order in which rotations are + applied, and it differs from the order used in other programs (``azim, elev``) + and in `matplotlib.colors.LightSource`. However, it cannot be + changed, since that would compromise backward compatibility. + + Parameters ---------- elev : float, default: None @@ -1606,13 +1639,8 @@ def _on_move(self, event): # update view vertical_axis = self._axis_names[self._vertical_axis] - self.view_init( - elev=elev, - azim=azim, - roll=roll, - vertical_axis=vertical_axis, - share=True, - ) + self.view_init(elev, azim, roll, vertical_axis=vertical_axis, + share=True) self.stale = True # Pan diff --git a/lib/mpl_toolkits/mplot3d/tests/test_art3d.py b/lib/mpl_toolkits/mplot3d/tests/test_art3d.py index f4f7067b76bb..b8a189e5705a 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_art3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_art3d.py @@ -10,9 +10,9 @@ def test_scatter_3d_projection_conservation(): fig = plt.figure() ax = fig.add_subplot(projection='3d') # fix axes3d projection - ax.roll = 0 ax.elev = 0 ax.azim = -45 + ax.roll = 0 ax.stale = True x = [0, 1, 2, 3, 4] diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index ad952e4395af..fbebb1d371b5 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -177,7 +177,7 @@ def test_bar3d_shaded(): ) for ax, (elev, azim, roll) in zip(axs, views): ax.bar3d(x2d, y2d, x2d * 0, 1, 1, z, shade=True) - ax.view_init(elev=elev, azim=azim, roll=roll) + ax.view_init(elev, azim, roll) fig.canvas.draw() @@ -708,7 +708,7 @@ def test_surface3d_masked(): norm = mcolors.Normalize(vmax=z.max(), vmin=z.min()) colors = mpl.colormaps["plasma"](norm(z)) ax.plot_surface(x, y, z, facecolors=colors) - ax.view_init(30, -80, 0) + ax.view_init(elev=30, azim=-80, roll=0) @check_figures_equal(extensions=["png"]) @@ -748,7 +748,7 @@ def test_surface3d_masked_strides(): z = np.ma.masked_less(x * y, 2) ax.plot_surface(x, y, z, rstride=4, cstride=4) - ax.view_init(60, -45, 0) + ax.view_init(elev=60, azim=-45, roll=0) @mpl3d_image_comparison(['text3d.png'], remove_text=False, style='mpl20') @@ -1160,7 +1160,7 @@ def test_axes3d_cla(): def test_axes3d_rotated(): fig = plt.figure() ax = fig.add_subplot(1, 1, 1, projection='3d') - ax.view_init(90, 45, 0) # look down, rotated. Should be square + ax.view_init(elev=90, azim=45, roll=0) # look down, rotated. Should be square def test_plotsurface_1d_raises(): @@ -1870,11 +1870,11 @@ def test_shared_view(fig_test, fig_ref): ax2 = fig_test.add_subplot(132, projection="3d", shareview=ax1) ax3 = fig_test.add_subplot(133, projection="3d") ax3.shareview(ax1) - ax2.view_init(elev=elev, azim=azim, roll=roll, share=True) + ax2.view_init(elev, azim, roll, share=True) for subplot_num in (131, 132, 133): ax = fig_ref.add_subplot(subplot_num, projection="3d") - ax.view_init(elev=elev, azim=azim, roll=roll) + ax.view_init(elev, azim, roll) def test_shared_axes_retick():