Movatterモバイル変換


[0]ホーム

URL:


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

Sign up
Appearance settings

Commit458ff92

Browse files
box aspect for axes
1 parentfe7fa10 commit458ff92

File tree

4 files changed

+315
-4
lines changed

4 files changed

+315
-4
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: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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+
importmatplotlib
23+
importnumpyasnp
24+
importmatplotlib.pyplotasplt
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+
# Box aspect for many subplots
135+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
136+
#
137+
# It is possible to pass the box aspect to an axes at initialization. The
138+
# following creates a 2 by 3 subplot grid with all square axes.
139+
140+
fig7,axs=plt.subplots(2,3,subplot_kw=dict(box_aspect=1),
141+
sharex=True,sharey=True,constrained_layout=True)
142+
143+
fori,axinenumerate(axs.flat):
144+
ax.scatter(i%3,-((i//3)-0.5)*200,c=[plt.cm.hsv(i/6)],s=300)
145+
plt.show()
146+
147+
#############################################################################
148+
#
149+
# ------------
150+
#
151+
# References
152+
# """"""""""
153+
#
154+
# The use of the following functions, methods and classes is shown
155+
# in this example:
156+
157+
matplotlib.axes.Axes.set_box_aspect

‎lib/matplotlib/axes/_base.py

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ def __init__(self, fig, rect,
383383
label='',
384384
xscale=None,
385385
yscale=None,
386+
box_aspect=None,
386387
**kwargs
387388
):
388389
"""
@@ -404,6 +405,10 @@ def __init__(self, fig, rect,
404405
frameon : bool, optional
405406
True means that the axes frame is visible.
406407
408+
box_aspect : None, or a number, optional
409+
Sets the aspect of the axes box. See `~.axes.Axes.set_box_aspect`
410+
for details.
411+
407412
**kwargs
408413
Other optional keyword arguments:
409414
@@ -437,7 +442,7 @@ def __init__(self, fig, rect,
437442
self._shared_y_axes.join(self,sharey)
438443
self.set_label(label)
439444
self.set_figure(fig)
440-
445+
self.set_box_aspect(box_aspect)
441446
self.set_axes_locator(kwargs.get("axes_locator",None))
442447

443448
self.spines=self._gen_axes_spines()
@@ -1282,6 +1287,18 @@ def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
12821287
self.stale=True
12831288

12841289
defget_adjustable(self):
1290+
"""
1291+
Returns the adjustable parameter, *{'box', 'datalim'}* that defines
1292+
which parameter the Axes will change to achieve a given aspect.
1293+
1294+
See Also
1295+
--------
1296+
matplotlib.axes.Axes.set_adjustable
1297+
defining the parameter to adjust in order to meet the required
1298+
aspect.
1299+
matplotlib.axes.Axes.set_aspect
1300+
for a description of aspect handling.
1301+
"""
12851302
returnself._adjustable
12861303

12871304
defset_adjustable(self,adjustable,share=False):
@@ -1333,6 +1350,55 @@ def set_adjustable(self, adjustable, share=False):
13331350
ax._adjustable=adjustable
13341351
self.stale=True
13351352

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

14631529
aspect=self.get_aspect()
14641530

1465-
ifaspect=='auto':
1531+
ifaspect=='auto'andself._box_aspectisNone:
14661532
self._set_position(position,which='active')
14671533
return
14681534

@@ -1482,11 +1548,20 @@ def apply_aspect(self, position=None):
14821548
self._set_position(pb1.anchored(self.get_anchor(),pb),'active')
14831549
return
14841550

1485-
# self._adjustable == 'datalim'
1551+
# The following is only seen if self._adjustable == 'datalim'
1552+
ifself._box_aspectisnotNone:
1553+
pb=position.frozen()
1554+
pb1=pb.shrunk_to_aspect(self._box_aspect,pb,fig_aspect)
1555+
self._set_position(pb1.anchored(self.get_anchor(),pb),'active')
1556+
ifaspect=="auto":
1557+
return
14861558

14871559
# reset active to original in case it had been changed by prior use
14881560
# of 'box'
1489-
self._set_position(position,which='active')
1561+
ifself._box_aspectisNone:
1562+
self._set_position(position,which='active')
1563+
else:
1564+
position=pb1.anchored(self.get_anchor(),pb)
14901565

14911566
x_trf=self.xaxis.get_transform()
14921567
y_trf=self.yaxis.get_transform()

‎lib/matplotlib/tests/test_axes.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6565,5 +6565,70 @@ def test_aspect_nonlinear_adjustable_datalim():
65656565
aspect=1,adjustable="datalim")
65666566
ax.margins(0)
65676567
ax.apply_aspect()
6568+
65686569
assertax.get_xlim()==pytest.approx([1*10**(1/2),100/10**(1/2)])
65696570
assertax.get_ylim()== (1/101,1/11)
6571+
6572+
6573+
deftest_box_aspect():
6574+
# Test if axes with box_aspect=1 has same dimensions
6575+
# as axes with aspect equal and adjustable="box"
6576+
6577+
fig1,ax1=plt.subplots()
6578+
axtwin=ax1.twinx()
6579+
axtwin.plot([12,344])
6580+
6581+
ax1.set_box_aspect(1)
6582+
6583+
fig2,ax2=plt.subplots()
6584+
ax2.margins(0)
6585+
ax2.plot([0,2], [6,8])
6586+
ax2.set_aspect("equal",adjustable="box")
6587+
6588+
fig1.canvas.draw()
6589+
fig2.canvas.draw()
6590+
6591+
bb1=ax1.get_position()
6592+
bbt=axtwin.get_position()
6593+
bb2=ax2.get_position()
6594+
6595+
assert_array_equal(bb1.extents,bb2.extents)
6596+
assert_array_equal(bbt.extents,bb2.extents)
6597+
6598+
6599+
deftest_box_aspect_custom_position():
6600+
# Test if axes with custom position and box_aspect
6601+
# behaves the same independent of the order of setting those.
6602+
6603+
fig1,ax1=plt.subplots()
6604+
ax1.set_position([0.1,0.1,0.9,0.2])
6605+
fig1.canvas.draw()
6606+
ax1.set_box_aspect(1.)
6607+
6608+
fig2,ax2=plt.subplots()
6609+
ax2.set_box_aspect(1.)
6610+
fig2.canvas.draw()
6611+
ax2.set_position([0.1,0.1,0.9,0.2])
6612+
6613+
fig1.canvas.draw()
6614+
fig2.canvas.draw()
6615+
6616+
bb1=ax1.get_position()
6617+
bb2=ax2.get_position()
6618+
6619+
assert_array_equal(bb1.extents,bb2.extents)
6620+
6621+
6622+
deftest_bbox_aspect_axes_init():
6623+
# Test that box_aspect can be given to axes init and produces
6624+
# all equal square axes.
6625+
fig,axs=plt.subplots(2,3,subplot_kw=dict(box_aspect=1),
6626+
constrained_layout=True)
6627+
fig.canvas.draw()
6628+
renderer=fig.canvas.get_renderer()
6629+
sizes= []
6630+
foraxinaxs.flat:
6631+
bb=ax.get_window_extent(renderer)
6632+
sizes.extend([bb.width,bb.height])
6633+
6634+
assert_allclose(sizes,sizes[0])

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp