Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 75e605f

Browse filesBrowse files
committed
Provide adjustable='box' for set_aspect('equal') for 3D axes.
1 parent bf9a451 commit 75e605f
Copy full SHA for 75e605f

File tree

Expand file treeCollapse file tree

4 files changed

+76
-25
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+76
-25
lines changed

‎doc/users/next_whats_new/3d_plot_aspects.rst

Copy file name to clipboardExpand all lines: doc/users/next_whats_new/3d_plot_aspects.rst
+7-3Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ Set equal aspect ratio for 3D plots
22
-----------------------------------
33

44
Users can set the aspect ratio for the X, Y, Z axes of a 3D plot to be 'equal',
5-
'equalxy', 'equalxz', or 'equalyz' rather than the default of 'auto'.
5+
'equalxy', 'equalxz', or 'equalyz' rather than the default of 'auto'. Just like
6+
in case of 2D plots, either the data limits or the bounding box can be adjusted
7+
to achieve the desired aspect.
68

79
.. plot::
810
:include-source: true
@@ -26,7 +28,9 @@ Users can set the aspect ratio for the X, Y, Z axes of a 3D plot to be 'equal',
2628
# Set the aspect ratios
2729
for i, ax in enumerate(axs):
2830
ax.set_box_aspect((3, 4, 5))
29-
ax.set_aspect(aspects[i])
30-
ax.set_title("set_aspect('{aspects[i]}')")
31+
ax.set_aspect(aspects[i], adjustable='datalim')
32+
# Alternatively: ax.set_aspect(aspects[i], adjustable='box')
33+
# which will modify the box aspect ratio instead of the data limits.
34+
ax.set_title(f"set_aspect('{aspects[i]}')")
3135

3236
plt.show()

‎lib/mpl_toolkits/mplot3d/axes3d.py

