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

Commit25c45b2

Browse files
committed
ENH have ax.get_tightbbox have a bbox around all artists
1 parent932fd81 commit25c45b2

File tree

9 files changed

+260
-55
lines changed

9 files changed

+260
-55
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
@@ -710,6 +738,17 @@ def get_animated(self):
710738
"Return the artist's animated state"
711739
returnself._animated
712740

741+
defget_in_layout(self):
742+
"""
743+
Return boolean flag, ``True`` if artist is included in layout
744+
calculations.
745+
746+
E.g. :doc:`/tutorials/intermediate/constrainedlayout_guide`,
747+
`.Figure.tight_layout()`, and
748+
``fig.savefig(fname, bbox_inches='tight')``.
749+
"""
750+
returnself._in_layout
751+
713752
defget_clip_on(self):
714753
'Return whether artist uses clipping'
715754
returnself._clipon
@@ -845,6 +884,19 @@ def set_animated(self, b):
845884
self._animated=b
846885
self.pchanged()
847886

887+
defset_in_layout(self,in_layout):
888+
"""
889+
Set if artist is to be included in layout calculations,
890+
E.g. :doc:`/tutorials/intermediate/constrained_layout`,
891+
`.Figure.tight_layout()`, and
892+
``fig.savefig(fname, bbox_inches='tight')``.
893+
894+
Parameters
895+
----------
896+
in_layout : bool
897+
"""
898+
self._in_layout=in_layout
899+
848900
defupdate(self,props):
849901
"""
850902
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
@@ -4108,19 +4108,47 @@ def pick(self, *args):
41084108
martist.Artist.pick(self,args[0])
41094109

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

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

41264154
bb= []
@@ -4153,11 +4181,14 @@ def get_tightbbox(self, renderer, call_axes_locator=True):
41534181
ifbb_yaxis:
41544182
bb.append(bb_yaxis)
41554183

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

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

‎lib/matplotlib/backend_bases.py

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2060,36 +2060,9 @@ def print_figure(self, filename, dpi=None, facecolor=None, edgecolor=None,
20602060
dryrun=True,
20612061
**kwargs)
20622062
renderer=self.figure._cachedRenderer
2063-
bbox_inches=self.figure.get_tightbbox(renderer)
2064-
20652063
bbox_artists=kwargs.pop("bbox_extra_artists",None)
2066-
ifbbox_artistsisNone:
2067-
bbox_artists= \
2068-
self.figure.get_default_bbox_extra_artists()
2069-
2070-
bbox_filtered= []
2071-
forainbbox_artists:
2072-
bbox=a.get_window_extent(renderer)
2073-
ifa.get_clip_on():
2074-
clip_box=a.get_clip_box()
2075-
ifclip_boxisnotNone:
2076-
bbox=Bbox.intersection(bbox,clip_box)
2077-
clip_path=a.get_clip_path()
2078-
ifclip_pathisnotNoneandbboxisnotNone:
2079-
clip_path= \
2080-
clip_path.get_fully_transformed_path()
2081-
bbox=Bbox.intersection(
2082-
bbox,clip_path.get_extents())
2083-
ifbboxisnotNoneand (
2084-
bbox.width!=0orbbox.height!=0):
2085-
bbox_filtered.append(bbox)
2086-
2087-
ifbbox_filtered:
2088-
_bbox=Bbox.union(bbox_filtered)
2089-
trans=Affine2D().scale(1.0/self.figure.dpi)
2090-
bbox_extra=TransformedBbox(_bbox,trans)
2091-
bbox_inches=Bbox.union([bbox_inches,bbox_extra])
2092-
2064+
bbox_inches=self.figure.get_tightbbox(renderer,
2065+
bbox_extra_artists=bbox_artists)
20932066
pad=kwargs.pop("pad_inches",None)
20942067
ifpadisNone:
20952068
pad=rcParams['savefig.pad_inches']

‎lib/matplotlib/figure.py

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,10 +1464,7 @@ def draw(self, renderer):
14641464
try:
14651465
renderer.open_group('figure')
14661466
ifself.get_constrained_layout()andself.axes:
1467-
ifTrue:
1468-
self.execute_constrained_layout(renderer)
1469-
else:
1470-
pass
1467+
self.execute_constrained_layout(renderer)
14711468
ifself.get_tight_layout()andself.axes:
14721469
try:
14731470
self.tight_layout(renderer,
@@ -2008,26 +2005,52 @@ def waitforbuttonpress(self, timeout=-1):
20082005

20092006
defget_default_bbox_extra_artists(self):
20102007
bbox_artists= [artistforartistinself.get_children()
2011-
ifartist.get_visible()]
2008+
if(artist.get_visible()andartist.get_in_layout())]
20122009
foraxinself.axes:
20132010
ifax.get_visible():
20142011
bbox_artists.extend(ax.get_default_bbox_extra_artists())
20152012
# we don't want the figure's patch to influence the bbox calculation
20162013
bbox_artists.remove(self.patch)
20172014
returnbbox_artists
20182015

2019-
defget_tightbbox(self,renderer):
2016+
defget_tightbbox(self,renderer,bbox_extra_artists=None):
20202017
"""
20212018
Return a (tight) bounding box of the figure in inches.
20222019
2023-
Currently, this takes only axes title, axis labels, and axis
2024-
ticklabels into account. Needs improvement.
2020+
Artists that have ``artist.set_in_layout(False)`` are not included
2021+
in the bbox.
2022+
2023+
Parameters
2024+
----------
2025+
renderer : `.RendererBase` instance
2026+
renderer that will be used to draw the figures (i.e.
2027+
``fig.canvas.get_renderer()``)
2028+
2029+
bbox_extra_artists : list of `.Artist` or ``None``
2030+
List of artists to include in the tight bounding box. If
2031+
``None`` (default), then all artist children of each axes are
2032+
included in the tight bounding box.
2033+
2034+
Returns
2035+
-------
2036+
bbox : `.BboxBase`
2037+
containing the bounding box (in figure inches).
20252038
"""
20262039

20272040
bb= []
2041+
ifbbox_extra_artistsisNone:
2042+
artists=self.get_default_bbox_extra_artists()
2043+
else:
2044+
artists=bbox_extra_artists
2045+
2046+
forainartists:
2047+
bbox=a.get_tightbbox(renderer)
2048+
ifbboxisnotNoneand (bbox.width!=0orbbox.height!=0):
2049+
bb.append(bbox)
2050+
20282051
foraxinself.axes:
20292052
ifax.get_visible():
2030-
bb.append(ax.get_tightbbox(renderer))
2053+
bb.append(ax.get_tightbbox(renderer,bbox_extra_artists))
20312054

20322055
iflen(bb)==0:
20332056
returnself.bbox_inches
@@ -2079,6 +2102,10 @@ def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None,
20792102
"""
20802103
Automatically adjust subplot parameters to give specified padding.
20812104
2105+
To exclude an artist on the axes from the bounding box calculation
2106+
that determines the subplot parameters (i.e. legend, or annotation),
2107+
then set `a.set_in_layout(False)` for that artist.
2108+
20822109
Parameters
20832110
----------
20842111
pad : float

‎lib/matplotlib/legend.py

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

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

‎lib/matplotlib/tests/test_figure.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,3 +383,28 @@ def test_fspath(fmt, tmpdir):
383383
# All the supported formats include the format name (case-insensitive)
384384
# in the first 100 bytes.
385385
assertfmt.encode("ascii")infile.read(100).lower()
386+
387+
388+
deftest_tightbbox():
389+
fig,ax=plt.subplots()
390+
ax.set_xlim(0,1)
391+
t=ax.text(1.,0.5,'This dangles over end')
392+
renderer=fig.canvas.get_renderer()
393+
x1Nom0=9.035# inches
394+
assertnp.abs(t.get_tightbbox(renderer).x1-x1Nom0*fig.dpi)<2
395+
assertnp.abs(ax.get_tightbbox(renderer).x1-x1Nom0*fig.dpi)<2
396+
assertnp.abs(fig.get_tightbbox(renderer).x1-x1Nom0)<0.05
397+
assertnp.abs(fig.get_tightbbox(renderer).x0-0.679)<0.05
398+
# now exclude t from the tight bbox so now the bbox is quite a bit
399+
# smaller
400+
t.set_in_layout(False)
401+
x1Nom=7.333
402+
assertnp.abs(ax.get_tightbbox(renderer).x1-x1Nom*fig.dpi)<2
403+
assertnp.abs(fig.get_tightbbox(renderer).x1-x1Nom)<0.05
404+
405+
t.set_in_layout(True)
406+
x1Nom=7.333
407+
assertnp.abs(ax.get_tightbbox(renderer).x1-x1Nom0*fig.dpi)<2
408+
# test bbox_extra_artists method...
409+
assertnp.abs(ax.get_tightbbox(renderer,
410+
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