8000 Merge pull request #23409 from scottshambaugh/aspect_equal_3d · matplotlib/matplotlib@437158c · GitHub
[go: up one dir, main page]

Skip to content

Commit 437158c

Browse files
authored
Merge pull request #23409 from scottshambaugh/aspect_equal_3d
ENH: Provide axis('equal') for Axes3D replaces PR #23017
2 parents 16ad86e + bfd0afe commit 437158c

File tree

7 files changed

+87
-19
lines changed

7 files changed

+87
-19
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
Set equal aspect ratio for 3D plots
2+
-----------------------------------
3+
4+
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'.
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+
17+
# Draw rectangular cuboid with side lengths [1, 1, 5]
18+
r = [0, 1]
19+
scale = np.array([1, 1, 5])
20+
pts = combinations(np.array(list(product(r, r, r))), 2)
21+
for start, end in pts:
22+
if np.sum(np.abs(start - end)) == r[1] - r[0]:
23+
for ax in axs:
24+
ax.plot3D(*zip(start*scale, end*scale), color='C0')
25+
26+
# Set the aspect ratios
27+
for i, ax in enumerate(axs):
28+
ax.set_box_aspect((3, 4, 5))
29+
ax.set_aspect(aspects[i])
30+
ax.set_title("set_aspect('{aspects[i]}')")
31+
32+
plt.show()

examples/mplot3d/surface3d_2.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,7 @@
2323
# Plot the surface
2424
ax.plot_surface(x, y, z)
2525

26+
# Set an equal aspect ratio
27+
ax.set_aspect('equal')
28+
2629
plt.show()

examples/mplot3d/voxels_numpy_logo.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,6 @@ def explode(data):
4242

4343
ax = plt.figure().add_subplot(projection='3d')
4444
ax.voxels(x, y, z, filled_2, facecolors=fcolors_2, edgecolors=ecolors_2)
45+
ax.set_aspect('equal')
4546

4647
plt.show()

examples/mplot3d/voxels_rgb.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,6 @@ def midpoints(x):
3939
edgecolors=np.clip(2*colors - 0.5, 0, 1), # brighter
4040
linewidth=0.5)
4141
ax.set(xlabel='r', ylabel='g', zlabel='b')
42+
ax.set_aspect('equal')
4243

4344
plt.show()

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -271,22 +271,19 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
271271
"""
272272
Set the aspect ratios.
273273
274-
Axes 3D does not current support any aspect but 'auto' which fills
275-
the Axes with the data limits.
276-
277-
To simulate having equal aspect in data space, set the ratio
278-
of your data limits to match the value of `.get_box_aspect`.
279-
To control box aspect ratios use `~.Axes3D.set_box_aspect`.
280-
281274
Parameters
282275
----------
283-
aspect : {'auto'}
276+
aspect : {'auto', 'equal', 'equalxy', 'equalxz', 'equalyz'}
284277
Possible values:
285278
286279
========= ==================================================
287280
value description
288281
========= ==================================================
289282
'auto' automatic; fill the position rectangle with data.
283+
'equal' adapt all the axes to have equal aspect ratios.
284+
'equalxy' adapt the x and y axes to have equal aspect ratios.
285+
'equalxz' adapt the x and z axes to have equal aspect ratios.
286+
'equalyz' adapt the y and z axes to have equal aspect ratios.
290287
========= ==================================================
291288
292289
adjustable : None
@@ -320,13 +317,36 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
320317
--------
321318
mpl_toolkits.mplot3d.axes3d.Axes3D.set_box_aspect
322319
"""
323-
if aspect != 'auto':
324-
raise NotImplementedError(
325-
"Axes3D currently only supports the aspect argument "
326-
f"'auto'. You passed in {aspect!r}."
327-
)
320+
_api.check_in_list(('auto', 'equal', 'equalxy', 'equalyz', 'equalxz'),
321+
aspect=aspect)
328322
super().set_aspect(
329-
aspect, adjustable=adjustable, anchor=anchor, share=share)
323+
aspect='auto', adjustable=adjustable, anchor=anchor, share=share)
324+
self._aspect = aspect
325+
326+
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]
335+
336+
view_intervals = np.array([self.xaxis.get_view_interval(),
337+
self.yaxis.get_view_interval(),
338+
self.zaxis.get_view_interval()])
339+
mean = np.mean(view_intervals, axis=1)
340+
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.)
330350

331351
def set_box_aspect(self, aspect, *, zoom=1):
332352
"""
Loading

lib/mpl_toolkits/tests/test_mplot3d.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,22 @@ def test_invisible_axes(fig_test, fig_ref):
2929
ax.set_visible(False)
3030

3131

32-
def test_aspect_equal_error():
33-
fig = plt.figure()
34-
ax = fig.add_subplot(projection='3d')
35-
with pytest.raises(NotImplementedError):
36-
ax.set_aspect('equal')
32+
@mpl3d_image_comparison(['aspects.png'], remove_text=False)
33+
def test_aspects():
34+
aspects = ('auto', 'equal', 'equalxy', 'equalyz', 'equalxz')
35+
fig, axs = plt.subplots(1, len(aspects), subplot_kw={'projection': '3d'})
36+
37+
# Draw rectangular cuboid with side lengths [1, 1, 5]
38+
r = [0, 1]
39+
scale = np.array([1, 1, 5])
40+
pts = itertools.combinations(np.array(list(itertools.product(r, r, r))), 2)
41+
for start, end in pts:
42+
if np.sum(np.abs(start - end)) == r[1] - r[0]:
43+
for ax in axs:
44+
ax.plot3D(*zip(start*scale, end*scale))
45+
for i, ax in enumerate(axs):
46+
ax.set_box_aspect((3, 4, 5))
47+
ax.set_aspect(aspects[i])
3748

3849

3950
def test_axes3d_repr():

0 commit comments

Comments
 (0)
0