Copy file name to clipboardExpand all lines: lib/mpl_toolkits/mplot3d/axes3d.py
+50-21Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -286,9 +286,7 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
286286
'equalyz' adapt the y and z axes to have equal aspect ratios.
287287
========= ==================================================
288288
289-
adjustable : None
290-
Currently ignored by Axes3D
291-
289+
adjustable : None or {'box', 'datalim'}, optional
292290
If not *None*, this defines which parameter will be adjusted to
293291
meet the required aspect. See `.set_adjustable` for further
294292
details.
@@ -319,34 +317,65 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
319317
"""
320318
_api.check_in_list(('auto', 'equal', 'equalxy', 'equalyz', 'equalxz'),
321319
aspect=aspect)
320+
if adjustable is None:
321+
adjustable = self._adjustable
322+
_api.check_in_list(('box', 'datalim'), adjustable=adjustable)
322323
super().set_aspect(
323324
aspect='auto', adjustable=adjustable, anchor=anchor, share=share)
324325
self._aspect = aspect
325326

326327
if aspect in ('equal', 'equalxy', 'equalxz', 'equalyz'):
327-
if aspect == 'equal':
328-
ax_indices = [0, 1, 2]
329-
elif aspect == 'equalxy':
330-
ax_indices = [0, 1]
331-
elif aspect == 'equalxz':
332-
ax_indices = [0, 2]
333-
elif aspect == 'equalyz':
334-
ax_indices = [1, 2]
328+
ax_idx = self._equal_aspect_axis_indices(aspect)
335329

336330
view_intervals = np.array([self.xaxis.get_view_interval(),
337331
self.yaxis.get_view_interval(),
338332
self.zaxis.get_view_interval()])
339-
mean = np.mean(view_intervals, axis=1)
340333
ptp = np.ptp(view_intervals, axis=1)
341-
delta = max(ptp[ax_indices])
342-
scale = self._box_aspect[ptp == delta][0]
343-
deltas = delta * self._box_aspect / scale
344-
345-
for i, set_lim in enumerate((self.set_xlim3d,
346-
self.set_ylim3d,
347-
self.set_zlim3d)):
348-
if i in ax_indices:
349-
set_lim(mean[i] - deltas[i]/2., mean[i] + deltas[i]/2.)
334+
if adjustable == 'datalim':
335+
mean = np.mean(view_intervals, axis=1)
336+
delta = max(ptp[ax_idx])
337+
scale = self._box_aspect[ptp == delta][0]
338+
deltas = delta * self._box_aspect / scale
339+
340+
for i, set_lim in enumerate((self.set_xlim3d,
341+
self.set_ylim3d,
342+
self.set_zlim3d)):
343+
if i in ax_idx:
344+
set_lim(mean[i] - deltas[i]/2., mean[i] + deltas[i]/2.)
345+
else: # 'box'
346+
# Change the box aspect such that the ratio of the length of
347+
# the unmodified axis to the length of the diagonal
348+
# perpendicular to it remains unchanged.
349+
box_aspect = np.array(self._box_aspect)
350+
box_aspect[ax_idx] = ptp[ax_idx]
351+
remaining_ax_idx = {0, 1, 2}.difference(ax_idx)
352+
if remaining_ax_idx:
353+
remaining = remaining_ax_idx.pop()
354+
old_diag = np.linalg.norm(self._box_aspect[ax_idx])
355+
new_diag = np.linalg.norm(box_aspect[ax_idx])
356+
box_aspect[remaining] *= new_diag / old_diag
357+
self.set_box_aspect(box_aspect)
358+
359+
def _equal_aspect_axis_indices(self, aspect):
360+
"""
361+
Get the indices for which of the x, y, z axes are constrained to have
362+
equal aspect ratios.
363+
364+
Parameters
365+
----------
366+
aspect : {'auto', 'equal', 'equalxy', 'equalxz', 'equalyz'}
367+
See descriptions in docstring for `.set_aspect()`.
368+
"""
369+
ax_indices = [] # aspect == 'auto'
370+
if aspect == 'equal':
371+
ax_indices = [0, 1, 2]
372+
elif aspect == 'equalxy':
373+
ax_indices = [0, 1]
374+
elif aspect == 'equalxz':
375+
ax_indices = [0, 2]
376+
elif aspect == 'equalyz':
377+
ax_indices = [1, 2]
378+
return ax_indices
350379

351380
def set_box_aspect(self, aspect, *, zoom=1):
352381
"""
Loading

‎lib/mpl_toolkits/tests/test_mplot3d.py

Copy file name to clipboardExpand all lines: lib/mpl_toolkits/tests/test_mplot3d.py
+19-1Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,25 @@ def test_aspects():
4444
ax.plot3D(*zip(start*scale, end*scale))
4545
for i, ax in enumerate(axs):
4646
ax.set_box_aspect((3, 4, 5))
47-
ax.set_aspect(aspects[i])
47+
ax.set_aspect(aspects[i], adjustable='datalim')
48+
49+
50+
@mpl3d_image_comparison(['aspects_adjust_box.png'], remove_text=False)
51+
def test_aspects_adjust_box():
52+
aspects = ('auto', 'equal', 'equalxy', 'equalyz', 'equalxz')
53+
fig, axs = plt.subplots(1, len(aspects), subplot_kw={'projection': '3d'},
54+
figsize=(11, 3))
55+
56+
# Draw rectangular cuboid with side lengths [4, 3, 5]
57+
r = [0, 1]
58+
scale = np.array([4, 3, 5])
59+
pts = itertools.combinations(np.array(list(itertools.product(r, r, r))), 2)
60+
for start, end in pts:
61+
if np.sum(np.abs(start - end)) == r[1] - r[0]:
62+
for ax in axs:
63+
ax.plot3D(*zip(start*scale, end*scale))
64+
for i, ax in enumerate(axs):
65+
ax.set_aspect(aspects[i], adjustable='box')
4866

4967

5068
def test_axes3d_repr():

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.