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

Commitb18d16c

Browse files
committed
ENH have ax.get_tightbbox have a bbox around all artists
1 parentff059c0 commitb18d16c

File tree

9 files changed

+259
-54
lines changed

9 files changed

+259
-54
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
`.matplotlib.Axes.get_tightbbox` now includes all artists
2+
---------------------------------------------------------
3+
4+
Layout tools like `.Figure.tight_layout`, ``constrained_layout``,
5+
and ``fig.savefig('fname.png', bbox_inches="tight")`` use
6+
`.matplotlib.Axes.get_tightbbox` to determine the bounds of each axes on
7+
a figure and adjust spacing between axes.
8+
9+
In Matplotlib 2.2 ``get_tightbbox`` started to include legends made on the
10+
axes, but still excluded some other artists, like text that may overspill an
11+
axes. For Matplotlib 3.0, *all* artists are now included in the bounding box.
12+
13+
This new default may be overridden in either of two ways:
14+
15+
1. Make the artist to be excluded a child of the figure, not the axes. E.g.,
16+
call ``fig.legend()`` instead of ``ax.legend()`` (perhaps using
17+
`~.matplotlib.Axes.get_legend_handles_labels` to gather handles and labels
18+
from the parent axes).
19+
2. If the artist is a child of the axes, set the artist property
20+
``artist.set_in_layout(False)``.

‎lib/matplotlib/artist.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ def __init__(self):
114114
self._sketch=rcParams['path.sketch']
115115
self._path_effects=rcParams['path.effects']
116116
self._sticky_edges=_XYPair([], [])
117+
self._in_layout=True
117118

118119
def__getstate__(self):
119120
d=self.__dict__.copy()
@@ -251,6 +252,33 @@ def get_window_extent(self, renderer):
251252
"""
252253
returnBbox([[0,0], [0,0]])
253254

255+
defget_tightbbox(self,renderer):
256+
"""
257+
Like `Artist.get_window_extent`, but includes any clipping.
258+
259+
Parameters
260+
----------
261+
renderer : `.RendererBase` instance
262+
renderer that will be used to draw the figures (i.e.
263+
``fig.canvas.get_renderer()``)
264+
265+
Returns
266+
-------
267+
bbox : `.BboxBase`
268+
containing the bounding box (in figure pixel co-ordinates).
269+
"""
270+
271+
bbox=self.get_window_extent(renderer)
272+
ifself.get_clip_on():
273+
clip_box=self.get_clip_box()
274+
ifclip_boxisnotNone:
275+
bbox=Bbox.intersection(bbox,clip_box)
276+
clip_path=self.get_clip_path()
277+
ifclip_pathisnotNoneandbboxisnotNone:
278+
clip_path=clip_path.get_fully_transformed_path()
279+
bbox=Bbox.intersection(bbox,clip_path.get_extents())
280+
returnbbox
281+
254282
defadd_callback(self,func):
255283
"""
256284
Adds a callback function that will be called whenever one of
@@ -701,6 +729,17 @@ def get_animated(self):
701729
"Return the artist's animated state"
702730
returnself._animated
703731

732+
defget_in_layout(self):
733+
"""
734+
Return boolean flag, ``True`` if artist is included in layout
735+
calculations.
736+
737+
E.g. :doc:`/tutorials/intermediate/constrainedlayout_guide`,
738+
`.Figure.tight_layout()`, and
739+
``fig.savefig(fname, bbox_inches='tight')``.
740+
"""
741+
returnself._in_layout
742+
704743
defget_clip_on(self):
705744
'Return whether artist uses clipping'
706745
returnself._clipon
@@ -830,6 +869,19 @@ def set_animated(self, b):
830869
self._animated=b
831870
self.pchanged()
832871

872+
defset_in_layout(self,in_layout):
873+
"""
874+
Set if artist is to be included in layout calculations,
875+
E.g. :doc:`/tutorials/intermediate/constrainedlayout_guide`,
876+
`.Figure.tight_layout()`, and
877+
``fig.savefig(fname, bbox_inches='tight')``.
878+
879+
Parameters
880+
----------
881+
in_layout : bool
882+
"""
883+
self._in_layout=in_layout
884+
833885
defupdate(self,props):
834886
"""
835887
Update this artist's properties from the dictionary *prop*.

‎lib/matplotlib/axes/_base.py

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4110,19 +4110,47 @@ def pick(self, *args):
41104110
martist.Artist.pick(self,args[0])
41114111

41124112
defget_default_bbox_extra_artists(self):
4113+
"""
4114+
Return a default list of artists that are used for the bounding box
4115+
calculation.
4116+
4117+
Artists are excluded either by not being visible or
4118+
``artist.set_in_layout(False)``.
4119+
"""
41134120
return [artistforartistinself.get_children()
4114-
ifartist.get_visible()]
4121+
if(artist.get_visible()andartist.get_in_layout())]
41154122

4116-
defget_tightbbox(self,renderer,call_axes_locator=True):
4123+
defget_tightbbox(self,renderer,call_axes_locator=True,
4124+
bbox_extra_artists=None):
41174125
"""
4118-
Return the tight bounding box of the axes.
4119-
The dimension of the Bbox in canvas coordinate.
4126+
Return the tight bounding box of the axes, including axis and their
4127+
decorators (xlabel, title, etc).
4128+
4129+
Artists that have ``artist.set_in_layout(False)`` are not included
4130+
in the bbox.
4131+
4132+
Parameters
4133+
----------
4134+
renderer : `.RendererBase` instance
4135+
renderer that will be used to draw the figures (i.e.
4136+
``fig.canvas.get_renderer()``)
4137+
4138+
bbox_extra_artists : list of `.Artist` or ``None``
4139+
List of artists to include in the tight bounding box. If
4140+
``None`` (default), then all artist children of the axes are
4141+
included in the tight bounding box.
4142+
4143+
call_axes_locator : boolean (default ``True``)
4144+
If *call_axes_locator* is ``False``, it does not call the
4145+
``_axes_locator`` attribute, which is necessary to get the correct
4146+
bounding box. ``call_axes_locator=False`` can be used if the
4147+
caller is only interested in the relative size of the tightbbox
4148+
compared to the axes bbox.
41204149
4121-
If *call_axes_locator* is *False*, it does not call the
4122-
_axes_locator attribute, which is necessary to get the correct
4123-
bounding box. ``call_axes_locator==False`` can be used if the
4124-
caller is only intereted in the relative size of the tightbbox
4125-
compared to the axes bbox.
4150+
Returns
4151+
-------
4152+
bbox : `.BboxBase`
4153+
bounding box in figure pixel coordinates.
41264154
"""
41274155

41284156
bb= []
@@ -4155,11 +4183,14 @@ def get_tightbbox(self, renderer, call_axes_locator=True):
41554183
ifbb_yaxis:
41564184
bb.append(bb_yaxis)
41574185

4158-
forchildinself.get_children():
4159-
ifisinstance(child,OffsetBox)andchild.get_visible():
4160-
bb.append(child.get_window_extent(renderer))
4161-
elifisinstance(child,Legend)andchild.get_visible():
4162-
bb.append(child._legend_box.get_window_extent(renderer))
4186+
bbox_artists=bbox_extra_artists
4187+
ifbbox_artistsisNone:
4188+
bbox_artists=self.get_default_bbox_extra_artists()
4189+
4190+
forainbbox_artists:
4191+
bbox=a.get_tightbbox(renderer)
4192+
ifbboxisnotNoneand (bbox.width!=0orbbox.height!=0):
4193+
bb.append(bbox)
41634194

41644195
_bbox=mtransforms.Bbox.union(
41654196
[bforbinbbifb.width!=0orb.height!=0])

‎lib/matplotlib/backend_bases.py

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2048,36 +2048,9 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None,
20482048
dryrun=True,
20492049
**kwargs)
20502050
renderer=self.figure._cachedRenderer
2051-
bbox_inches=self.figure.get_tightbbox(renderer)
2052-
20532051
bbox_artists=kwargs.pop("bbox_extra_artists",None)
2054-
ifbbox_artistsisNone:
2055-
bbox_artists= \
2056-
self.figure.get_default_bbox_extra_artists()
2057-
2058-
bbox_filtered= []
2059-
forainbbox_artists:
2060-
bbox=a.get_window_extent(renderer)
2061-
ifa.get_clip_on():
2062-
clip_box=a.get_clip_box()
2063-
ifclip_boxisnotNone:
2064-
bbox=Bbox.intersection(bbox,clip_box)
2065-
clip_path=a.get_clip_path()
2066-
ifclip_pathisnotNoneandbboxisnotNone:
2067-
clip_path= \
2068-
clip_path.get_fully_transformed_path()
2069-
bbox=Bbox.intersection(
2070-
bbox,clip_path.get_extents())
2071-
ifbboxisnotNoneand (
2072-
bbox.width!=0orbbox.height!=0):
2073-
bbox_filtered.append(bbox)
2074-
2075-
ifbbox_filtered:
2076-
_bbox=Bbox.union(bbox_filtered)
2077-
trans=Affine2D().scale(1.0/self.figure.dpi)
2078-
bbox_extra=TransformedBbox(_bbox,trans)
2079-
bbox_inches=Bbox.union([bbox_inches,bbox_extra])
2080-
2052+
bbox_inches=self.figure.get_tightbbox(renderer,
2053+
bbox_extra_artists=bbox_artists)
20812054
pad=kwargs.pop("pad_inches",None)
20822055
ifpadisNone:
20832056
pad=rcParams['savefig.pad_inches']

‎lib/matplotlib/figure.py

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,10 +1593,7 @@ def draw(self, renderer):
15931593
try:
15941594
renderer.open_group('figure')
15951595
ifself.get_constrained_layout()andself.axes:
1596-
ifTrue:
1597-
self.execute_constrained_layout(renderer)
1598-
else:
1599-
pass
1596+
self.execute_constrained_layout(renderer)
16001597
ifself.get_tight_layout()andself.axes:
16011598
try:
16021599
self.tight_layout(renderer,
@@ -2181,26 +2178,52 @@ def waitforbuttonpress(self, timeout=-1):
21812178

21822179
defget_default_bbox_extra_artists(self):
21832180
bbox_artists= [artistforartistinself.get_children()
2184-
ifartist.get_visible()]
2181+
if(artist.get_visible()andartist.get_in_layout())]
21852182
foraxinself.axes:
21862183
ifax.get_visible():
21872184
bbox_artists.extend(ax.get_default_bbox_extra_artists())
21882185
# we don't want the figure's patch to influence the bbox calculation
21892186
bbox_artists.remove(self.patch)
21902187
returnbbox_artists
21912188

2192-
defget_tightbbox(self,renderer):
2189+
defget_tightbbox(self,renderer,bbox_extra_artists=None):
21932190
"""
21942191
Return a (tight) bounding box of the figure in inches.
21952192
2196-
Currently, this takes only axes title, axis labels, and axis
2197-
ticklabels into account. Needs improvement.
2193+
Artists that have ``artist.set_in_layout(False)`` are not included
2194+
in the bbox.
2195+
2196+
Parameters
2197+
----------
2198+
renderer : `.RendererBase` instance
2199+
renderer that will be used to draw the figures (i.e.
2200+
``fig.canvas.get_renderer()``)
2201+
2202+
bbox_extra_artists : list of `.Artist` or ``None``
2203+
List of artists to include in the tight bounding box. If
2204+
``None`` (default), then all artist children of each axes are
2205+
included in the tight bounding box.
2206+
2207+
Returns
2208+
-------
2209+
bbox : `.BboxBase`
2210+
containing the bounding box (in figure inches).
21982211
"""
21992212

22002213
bb= []
2214+
ifbbox_extra_artistsisNone:
2215+
artists=self.get_default_bbox_extra_artists()
2216+
else:
2217+
artists=bbox_extra_artists
2218+
2219+
forainartists:
2220+
bbox=a.get_tightbbox(renderer)
2221+
ifbboxisnotNoneand (bbox.width!=0orbbox.height!=0):
2222+
bb.append(bbox)
2223+
22012224
foraxinself.axes:
22022225
ifax.get_visible():
2203-
bb.append(ax.get_tightbbox(renderer))
2226+
bb.append(ax.get_tightbbox(renderer,bbox_extra_artists))
22042227

22052228
iflen(bb)==0:
22062229
returnself.bbox_inches
@@ -2252,6 +2275,10 @@ def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None,
22522275
"""
22532276
Automatically adjust subplot parameters to give specified padding.
22542277
2278+
To exclude an artist on the axes from the bounding box calculation
2279+
that determines the subplot parameters (i.e. legend, or annotation),
2280+
then set `a.set_in_layout(False)` for that artist.
2281+
22552282
Parameters
22562283
----------
22572284
pad : float

