Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0e2fdb9

Browse files
box aspect for axes
1 parent 667a100 commit 0e2fdb9

File tree

4 files changed

+279
-3
lines changed

4 files changed

+279
-3
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
:orphan:
2+
3+
Setting axes box aspect
4+
-----------------------
5+
6+
It is now possible to set the aspect of an axes box directly via
7+
`~Axes.set_box_aspect`. The box aspect is the ratio between axes height
8+
and axes width in physical units, independent of the data limits.
9+
This is useful to e.g. produce a square plot, independent of the data it
10+
contains, or to have a usual plot with the same axes dimensions next to
11+
an image plot with fixed (data-)aspect.
12+
13+
For use cases check out the :doc:`Axes box aspect
14+
</gallery/subplots_axes_and_figures/axes_box_aspect>` example.
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
"""
2+
===============
3+
Axes box aspect
4+
===============
5+
6+
This demo shows how to set the aspect of an axes box directly via
7+
`~Axes.set_box_aspect`. The box aspect is the ratio between axes height
8+
and axes width in physical units, independent of the data limits.
9+
This is useful to e.g. produce a square plot, independent of the data it
10+
contains, or to have a usual plot with the same axes dimensions next to
11+
an image plot with fixed (data-)aspect.
12+
13+
The following lists a few use cases for `~Axes.set_box_aspect`.
14+
"""
15+
16+
############################################################################
17+
# A square axes, independent of data
18+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
19+
#
20+
# Produce a square axes, no matter what the data limits are.
21+
22+
import matplotlib
23+
import numpy as np
24+
import matplotlib.pyplot as plt
25+
26+
fig1, ax = plt.subplots()
27+
28+
ax.set_xlim(300, 400)
29+
ax.set_box_aspect(1)
30+
31+
plt.show()
32+
33+
############################################################################
34+
# Shared square axes
35+
# ~~~~~~~~~~~~~~~~~~
36+
#
37+
# Produce shared subplots that are squared in size.
38+
#
39+
fig2, (ax, ax2) = plt.subplots(ncols=2, sharey=True)
40+
41+
ax.plot([1, 5], [0, 10])
42+
ax2.plot([100, 500], [10, 15])
43+
44+
ax.set_box_aspect(1)
45+
ax2.set_box_aspect(1)
46+
47+
plt.show()
48+
49+
############################################################################
50+
# Square twin axes
51+
# ~~~~~~~~~~~~~~~~
52+
#
53+
# Produce a square axes, with a twin axes. The twinned axes takes over the
54+
# box aspect of the parent.
55+
#
56+
57+
fig3, ax = plt.subplots()
58+
59+
ax2 = ax.twinx()
60+
61+
ax.plot([0, 10])
62+
ax2.plot([12, 10])
63+
64+
ax.set_box_aspect(1)
65+
66+
plt.show()
67+
68+
69+
############################################################################
70+
# Normal plot next to image
71+
# ~~~~~~~~~~~~~~~~~~~~~~~~~
72+
#
73+
# When creating an image plot with fixed data aspect and the default
74+
# ``adjustable="box"`` next to a normal plot, the axes would be unequal in
75+
# height. `~Axes.set_box_aspect` provides an easy solution to that by allowing
76+
# to have the normal plot's axes use the images dimensions as box aspect.
77+
#
78+
# This example also shows that ``constrained_layout`` interplays nicely with
79+
# a fixed box aspect.
80+
81+
fig4, (ax, ax2) = plt.subplots(ncols=2, constrained_layout=True)
82+
83+
im = np.random.rand(16, 27)
84+
ax.imshow(im)
85+
86+
ax2.plot([23, 45])
87+
ax2.set_box_aspect(im.shape[0]/im.shape[1])
88+
89+
plt.show()
90+
91+
############################################################################
92+
# Square joint/marginal plot
93+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~
94+
#
95+
# It may be desireable to show marginal distributions next to a plot of joint
96+
# data. The following creates a square plot with the box aspect of the
97+
# marginal axes being equal to the width- and height-ratios of the gridspec.
98+
# This ensures that all axes align perfectly, independent on the size of the
99+
# figure.
100+
101+
fig5, axs = plt.subplots(2, 2, sharex="col", sharey="row",
102+
gridspec_kw=dict(height_ratios=[1, 3],
103+
width_ratios=[3, 1]))
104+
axs[0, 1].set_visible(False)
105+
axs[0, 0].set_box_aspect(1/3)
106+
axs[1, 0].set_box_aspect(1)
107+
axs[1, 1].set_box_aspect(3/1)
108+
109+
x, y = np.random.randn(2, 400) * np.array([[.5], [180]])
110+
axs[1, 0].scatter(x, y)
111+
axs[0, 0].hist(x)
112+
axs[1, 1].hist(y, orientation="horizontal")
113+
114+
plt.show()
115+
116+
############################################################################
117+
# Square joint/marginal plot
118+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~
119+
#
120+
# When setting the box aspect, one may still set the data aspect as well.
121+
# Here we create an axes with a box twice as long as tall and use an "equal"
122+
# data aspect for its contents, i.e. the circle actually stays circular.
123+
124+
fig6, ax = plt.subplots()
125+
126+
ax.add_patch(plt.Circle((5, 3), 1))
127+
ax.set_aspect("equal", adjustable="datalim")
128+
ax.set_box_aspect(0.5)
129+
ax.autoscale()
130+
131+
plt.show()
132+
133+
#############################################################################
134+
#
135+
# ------------
136+
#
137+
# References
138+
# """"""""""
139+
#
140+
# The use of the following functions, methods and classes is shown
141+
# in this example:
142+
143+
matplotlib.axes.Axes.set_box_aspect

lib/matplotlib/axes/_base.py

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,7 @@ def __init__(self, fig, rect,
426426
self.axes = self
427427
self._aspect = 'auto'
428428
self._adjustable = 'box'
429+
self._box_aspect = None
429430
self._anchor = 'C'
430431
self._stale_viewlim_x = False
431432
self._stale_viewlim_y = False
@@ -1282,6 +1283,18 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
12821283
self.stale = True
12831284

12841285
def get_adjustable(self):
1286+
"""
1287+
Returns the adjustable parameter, *{'box', 'datalim'}* that defines
1288+
which parameter the Axes will change to achieve a given aspect.
1289+
1290+
See Also
1291+
--------
1292+
matplotlib.axes.Axes.set_adjustable
1293+
defining the parameter to adjust in order to meet the required
1294+
aspect.
1295+
matplotlib.axes.Axes.set_aspect
1296+
for a description of aspect handling.
1297+
"""
12851298
return self._adjustable
12861299

12871300
def set_adjustable(self, adjustable, share=False):
@@ -1333,6 +1346,54 @@ def set_adjustable(self, adjustable, share=False):
13331346
ax._adjustable = adjustable
13341347
self.stale = True
13351348

1349+
def get_box_aspect(self):
1350+
"""
1351+
Get the axes box aspect.
1352+
Will be ``None`` if not explicitely specified.
1353+
1354+
See Also
1355+
--------
1356+
matplotlib.axes.Axes.set_box_aspect
1357+
for a description of box aspect.
1358+
matplotlib.axes.Axes.set_aspect
1359+
for a description of aspect handling.
1360+
"""
1361+
return self._box_aspect
1362+
1363+
def set_box_aspect(self, aspect=None):
1364+
"""
1365+
Set the axes box aspect. The box aspect is the ratio of the
1366+
axes height to the axes width in physical units. This is not to be
1367+
confused with the data aspect, set via `~Axes.set_aspect`.
1368+
1369+
Parameters
1370+
----------
1371+
aspect : None, or a number
1372+
Changes the physical dimensions of the Axes, such that the ratio
1373+
of the axes height to the axes width in physical units is equal to
1374+
*aspect*. If *None*, the axes geometry will not be adjusted.
1375+
1376+
Note that this changes the adjustable to *datalim*.
1377+
1378+
See Also
1379+
--------
1380+
matplotlib.axes.Axes.set_aspect
1381+
for a description of aspect handling.
1382+
"""
1383+
axs = {*self._twinned_axes.get_siblings(self),
1384+
*self._twinned_axes.get_siblings(self)}
1385+
1386+
if aspect is not None:
1387+
aspect = float(aspect)
1388+
# when box_aspect is set to other than ´None`,
1389+
# adjustable must be "datalim"
1390+
for ax in axs:
1391+
ax.set_adjustable("datalim")
1392+
1393+
for ax in axs:
1394+
ax._box_aspect = aspect
1395+
ax.stale = True
1396+
13361397
def get_anchor(self):
13371398
"""
13381399
Get the anchor location.
@@ -1464,7 +1525,7 @@ def apply_aspect(self, position=None):
14641525

14651526
aspect = self.get_aspect()
14661527

1467-
if aspect == 'auto':
1528+
if aspect == 'auto' and self._box_aspect is None:
14681529
self._set_position(position, which='active')
14691530
return
14701531

@@ -1484,11 +1545,20 @@ def apply_aspect(self, position=None):
14841545
self._set_position(pb1.anchored(self.get_anchor(), pb), 'active')
14851546
return
14861547

1487-
# self._adjustable == 'datalim'
1548+
# The following is only seen if self._adjustable == 'datalim'
1549+
if self._box_aspect is not None:
1550+
pb = position.frozen()
1551+
pb1 = pb.shrunk_to_aspect(self._box_aspect, pb, fig_aspect)
1552+
self._set_position(pb1.anchored(self.get_anchor(), pb), 'active')
1553+
if aspect == "auto":
1554+
return
14881555

14891556
# reset active to original in case it had been changed by prior use
14901557
# of 'box'
1491-
self._set_position(position, which='active')
1558+
if self._box_aspect is None:
1559+
self._set_position(position, which='active')
1560+
else:
1561+
position = pb1.anchored(self.get_anchor(), pb)
14921562

14931563
x_trf = self.xaxis.get_transform()
14941564
y_trf = self.yaxis.get_transform()

lib/matplotlib/tests/test_axes.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6560,3 +6560,52 @@ def test_aspect_nonlinear_adjustable_datalim():
65606560
ax.margins(0)
65616561
ax.apply_aspect()
65626562
assert ax.get_xlim() == pytest.approx(np.array([1/10, 10]) * np.sqrt(10))
6563+
6564+
6565+
def test_box_aspect():
6566+
# Test if axes with box_aspect=1 has same dimensions
6567+
# as axes with aspect equal and adjustable="box"
6568+
6569+
fig1, ax1 = plt.subplots()
6570+
axtwin = ax1.twinx()
6571+
axtwin.plot([12, 344])
6572+
6573+
ax1.set_box_aspect(1)
6574+
6575+
fig2, ax2 = plt.subplots()
6576+
ax2.margins(0)
6577+
ax2.plot([0, 2], [6, 8])
6578+
ax2.set_aspect("equal", adjustable="box")
6579+
6580+
fig1.canvas.draw()
6581+
fig2.canvas.draw()
6582+
6583+
bb1 = ax1.get_position()
6584+
bbt = axtwin.get_position()
6585+
bb2 = ax2.get_position()
6586+
6587+
assert_array_equal(bb1.extents, bb2.extents)
6588+
assert_array_equal(bbt.extents, bb2.extents)
6589+
6590+
6591+
def test_box_aspect_custom_position():
6592+
# Test if axes with custom position and box_aspect
6593+
# behaves the same independent of the order of setting those.
6594+
6595+
fig1, ax1 = plt.subplots()
6596+
ax1.set_position([0.1, 0.1, 0.9, 0.2])
6597+
fig1.canvas.draw()
6598+
ax1.set_box_aspect(1.)
6599+
6600+
fig2, ax2 = plt.subplots()
6601+
ax2.set_box_aspect(1.)
6602+
fig2.canvas.draw()
6603+
ax2.set_position([0.1, 0.1, 0.9, 0.2])
6604+
6605+
fig1.canvas.draw()
6606+
fig2.canvas.draw()
6607+
6608+
bb1 = ax1.get_position()
6609+
bb2 = ax2.get_position()
6610+
6611+
assert_array_equal(bb1.extents, bb2.extents)

0 commit comments

Comments
 (0)