diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 0b6cc83216d0..cd0eea6fa132 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -273,22 +273,16 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): """ Set the aspect ratios. - Axes 3D does not current support any aspect but 'auto' which fills - the Axes with the data limits. - - To simulate having equal aspect in data space, set the ratio - of your data limits to match the value of `.get_box_aspect`. - To control box aspect ratios use `~.Axes3D.set_box_aspect`. - Parameters ---------- - aspect : {'auto'} + aspect : {'auto', 'equal'} Possible values: ========= ================================================== value description ========= ================================================== 'auto' automatic; fill the position rectangle with data. + 'equal' automatic; adapt the axes to have equal aspect ========= ================================================== adjustable : None @@ -322,14 +316,27 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): -------- mpl_toolkits.mplot3d.axes3d.Axes3D.set_box_aspect """ - if aspect != 'auto': + if aspect not in ['auto', 'equal']: raise NotImplementedError( "Axes3D currently only supports the aspect argument " - f"'auto'. You passed in {aspect!r}." + f"'auto' or 'equal'. You passed in {aspect!r}." ) super().set_aspect( aspect, adjustable=adjustable, anchor=anchor, share=share) + if aspect == 'equal': + self.set_box_aspect((1., 1., 1.)) + v_intervals = np.vstack((self.xaxis.get_view_interval(), + self.yaxis.get_view_interval(), + self.zaxis.get_view_interval())).T + mean = np.mean(v_intervals, axis=0) + delta = np.max(np.max(v_intervals, axis=0) - + np.min(v_intervals, axis=0)) + + self.set_xlim3d(mean[0] - delta / 2., mean[0] + delta / 2.) + self.set_ylim3d(mean[1] - delta / 2., mean[1] + delta / 2.) + self.set_zlim3d(mean[2] - delta / 2., mean[2] + delta / 2.) + def set_box_aspect(self, aspect, *, zoom=1): """ Set the Axes box aspect. diff --git a/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/aspect_equal.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/aspect_equal.png new file mode 100644 index 000000000000..e8a07fcf5c27 Binary files /dev/null and b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/aspect_equal.png differ diff --git a/lib/mpl_toolkits/tests/test_mplot3d.py b/lib/mpl_toolkits/tests/test_mplot3d.py index 543816f9b9dd..56ab4ab48c52 100644 --- a/lib/mpl_toolkits/tests/test_mplot3d.py +++ b/lib/mpl_toolkits/tests/test_mplot3d.py @@ -21,11 +21,27 @@ image_comparison, remove_text=True, style='default') -def test_aspect_equal_error(): +@mpl3d_image_comparison(['aspect_equal.png'], remove_text=False) +def test_aspect_equal(): fig = plt.figure() ax = fig.add_subplot(projection='3d') - with pytest.raises(NotImplementedError): - ax.set_aspect('equal') + + points = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0], + [0, 0, 3], [1, 0, 3], [0, 1, 3], [1, 1, 3]]) + + x = np.asarray([coord[0] for coord in points]) + y = np.asarray([coord[1] for coord in points]) + z = np.asarray([coord[2] for coord in points]) + + def plot_edge(i, j): + ax.plot([x[i], x[j]], [y[i], y[j]], [z[i], z[j]], c='r') + + # hexaedron creation + plot_edge(0, 1), plot_edge(1, 3), plot_edge(3, 2), plot_edge(2, 0) + plot_edge(4, 5), plot_edge(5, 7), plot_edge(7, 6), plot_edge(6, 4) + plot_edge(0, 4), plot_edge(1, 5), plot_edge(3, 7), plot_edge(2, 6) + + ax.set_aspect('equal') @mpl3d_image_comparison(['bar3d.png'])