‎lib/matplotlib/legend.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,22 @@ def get_window_extent(self, *args, **kwargs):
981981
'Return extent of the legend.'
982982
returnself._legend_box.get_window_extent(*args,**kwargs)
983983

984+
defget_tightbbox(self,renderer):
985+
"""
986+
Like `.Legend.get_window_extent`, but uses the box for the legend.
987+
988+
Parameters
989+
----------
990+
renderer : `.RendererBase` instance
991+
renderer that will be used to draw the figures (i.e.
992+
``fig.canvas.get_renderer()``)
993+
994+
Returns
995+
-------
996+
`.BboxBase` : containing the bounding box in figure pixel co-ordinates.
997+
"""
998+
returnself._legend_box.get_window_extent(renderer)
999+
9841000
defget_frame_on(self):
9851001
"""Get whether the legend box patch is drawn."""
9861002
returnself._drawFrame

‎lib/matplotlib/tests/test_figure.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,3 +391,28 @@ def test_fspath(fmt, tmpdir):
391391
# All the supported formats include the format name (case-insensitive)
392392
# in the first 100 bytes.
393393
assertfmt.encode("ascii")infile.read(100).lower()
394+
395+
396+
deftest_tightbbox():
397+
fig,ax=plt.subplots()
398+
ax.set_xlim(0,1)
399+
t=ax.text(1.,0.5,'This dangles over end')
400+
renderer=fig.canvas.get_renderer()
401+
x1Nom0=9.035# inches
402+
assertnp.abs(t.get_tightbbox(renderer).x1-x1Nom0*fig.dpi)<2
403+
assertnp.abs(ax.get_tightbbox(renderer).x1-x1Nom0*fig.dpi)<2
404+
assertnp.abs(fig.get_tightbbox(renderer).x1-x1Nom0)<0.05
405+
assertnp.abs(fig.get_tightbbox(renderer).x0-0.679)<0.05
406+
# now exclude t from the tight bbox so now the bbox is quite a bit
407+
# smaller
408+
t.set_in_layout(False)
409+
x1Nom=7.333
410+
assertnp.abs(ax.get_tightbbox(renderer).x1-x1Nom*fig.dpi)<2
411+
assertnp.abs(fig.get_tightbbox(renderer).x1-x1Nom)<0.05
412+
413+
t.set_in_layout(True)
414+
x1Nom=7.333
415+
assertnp.abs(ax.get_tightbbox(renderer).x1-x1Nom0*fig.dpi)<2
416+
# test bbox_extra_artists method...
417+
assertnp.abs(ax.get_tightbbox(renderer,
418+
bbox_extra_artists=[]).x1-x1Nom*fig.dpi)<2

‎tutorials/intermediate/constrainedlayout_guide.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ def example_plot(ax, fontsize=12, nodec=False):
189189

190190
fig,ax=plt.subplots(constrained_layout=True)
191191
ax.plot(np.arange(10),label='This is a plot')
192-
ax.legend(loc='center left',bbox_to_anchor=(0.9,0.5))
192+
ax.legend(loc='center left',bbox_to_anchor=(0.8,0.5))
193193

194194
#############################################
195195
# However, this will steal space from a subplot layout:
@@ -198,7 +198,39 @@ def example_plot(ax, fontsize=12, nodec=False):
198198
foraxinaxs.flatten()[:-1]:
199199
ax.plot(np.arange(10))
200200
axs[1,1].plot(np.arange(10),label='This is a plot')
201-
axs[1,1].legend(loc='center left',bbox_to_anchor=(0.9,0.5))
201+
axs[1,1].legend(loc='center left',bbox_to_anchor=(0.8,0.5))
202+
203+
#############################################
204+
# In order for a legend or other artist to *not* steal space
205+
# from the subplot layout, we can ``leg.set_in_layout(False)``.
206+
# Of course this can mean the legend ends up
207+
# cropped, but can be useful if the plot is subsequently called
208+
# with ``fig.savefig('outname.png', bbox_inches='tight')``. Note,
209+
# however, that the legend's ``get_in_layout`` status will have to be
210+
# toggled again to make the saved file work:
211+
212+
fig,axs=plt.subplots(2,2,constrained_layout=True)
213+
foraxinaxs.flatten()[:-1]:
214+
ax.plot(np.arange(10))
215+
axs[1,1].plot(np.arange(10),label='This is a plot')
216+
leg=axs[1,1].legend(loc='center left',bbox_to_anchor=(0.8,0.5))
217+
leg.set_in_layout(False)
218+
wanttoprint=False
219+
ifwanttoprint:
220+
leg.set_in_layout(True)
221+
fig.do_constrained_layout(False)
222+
fig.savefig('outname.png',bbox_inches='tight')
223+
224+
#############################################
225+
# A better way to get around this awkwardness is to simply
226+
# use a legend for the figure:
227+
fig,axs=plt.subplots(2,2,constrained_layout=True)
228+
foraxinaxs.flatten()[:-1]:
229+
ax.plot(np.arange(10))
230+
lines=axs[1,1].plot(np.arange(10),label='This is a plot')
231+
labels= [l.get_label()forlinlines]
232+
leg=fig.legend(lines,labels,loc='center left',
233+
bbox_to_anchor=(0.8,0.5),bbox_transform=axs[1,1].transAxes)
202234

203235
###############################################################################
204236
# Padding and Spacing

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp