From e59721eb67ccf6fb8656127335d7b4d5e1f80741 Mon Sep 17 00:00:00 2001 From: patquem Date: Fri, 25 Mar 2022 11:22:45 +0100 Subject: [PATCH 1/5] Provide axis('equal') for Axes3D Whats new for 3D plot equal aspect ratio Code review updates set_aspect('equal') now adopts current box aspect Update test image Update whats new Update whats new test image --- doc/users/next_whats_new/3d_plot_aspects.rst | 28 ++++++++++++++++++ lib/mpl_toolkits/mplot3d/axes3d.py | 26 +++++++++------- .../baseline_images/test_mplot3d/aspects.png | Bin 0 -> 37033 bytes lib/mpl_toolkits/tests/test_mplot3d.py | 22 ++++++++++++-- 4 files changed, 63 insertions(+), 13 deletions(-) create mode 100644 doc/users/next_whats_new/3d_plot_aspects.rst create mode 100644 lib/mpl_toolkits/tests/baseline_images/test_mplot3d/aspects.png diff --git a/doc/users/next_whats_new/3d_plot_aspects.rst b/doc/users/next_whats_new/3d_plot_aspects.rst new file mode 100644 index 000000000000..689fdaf741b5 --- /dev/null +++ b/doc/users/next_whats_new/3d_plot_aspects.rst @@ -0,0 +1,28 @@ +Set equal aspect ratio for 3D plots +----------------------------------- + +Users can set the aspect ratio for the X, Y, Z axes of a 3D plot to be 'equal', +'equalxy', 'equalxz', or 'equalyz' rather than the default of 'auto'. + +.. plot:: + :include-source: true + + aspects = ('auto', 'equal', 'equalxy', 'equalyz', 'equalxz') + fig, axs = plt.subplots(1, len(aspects), subplot_kw={'projection': '3d'}) + + # Draw rectangular cuboid with side lengths [1, 1, 2] + r = [0, 1] + scale = np.array([1, 1, 2]) + pts = itertools.combinations(np.array(list(itertools.product(r, r, r))), 2) + for start, end in pts: + if np.sum(np.abs(start - end)) == r[1] - r[0]: + for ax in axs: + ax.plot3D(*zip(start*scale, end*scale), color='C0') + + # Set the aspect ratios + for i, ax in enumerate(axs): + ax.set_box_aspect((3, 4, 5)) + ax.set_aspect(aspects[i]) + ax.title(f"set_aspect('{aspects[i]}')") + + plt.show() diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index d50ad5235ccd..d317681040e4 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -272,22 +272,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' adapt the axes to have equal aspect ratios. ========= ================================================== adjustable : None @@ -321,14 +315,26 @@ 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': + v_intervals = np.vstack((self.xaxis.get_view_interval(), + self.yaxis.get_view_interval(), + self.zaxis.get_view_interval())) + mean = np.mean(v_intervals, axis=1) + delta = np.max(np.ptp(v_intervals, axis=1)) + deltas = delta * self._box_aspect / min(self._box_aspect) + + self.set_xlim3d(mean[0] - deltas[0] / 2., mean[0] + deltas[0] / 2.) + self.set_ylim3d(mean[1] - deltas[1] / 2., mean[1] + deltas[1] / 2.) + self.set_zlim3d(mean[2] - deltas[2] / 2., mean[2] + deltas[2] / 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/aspects.png b/lib/mpl_toolkits/tests/baseline_images/test_mplot3d/aspects.png new file mode 100644 index 0000000000000000000000000000000000000000..3bb088e2d131a04afcadbe5ed50be42d3d38c1b3 GIT binary patch literal 37033 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

gp`chqe##e`?=5)4!42kMs>TXfwvvCv4f# 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

