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 7c6a74c

Browse filesBrowse files
authored
Provide adjustable='box' to 3D axes aspect ratio setting (#23552)
* Provided `adjustable='box'` option to set 3D aspect ratio. * "What's New": `adjustable` argument of 3D plots aspect ratio.
1 parent 8179d12 commit 7c6a74c
Copy full SHA for 7c6a74c

File tree

Expand file treeCollapse file tree

4 files changed

+103
-22
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+103
-22
lines changed
+34Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
*adjustable* keyword argument for setting equal aspect ratios in 3D
2+
-------------------------------------------------------------------
3+
4+
While setting equal aspect ratios for 3D plots, users can choose to modify
5+
either the data limits or the bounding box.
6+
7+
.. plot::
8+
:include-source: true
9+
10+
import matplotlib.pyplot as plt
11+
import numpy as np
12+
from itertools import combinations, product
13+
14+
aspects = ('auto', 'equal', 'equalxy', 'equalyz', 'equalxz')
15+
fig, axs = plt.subplots(1, len(aspects), subplot_kw={'projection': '3d'},
16+
figsize=(12, 6))
17+
18+
# Draw rectangular cuboid with side lengths [4, 3, 5]
19+
r = [0, 1]
20+
scale = np.array([4, 3, 5])
21+
pts = combinations(np.array(list(product(r, r, r))), 2)
22+
for start, end in pts:
23+
if np.sum(np.abs(start - end)) == r[1] - r[0]:
24+
for ax in axs:
25+
ax.plot3D(*zip(start*scale, end*scale), color='C0')
26+
27+
# Set the aspect ratios
28+
for i, ax in enumerate(axs):
29+
ax.set_aspect(aspects[i], adjustable='datalim')
30+
# Alternatively: ax.set_aspect(aspects[i], adjustable='box')
31+
# which will change the box aspect ratio instead of axis data limits.
32+
ax.set_title(f"set_aspect('{aspects[i]}')")
33+
34+
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
@@ -287,9 +287,7 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
287287
'equalyz' adapt the y and z axes to have equal aspect ratios.
288288
========= ==================================================
289289
290-
adjustable : None
291-
Currently ignored by Axes3D
292-
290+
adjustable : None or {'box', 'datalim'}, optional
293291
If not *None*, this defines which parameter will be adjusted to
294292
meet the required aspect. See `.set_adjustable` for further
295293
details.
@@ -320,34 +318,65 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
320318
"""
321319
_api.check_in_list(('auto', 'equal', 'equalxy', 'equalyz', 'equalxz'),
322320
aspect=aspect)
321+
if adjustable is None:
322+
adjustable = self._adjustable
323+
_api.check_in_list(('box', 'datalim'), adjustable=adjustable)
323324
super().set_aspect(
324325
aspect='auto', adjustable=adjustable, anchor=anchor, share=share)
325326
self._aspect = aspect
326327

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

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

352381
def set_box_aspect(self, aspect, *, zoom=1):
353382
"""
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.