Skip to content

Commit ba7a45a

Browse files
authored
Merge pull request #24981 from rcomer/savefig-compressed
ENH: pad_inches='layout' for savefig
2 parents 2a7c93c + 1eb1323 commit ba7a45a

File tree

7 files changed

+66
-14
lines changed

7 files changed

+66
-14
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
pad_inches="layout" for savefig
2+
-------------------------------
3+
4+
When using constrained or compressed layout,
5+
6+
.. code-block:: python
7+
8+
savefig(filename, bbox_inches="tight", pad_inches="layout")
9+
10+
will now use the padding sizes defined on the layout engine.

lib/matplotlib/backend_bases.py

+17-7
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
from matplotlib._pylab_helpers import Gcf
4949
from matplotlib.backend_managers import ToolManager
5050
from matplotlib.cbook import _setattr_cm
51+
from matplotlib.layout_engine import ConstrainedLayoutEngine
5152
from matplotlib.path import Path
5253
from matplotlib.texmanager import TexManager
5354
from matplotlib.transforms import Affine2D
@@ -2273,8 +2274,11 @@ def print_figure(
22732274
Bounding box in inches: only the given portion of the figure is
22742275
saved. If 'tight', try to figure out the tight bbox of the figure.
22752276
2276-
pad_inches : float, default: :rc:`savefig.pad_inches`
2277-
Amount of padding around the figure when *bbox_inches* is 'tight'.
2277+
pad_inches : float or 'layout', default: :rc:`savefig.pad_inches`
2278+
Amount of padding in inches around the figure when bbox_inches is
2279+
'tight'. If 'layout' use the padding from the constrained or
2280+
compressed layout engine; ignored if one of those engines is not in
2281+
use.
22782282
22792283
bbox_extra_artists : list of `~matplotlib.artist.Artist`, optional
22802284
A list of extra artists that will be considered when the
@@ -2324,8 +2328,8 @@ def print_figure(
23242328
if bbox_inches is None:
23252329
bbox_inches = rcParams['savefig.bbox']
23262330

2327-
if (self.figure.get_layout_engine() is not None or
2328-
bbox_inches == "tight"):
2331+
layout_engine = self.figure.get_layout_engine()
2332+
if layout_engine is not None or bbox_inches == "tight":
23292333
# we need to trigger a draw before printing to make sure
23302334
# CL works. "tight" also needs a draw to get the right
23312335
# locations:
@@ -2341,9 +2345,15 @@ def print_figure(
23412345
if bbox_inches == "tight":
23422346
bbox_inches = self.figure.get_tightbbox(
23432347
renderer, bbox_extra_artists=bbox_extra_artists)
2344-
if pad_inches is None:
2345-
pad_inches = rcParams['savefig.pad_inches']
2346-
bbox_inches = bbox_inches.padded(pad_inches)
2348+
if (isinstance(layout_engine, ConstrainedLayoutEngine) and
2349+
pad_inches == "layout"):
2350+
h_pad = layout_engine.get()["h_pad"]
2351+
w_pad = layout_engine.get()["w_pad"]
2352+
else:
2353+
if pad_inches in [None, "layout"]:
2354+
pad_inches = rcParams['savefig.pad_inches']
2355+
h_pad = w_pad = pad_inches
2356+
bbox_inches = bbox_inches.padded(w_pad, h_pad)
23472357

23482358
# call adjust_bbox to save only the given area
23492359
restore_bbox = _tight_bbox.adjust_bbox(

lib/matplotlib/figure.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -3251,8 +3251,11 @@ def savefig(self, fname, *, transparent=None, **kwargs):
32513251
Bounding box in inches: only the given portion of the figure is
32523252
saved. If 'tight', try to figure out the tight bbox of the figure.
32533253
3254-
pad_inches : float, default: :rc:`savefig.pad_inches`
3255-
Amount of padding around the figure when bbox_inches is 'tight'.
3254+
pad_inches : float or 'layout', default: :rc:`savefig.pad_inches`
3255+
Amount of padding in inches around the figure when bbox_inches is
3256+
'tight'. If 'layout' use the padding from the constrained or
3257+
compressed layout engine; ignored if one of those engines is not in
3258+
use.
32563259
32573260
facecolor : color or 'auto', default: :rc:`savefig.facecolor`
32583261
The facecolor of the figure. If 'auto', use the current figure

lib/matplotlib/layout_engine.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ def __init__(self, *, h_pad=None, w_pad=None,
203203
Parameters
204204
----------
205205
h_pad, w_pad : float
206-
Padding around the axes elements in figure-normalized units.
206+
Padding around the axes elements in inches.
207207
Default to :rc:`figure.constrained_layout.h_pad` and
208208
:rc:`figure.constrained_layout.w_pad`.
209209
hspace, wspace : float
@@ -261,7 +261,7 @@ def set(self, *, h_pad=None, w_pad=None,
261261
Parameters
262262
----------
263263
h_pad, w_pad : float
264-
Padding around the axes elements in figure-normalized units.
264+
Padding around the axes elements in inches.
265265
Default to :rc:`figure.constrained_layout.h_pad` and
266266
:rc:`figure.constrained_layout.w_pad`.
267267
hspace, wspace : float

lib/matplotlib/tests/test_bbox_tight.py

+16
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,22 @@ def test_bbox_inches_tight_suptitle_non_default():
6969
fig.suptitle('Booo', x=0.5, y=1.1)
7070

7171

72+
@image_comparison(['bbox_inches_tight_layout.png'], remove_text=True,
73+
style='mpl20',
74+
savefig_kwarg=dict(bbox_inches='tight', pad_inches='layout'))
75+
def test_bbox_inches_tight_layout_constrained():
76+
fig, ax = plt.subplots(layout='constrained')
77+
fig.get_layout_engine().set(h_pad=0.5)
78+
ax.set_aspect('equal')
79+
80+
81+
def test_bbox_inches_tight_layout_notconstrained(tmp_path):
82+
# pad_inches='layout' should be ignored when not using constrained/
83+
# compressed layout. Smoke test that savefig doesn't error in this case.
84+
fig, ax = plt.subplots()
85+
fig.savefig(tmp_path / 'foo.png', bbox_inches='tight', pad_inches='layout')
86+
87+
7288
@image_comparison(['bbox_inches_tight_clipping'],
7389
remove_text=True, savefig_kwarg={'bbox_inches': 'tight'})
7490
def test_bbox_inches_tight_clipping():

lib/matplotlib/transforms.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -616,10 +616,23 @@ def expanded(self, sw, sh):
616616
a = np.array([[-deltaw, -deltah], [deltaw, deltah]])
617617
return Bbox(self._points + a)
618618

619-
def padded(self, p):
620-
"""Construct a `Bbox` by padding this one on all four sides by *p*."""
619+
@_api.rename_parameter("3.8", "p", "w_pad")
620+
def padded(self, w_pad, h_pad=None):
621+
"""
622+
Construct a `Bbox` by padding this one on all four sides.
623+
624+
Parameters
625+
----------
626+
w_pad : float
627+
Width pad
628+
h_pad: float, optional
629+
Height pad. Defaults to *w_pad*.
630+
631+
"""
621632
points = self.get_points()
622-
return Bbox(points + [[-p, -p], [p, p]])
633+
if h_pad is None:
634+
h_pad = w_pad
635+
return Bbox(points + [[-w_pad, -h_pad], [w_pad, h_pad]])
623636

624637
def translated(self, tx, ty):
625638
"""Construct a `Bbox` by translating this one by *tx* and *ty*."""

0 commit comments

Comments
 (0)