#cz2M=6Q20a9@*yFSr?mJ2L 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 Date: Thu, 14 Jul 2022 13:41:58 -0500 Subject: [PATCH 2/5] Add equalxy, equalyz, equalxz aspect ratios Update docstrings --- doc/users/next_whats_new/3d_plot_aspects.rst | 8 +++- lib/mpl_toolkits/mplot3d/axes3d.py | 47 ++++++++++++-------- lib/mpl_toolkits/tests/test_mplot3d.py | 37 +++++++-------- 3 files changed, 51 insertions(+), 41 deletions(-) diff --git a/doc/users/next_whats_new/3d_plot_aspects.rst b/doc/users/next_whats_new/3d_plot_aspects.rst index 689fdaf741b5..03aa427fca97 100644 --- a/doc/users/next_whats_new/3d_plot_aspects.rst +++ b/doc/users/next_whats_new/3d_plot_aspects.rst @@ -7,13 +7,17 @@ Users can set the aspect ratio for the X, Y, Z axes of a 3D plot to be 'equal', .. plot:: :include-source: true + import matplotlib.pyplot as plt + import numpy as np + from itertools import combinations, product + aspects = ('auto', 'equal', 'equalxy', 'equalyz', 'equalxz') fig, axs = plt.subplots(1, len(aspects), subplot_kw={'projection': '3d'}) # Draw rectangular cuboid with side lengths [1, 1, 2] r = [0, 1] scale = np.array([1, 1, 2]) - pts = itertools.combinations(np.array(list(itertools.product(r, r, r))), 2) + pts = combinations(np.array(list(product(r, r, r))), 2) for start, end in pts: if np.sum(np.abs(start - end)) == r[1] - r[0]: for ax in axs: @@ -23,6 +27,6 @@ Users can set the aspect ratio for the X, Y, Z axes of a 3D plot to be 'equal', for i, ax in enumerate(axs): ax.set_box_aspect((3, 4, 5)) ax.set_aspect(aspects[i]) - ax.title(f"set_aspect('{aspects[i]}')") + ax.set_title("set_aspect('{aspects[i]}')") plt.show() diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index d317681040e4..7e91afc85876 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -274,14 +274,17 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): Parameters ---------- - aspect : {'auto', 'equal'} + aspect : {'auto', 'equal', 'equalxy', 'equalxz', 'equalyz'} Possible values: ========= ================================================== value description ========= ================================================== 'auto' automatic; fill the position rectangle with data. - 'equal' adapt the axes to have equal aspect ratios. + 'equal' adapt all the axes to have equal aspect ratios. + 'equalxy' adapt the x and y axes to have equal aspect ratios. + 'equalxz' adapt the x and z axes to have equal aspect ratios. + 'equalyz' adapt the y and z axes to have equal aspect ratios. ========= ================================================== adjustable : None @@ -315,25 +318,33 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): -------- mpl_toolkits.mplot3d.axes3d.Axes3D.set_box_aspect """ - if aspect not in ('auto', 'equal'): - raise NotImplementedError( - "Axes3D currently only supports the aspect argument " - f"'auto' or 'equal'. You passed in {aspect!r}." - ) + _api.check_in_list(('auto', 'equal', 'equalxy', 'equalyz', 'equalxz'), + aspect=aspect) super().set_aspect( - aspect, adjustable=adjustable, anchor=anchor, share=share) - - if aspect == 'equal': - v_intervals = np.vstack((self.xaxis.get_view_interval(), - self.yaxis.get_view_interval(), - self.zaxis.get_view_interval())) - mean = np.mean(v_intervals, axis=1) - delta = np.max(np.ptp(v_intervals, axis=1)) + aspect='auto', adjustable=adjustable, anchor=anchor, share=share) + + if aspect in ('equal', 'equalxy', 'equalxz', 'equalyz'): + if aspect == 'equal': + axis_indices = [0, 1, 2] + elif aspect == 'equalxy': + axis_indices = [0, 1] + elif aspect == 'equalxz': + axis_indices = [0, 2] + elif aspect == 'equalyz': + axis_indices = [1, 2] + + view_intervals = np.array([self.xaxis.get_view_interval(), + self.yaxis.get_view_interval(), + self.zaxis.get_view_interval()]) + mean = np.mean(view_intervals, axis=1) + delta = np.max(np.ptp(view_intervals, axis=1)) deltas = delta * self._box_aspect / min(self._box_aspect) - self.set_xlim3d(mean[0] - deltas[0] / 2., mean[0] + deltas[0] / 2.) - self.set_ylim3d(mean[1] - deltas[1] / 2., mean[1] + deltas[1] / 2.) - self.set_zlim3d(mean[2] - deltas[2] / 2., mean[2] + deltas[2] / 2.) + for i, set_lim in enumerate((self.set_xlim3d, + self.set_ylim3d, + self.set_zlim3d)): + if i in axis_indices: + set_lim(mean[i] - deltas[i]/2., mean[i] + deltas[i]/2.) def set_box_aspect(self, aspect, *, zoom=1): """ diff --git a/lib/mpl_toolkits/tests/test_mplot3d.py b/lib/mpl_toolkits/tests/test_mplot3d.py index e396844984b6..e6a232064a73 100644 --- a/lib/mpl_toolkits/tests/test_mplot3d.py +++ b/lib/mpl_toolkits/tests/test_mplot3d.py @@ -27,27 +27,22 @@ def test_invisible_axes(fig_test, fig_ref): ax.set_visible(False) -@mpl3d_image_comparison(['aspect_equal.png'], remove_text=False) -def test_aspect_equal(): - fig = plt.figure() - ax = fig.add_subplot(projection='3d') - - 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(['aspects.png'], remove_text=False) +def test_aspects(): + aspects = ('auto', 'equal', 'equalxy', 'equalyz', 'equalxz') + fig, axs = plt.subplots(1, len(aspects), subplot_kw={'projection': '3d'}) + + # Draw rectangular cuboid with side lengths [1, 1, 2] + r = [0, 1] + scale = np.array([1, 1, 2]) + pts = itertools.combinations(np.array(list(itertools.product(r, r, r))), 2) + for start, end in pts: + if np.sum(np.abs(start - end)) == r[1] - r[0]: + for ax in axs: + ax.plot3D(*zip(start*scale, end*scale)) + for i, ax in enumerate(axs): + ax.set_box_aspect((3, 4, 5)) + ax.set_aspect(aspects[i]) @mpl3d_image_comparison(['bar3d.png']) From da0e6070bba6e0821fae9220fd3ba462777f6fca Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Thu, 21 Jul 2022 19:04:28 -0600 Subject: [PATCH 3/5] Use all available plot space --- doc/users/next_whats_new/3d_plot_aspects.rst | 4 ++-- lib/mpl_toolkits/mplot3d/axes3d.py | 16 +++++++++------- lib/mpl_toolkits/tests/test_mplot3d.py | 4 ++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/doc/users/next_whats_new/3d_plot_aspects.rst b/doc/users/next_whats_new/3d_plot_aspects.rst index 03aa427fca97..ebd7afae92ef 100644 --- a/doc/users/next_whats_new/3d_plot_aspects.rst +++ b/doc/users/next_whats_new/3d_plot_aspects.rst @@ -14,9 +14,9 @@ Users can set the aspect ratio for the X, Y, Z axes of a 3D plot to be 'equal', aspects = ('auto', 'equal', 'equalxy', 'equalyz', 'equalxz') fig, axs = plt.subplots(1, len(aspects), subplot_kw={'projection': '3d'}) - # Draw rectangular cuboid with side lengths [1, 1, 2] + # Draw rectangular cuboid with side lengths [1, 1, 5] r = [0, 1] - scale = np.array([1, 1, 2]) + scale = np.array([1, 1, 5]) pts = combinations(np.array(list(product(r, r, r))), 2) for start, end in pts: if np.sum(np.abs(start - end)) == r[1] - r[0]: diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 7e91afc85876..a0ebecd332ab 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -325,25 +325,27 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): if aspect in ('equal', 'equalxy', 'equalxz', 'equalyz'): if aspect == 'equal': - axis_indices = [0, 1, 2] + ax_indices = [0, 1, 2] elif aspect == 'equalxy': - axis_indices = [0, 1] + ax_indices = [0, 1] elif aspect == 'equalxz': - axis_indices = [0, 2] + ax_indices = [0, 2] elif aspect == 'equalyz': - axis_indices = [1, 2] + ax_indices = [1, 2] view_intervals = np.array([self.xaxis.get_view_interval(), self.yaxis.get_view_interval(), self.zaxis.get_view_interval()]) mean = np.mean(view_intervals, axis=1) - delta = np.max(np.ptp(view_intervals, axis=1)) - deltas = delta * self._box_aspect / min(self._box_aspect) + ptp = np.ptp(view_intervals, axis=1) + delta = max(ptp[ax_indices]) + scale = self._box_aspect[ptp == delta][0] + deltas = delta * self._box_aspect / scale for i, set_lim in enumerate((self.set_xlim3d, self.set_ylim3d, self.set_zlim3d)): - if i in axis_indices: + if i in ax_indices: set_lim(mean[i] - deltas[i]/2., mean[i] + deltas[i]/2.) def set_box_aspect(self, aspect, *, zoom=1): diff --git a/lib/mpl_toolkits/tests/test_mplot3d.py b/lib/mpl_toolkits/tests/test_mplot3d.py index e6a232064a73..91fee3dcfe38 100644 --- a/lib/mpl_toolkits/tests/test_mplot3d.py +++ b/lib/mpl_toolkits/tests/test_mplot3d.py @@ -32,9 +32,9 @@ def test_aspects(): aspects = ('auto', 'equal', 'equalxy', 'equalyz', 'equalxz') fig, axs = plt.subplots(1, len(aspects), subplot_kw={'projection': '3d'}) - # Draw rectangular cuboid with side lengths [1, 1, 2] + # Draw rectangular cuboid with side lengths [1, 1, 5] r = [0, 1] - scale = np.array([1, 1, 2]) + scale = np.array([1, 1, 5]) pts = itertools.combinations(np.array(list(itertools.product(r, r, r))), 2) for start, end in pts: if np.sum(np.abs(start - end)) == r[1] - r[0]: From 7141d2c5ad846065d54614fdab4d4aaadcc35d4f Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Thu, 21 Jul 2022 19:50:10 -0600 Subject: [PATCH 4/5] Update relevant gallery images --- examples/mplot3d/surface3d_2.py | 3 +++ examples/mplot3d/voxels_numpy_logo.py | 1 + examples/mplot3d/voxels_rgb.py | 1 + 3 files changed, 5 insertions(+) diff --git a/examples/mplot3d/surface3d_2.py b/examples/mplot3d/surface3d_2.py index d5cfca1905a3..bca9f1ca62e8 100644 --- a/examples/mplot3d/surface3d_2.py +++ b/examples/mplot3d/surface3d_2.py @@ -23,4 +23,7 @@ # Plot the surface ax.plot_surface(x, y, z) +# Set an equal aspect ratio +ax.set_aspect('equal') + plt.show() diff --git a/examples/mplot3d/voxels_numpy_logo.py b/examples/mplot3d/voxels_numpy_logo.py index 9aa72b31e290..8b790d073988 100644 --- a/examples/mplot3d/voxels_numpy_logo.py +++ b/examples/mplot3d/voxels_numpy_logo.py @@ -42,5 +42,6 @@ def explode(data): ax = plt.figure().add_subplot(projection='3d') ax.voxels(x, y, z, filled_2, facecolors=fcolors_2, edgecolors=ecolors_2) +ax.set_aspect('equal') plt.show() diff --git a/examples/mplot3d/voxels_rgb.py b/examples/mplot3d/voxels_rgb.py index a06cc10a5cc1..31bfcbca15a9 100644 --- a/examples/mplot3d/voxels_rgb.py +++ b/examples/mplot3d/voxels_rgb.py @@ -39,5 +39,6 @@ def midpoints(x): edgecolors=np.clip(2*colors - 0.5, 0, 1), # brighter linewidth=0.5) ax.set(xlabel='r', ylabel='g', zlabel='b') +ax.set_aspect('equal') plt.show() From 0bb940ef0b0296765048fc0de49b1759c4f5bdef Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Tue, 2 Aug 2022 21:17:35 -0600 Subject: [PATCH 5/5] Save aspect to axes --- lib/mpl_toolkits/mplot3d/axes3d.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index a0ebecd332ab..5319e38ff011 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -322,6 +322,7 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): aspect=aspect) super().set_aspect( aspect='auto', adjustable=adjustable, anchor=anchor, share=share) + self._aspect = aspect if aspect in ('equal', 'equalxy', 'equalxz', 'equalyz'): if aspect == 'equal': pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy