Skip to content

Commit e59721e

Browse files
patquemscottshambaugh
authored andcommitted
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
1 parent 37ccdca commit e59721e

File tree

4 files changed

+63
-13
lines changed

4 files changed

+63
-13
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
aspects = ('auto', 'equal', 'equalxy', 'equalyz', 'equalxz')
11+
fig, axs = plt.subplots(1, len(aspects), subplot_kw={'projection': '3d'})
12+
13+
# Draw rectangular cuboid with side lengths [1, 1, 2]
14+
r = [0, 1]
15+
scale = np.array([1, 1, 2])
16+
pts = itertools.combinations(np.array(list(itertools.product(r, r, r))), 2)
17+
for start, end in pts:
18+
if np.sum(np.abs(start - end)) == r[1] - r[0]:
19+
for ax in axs:
20+
ax.plot3D(*zip(start*scale, end*scale), color='C0')
21+
22+
# Set the aspect ratios
23+
for i, ax in enumerate(axs):
24+
ax.set_box_aspect((3, 4, 5))
25+
ax.set_aspect(aspects[i])
26+
ax.title(f"set_aspect('{aspects[i]}')")
27+
28+
plt.show()

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -272,22 +272,16 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
272272
"""
273273
Set the aspect ratios.
274274
275-
Axes 3D does not current support any aspect but 'auto' which fills
276-
the Axes with the data limits.
277-
278-
To simulate having equal aspect in data space, set the ratio
279-
of your data limits to match the value of `.get_box_aspect`.
280-
To control box aspect ratios use `~.Axes3D.set_box_aspect`.
281-
282275
Parameters
283276
----------
284-
aspect : {'auto'}
277+
aspect : {'auto', 'equal'}
285278
Possible values:
286279
287280
========= ==================================================
288281
value description
289282
========= ==================================================
290283
'auto' automatic; fill the position rectangle with data.
284+
'equal' adapt the axes to have equal aspect ratios.
291285
========= ==================================================
292286
293287
adjustable : None
@@ -321,14 +315,26 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
321315
--------
322316
mpl_toolkits.mplot3d.axes3d.Axes3D.set_box_aspect
323317
"""
324-
if aspect != 'auto':
318+
if aspect not in ('auto', 'equal'):
325319
raise NotImplementedError(
326320
"Axes3D currently only supports the aspect argument "
327-
f"'auto'. You passed in {aspect!r}."
321+
f"'auto' or 'equal'. You passed in {aspect!r}."
328322
)
329323
super().set_aspect(
330324
aspect, adjustable=adjustable, anchor=anchor, share=share)
331325

326+
if aspect == 'equal':
327+
v_intervals = np.vstack((self.xaxis.get_view_interval(),
328+
self.yaxis.get_view_interval(),
329+
self.zaxis.get_view_interval()))
330+
mean = np.mean(v_intervals, axis=1)
331+
delta = np.max(np.ptp(v_intervals, axis=1))
332+
deltas = delta * self._box_aspect / min(self._box_aspect)
333+
334+
self.set_xlim3d(mean[0] - deltas[0] / 2., mean[0] + deltas[0] / 2.)
335+
self.set_ylim3d(mean[1] - deltas[1] / 2., mean[1] + deltas[1] / 2.)
336+
self.set_zlim3d(mean[2] - deltas[2] / 2., mean[2] + deltas[2] / 2.)
337+
332338
def set_box_aspect(self, aspect, *, zoom=1):
333339
"""
334340
Set the Axes box aspect.
36.2 KB
Loading

lib/mpl_toolkits/tests/test_mplot3d.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,27 @@ def test_invisible_axes(fig_test, fig_ref):
2727
ax.set_visible(False)
2828

2929

30-
def test_aspect_equal_error():
30+
@mpl3d_image_comparison(['aspect_equal.png'], remove_text=False)
31+
def test_aspect_equal():
3132
fig = plt.figure()
3233
ax = fig.add_subplot(projection='3d')
33-
with pytest.raises(NotImplementedError):
34-
ax.set_aspect('equal')
34+
35+
points = np.array([[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0],
36+
[0, 0, 3], [1, 0, 3], [0, 1, 3], [1, 1, 3]])
37+
38+
x = np.asarray([coord[0] for coord in points])
39+
y = np.asarray([coord[1] for coord in points])
40+
z = np.asarray([coord[2] for coord in points])
41+
42+
def plot_edge(i, j):
43+
ax.plot([x[i], x[j]], [y[i], y[j]], [z[i], z[j]], c='r')
44+
45+
# hexaedron creation
46+
plot_edge(0, 1), plot_edge(1, 3), plot_edge(3, 2), plot_edge(2, 0)
47+
plot_edge(4, 5), plot_edge(5, 7), plot_edge(7, 6), plot_edge(6, 4)
48+
plot_edge(0, 4), plot_edge(1, 5), plot_edge(3, 7), plot_edge(2, 6)
49+
50+
ax.set_aspect('equal')
3551

3652

3753
@mpl3d_image_comparison(['bar3d.png'])

0 commit comments

Comments
 (0)