Skip to content

Commit 9049e10

Browse files
committed
Rebase
1 parent 0666c59 commit 9049e10

File tree

9 files changed

+306
-179
lines changed

9 files changed

+306
-179
lines changed

lib/matplotlib/_constrained_layout.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import numpy as np
1919

2020
from matplotlib import _api, artist as martist
21+
from matplotlib.backend_bases import _get_renderer
2122
import matplotlib.transforms as mtransforms
2223
import matplotlib._layoutgrid as mlayoutgrid
2324

@@ -62,7 +63,7 @@
6263

6364

6465
######################################################
65-
def do_constrained_layout(fig, renderer, h_pad, w_pad,
66+
def do_constrained_layout(fig, h_pad, w_pad,
6667
hspace=None, wspace=None):
6768
"""
6869
Do the constrained_layout. Called at draw time in
@@ -90,7 +91,8 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad,
9091
-------
9192
layoutgrid : private debugging structure
9293
"""
93-
94+
95+
renderer = _get_renderer(fig)
9496
# make layoutgrid tree...
9597
layoutgrids = make_layoutgrids(fig, None)
9698
if not layoutgrids['hasgrids']:

lib/matplotlib/backend_bases.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2301,8 +2301,8 @@ def print_figure(
23012301
else:
23022302
_bbox_inches_restore = None
23032303

2304-
# we have already done CL above, so turn it off:
2305-
stack.enter_context(self.figure._cm_set(constrained_layout=False))
2304+
# we have already done layout above, so turn it off:
2305+
stack.enter_context(self.figure._cm_set(layout_engine=None))
23062306
try:
23072307
# _get_renderer may change the figure dpi (as vector formats
23082308
# force the figure dpi to 72), so we need to set it again here.

lib/matplotlib/figure.py

Lines changed: 93 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,13 @@
3434

3535
from matplotlib.axes import Axes, SubplotBase, subplot_class_factory
3636
from matplotlib.gridspec import GridSpec
37+
from matplotlib.layout_engine import (constrained_layout_engine,
38+
tight_layout_engine, LayoutEngine)
3739
import matplotlib.legend as mlegend
3840
from matplotlib.patches import Rectangle
3941
from matplotlib.text import Text
4042
from matplotlib.transforms import (Affine2D, Bbox, BboxTransformTo,
4143
TransformedBbox)
42-
4344
_log = logging.getLogger(__name__)
4445

4546

@@ -1136,12 +1137,15 @@ def colorbar(self, mappable, cax=None, ax=None, use_gridspec=True, **kw):
11361137
"silence this warning, explicitly pass the 'ax' argument "
11371138
"to colorbar().")
11381139

1140+
if use_gridspec:
1141+
if (self.get_layout_engine() is not None and
1142+
not self.get_layout_engine().colorbar_gridspec):
1143+
use_gridspec = False
11391144
# Store the value of gca so that we can set it back later on.
11401145
if cax is None:
11411146
current_ax = self.gca()
11421147
userax = False
1143-
if (use_gridspec and isinstance(ax, SubplotBase)
1144-
and not self.get_constrained_layout()):
1148+
if (use_gridspec and isinstance(ax, SubplotBase)):
11451149
cax, kw = cbar.make_axes_gridspec(ax, **kw)
11461150
else:
11471151
cax, kw = cbar.make_axes(ax, **kw)
@@ -1189,12 +1193,12 @@ def subplots_adjust(self, left=None, bottom=None, right=None, top=None,
11891193
The height of the padding between subplots,
11901194
as a fraction of the average Axes height.
11911195
"""
1192-
if self.get_constrained_layout():
1193-
self.set_constrained_layout(False)
1196+
if (self.layout_engine and
1197+
not self.layout_engine.adjust_compatible):
11941198
_api.warn_external(
1195-
"This figure was using constrained_layout, but that is "
1199+
"This figure was using a layout engine that is "
11961200
"incompatible with subplots_adjust and/or tight_layout; "
1197-
"disabling constrained_layout.")
1201+
"not calling subplots_adjust.")
11981202
self.subplotpars.update(left, bottom, right, top, wspace, hspace)
11991203
for ax in self.axes:
12001204
if hasattr(ax, 'get_subplotspec'):
@@ -2068,6 +2072,9 @@ def get_constrained_layout_pads(self, relative=False):
20682072
"""
20692073
return self._parent.get_constrained_layout_pads(relative=relative)
20702074

2075+
def get_layout_engine(self):
2076+
return self._parent.get_layout_engine()
2077+
20712078
@property
20722079
def axes(self):
20732080
"""
@@ -2223,25 +2230,31 @@ def __init__(self,
22232230
22242231
%(Figure:kwdoc)s
22252232
"""
2233+
print('layout', layout)
22262234
super().__init__(**kwargs)
2227-
2235+
self.layout_engine = None
22282236
if layout is not None:
22292237
if tight_layout is not None:
22302238
_api.warn_external(
22312239
"The Figure parameters 'layout' and 'tight_layout' "
22322240
"cannot be used together. Please use 'layout' only.")
2233-
if constrained_layout is not None:
2241+
elif constrained_layout is not None:
22342242
_api.warn_external(
22352243
"The Figure parameters 'layout' and 'constrained_layout' "
22362244
"cannot be used together. Please use 'layout' only.")
2237-
if layout == 'constrained':
2238-
tight_layout = False
2239-
constrained_layout = True
2240-
elif layout == 'tight':
2241-
tight_layout = True
2242-
constrained_layout = False
2243-
else:
2244-
_api.check_in_list(['constrained', 'tight'], layout=layout)
2245+
_api.check_in_list(['constrained', 'tight'], layout=layout)
2246+
self.set_layout_engine(layout=layout)
2247+
elif tight_layout is not None:
2248+
if constrained_layout is not None:
2249+
_api.warn_external(
2250+
"The Figure parameters 'tight_layout' and "
2251+
"'constrained_layout' cannot be used together. Please use "
2252+
"'layout' parameter")
2253+
self.set_layout_engine(layout='tight')
2254+
self.get_layout_engine().set_info(tight_layout)
2255+
elif constrained_layout is not None:
2256+
self.set_layout_engine(layout='constrained')
2257+
self.get_layout_engine().set_info(constrained_layout)
22452258

22462259
self.callbacks = cbook.CallbackRegistry()
22472260
# Callbacks traditionally associated with the canvas (and exposed with
@@ -2292,20 +2305,37 @@ def __init__(self,
22922305

22932306
self.subplotpars = subplotpars
22942307

2295-
# constrained_layout:
2296-
self._constrained = False
2297-
2298-
self.set_tight_layout(tight_layout)
2299-
23002308
self._axstack = _AxesStack() # track all figure axes and current axes
23012309
self.clf()
23022310
self._cachedRenderer = None
23032311

2304-
self.set_constrained_layout(constrained_layout)
2305-
23062312
# list of child gridspecs for this figure
23072313
self._gridspecs = []
23082314

2315+
def set_layout_engine(self, layout=None, **kwargs):
2316+
"""
2317+
Set the layout engine... FIXME
2318+
"""
2319+
if layout is None:
2320+
if mpl.rcParams['figure.autolayout']:
2321+
layout = 'tight'
2322+
elif mpl.rcParams['figure.constrained_layout.use']:
2323+
layout = 'constrained'
2324+
else:
2325+
self.layout_engine = None
2326+
return
2327+
if layout == 'tight':
2328+
self.layout_engine = tight_layout_engine(self, **kwargs)
2329+
elif layout == 'constrained':
2330+
self.layout_engine = constrained_layout_engine(self, **kwargs)
2331+
elif isinstance(layout, LayoutEngine):
2332+
self.layout_engine = layout
2333+
else:
2334+
raise ValueError(f"Invalid value for 'layout': {layout!r}")
2335+
2336+
def get_layout_engine(self):
2337+
return self.layout_engine
2338+
23092339
# TODO: I'd like to dynamically add the _repr_html_ method
23102340
# to the figure in the right context, but then IPython doesn't
23112341
# use it, for some reason.
@@ -2395,8 +2425,9 @@ def _set_dpi(self, dpi, forward=True):
23952425

23962426
def get_tight_layout(self):
23972427
"""Return whether `.tight_layout` is called when drawing."""
2398-
return self._tight
2428+
return isinstance(self.layout_engine, tight_layout_engine)
23992429

2430+
@_api.deprecated("3.6", alternative="set_layout_engine")
24002431
def set_tight_layout(self, tight):
24012432
"""
24022433
Set whether and how `.tight_layout` is called when drawing.
@@ -2411,8 +2442,9 @@ def set_tight_layout(self, tight):
24112442
"""
24122443
if tight is None:
24132444
tight = mpl.rcParams['figure.autolayout']
2414-
self._tight = bool(tight)
2415-
self._tight_parameters = tight if isinstance(tight, dict) else {}
2445+
_tight_parameters = tight if isinstance(tight, dict) else {}
2446+
if bool(tight):
2447+
self.layout_engine = tight_layout_engine(self, _tight_parameters)
24162448
self.stale = True
24172449

24182450
def get_constrained_layout(self):
@@ -2421,8 +2453,9 @@ def get_constrained_layout(self):
24212453
24222454
See :doc:`/tutorials/intermediate/constrainedlayout_guide`.
24232455
"""
2424-
return self._constrained
2456+
return isinstance(self.layout_engine, constrained_layout_engine)
24252457

2458+
@_api.deprecated("3.6", alternative="set_layout_engine('constrained')")
24262459
def set_constrained_layout(self, constrained):
24272460
"""
24282461
Set whether ``constrained_layout`` is used upon drawing. If None,
@@ -2439,22 +2472,17 @@ def set_constrained_layout(self, constrained):
24392472
----------
24402473
constrained : bool or dict or None
24412474
"""
2442-
self._constrained_layout_pads = dict()
2443-
self._constrained_layout_pads['w_pad'] = None
2444-
self._constrained_layout_pads['h_pad'] = None
2445-
self._constrained_layout_pads['wspace'] = None
2446-
self._constrained_layout_pads['hspace'] = None
24472475
if constrained is None:
24482476
constrained = mpl.rcParams['figure.constrained_layout.use']
2449-
self._constrained = bool(constrained)
2450-
if isinstance(constrained, dict):
2451-
self.set_constrained_layout_pads(**constrained)
2452-
else:
2453-
self.set_constrained_layout_pads()
2477+
_constrained = bool(constrained)
2478+
_parameters = constrained if isinstance(constrained, dict) else {}
2479+
if _constrained:
2480+
self.layout_engine = constrained_layout_engine(self, _parameters)
24542481
self.stale = True
24552482

2456-
def set_constrained_layout_pads(self, *, w_pad=None, h_pad=None,
2457-
wspace=None, hspace=None):
2483+
@_api.deprecated(
2484+
"3.6", alternative="figure.layout_engine.set_info()")
2485+
def set_constrained_layout_pads(self, **kwargs):
24582486
"""
24592487
Set padding for ``constrained_layout``.
24602488
@@ -2482,18 +2510,13 @@ def set_constrained_layout_pads(self, *, w_pad=None, h_pad=None,
24822510
subplot width. The total padding ends up being h_pad + hspace.
24832511
24842512
"""
2513+
if isinstance(self.layout_engine, constrained_layout_engine):
2514+
self.layout_engine.set_info(**kwargs)
24852515

2486-
for name, size in zip(['w_pad', 'h_pad', 'wspace', 'hspace'],
2487-
[w_pad, h_pad, wspace, hspace]):
2488-
if size is not None:
2489-
self._constrained_layout_pads[name] = size
2490-
else:
2491-
self._constrained_layout_pads[name] = (
2492-
mpl.rcParams[f'figure.constrained_layout.{name}'])
2493-
2516+
@_api.deprecated("3.6", alternative="fig.layout_engine.get_info()")
24942517
def get_constrained_layout_pads(self, relative=False):
24952518
"""
2496-
Get padding for ``constrained_layout``.
2519+
Get padding for ``constrained_layout`` if it is the ``layout_engine``.
24972520
24982521
Returns a list of ``w_pad, h_pad`` in inches and
24992522
``wspace`` and ``hspace`` as fractions of the subplot.
@@ -2505,13 +2528,16 @@ def get_constrained_layout_pads(self, relative=False):
25052528
relative : bool
25062529
If `True`, then convert from inches to figure relative.
25072530
"""
2508-
w_pad = self._constrained_layout_pads['w_pad']
2509-
h_pad = self._constrained_layout_pads['h_pad']
2510-
wspace = self._constrained_layout_pads['wspace']
2511-
hspace = self._constrained_layout_pads['hspace']
2531+
if not isinstance(self.layout_engine, constrained_layout_engine):
2532+
return None, None, None, None
2533+
info = self.layout_engine.get_info()
2534+
w_pad = info['w_pad']
2535+
h_pad = info['h_pad']
2536+
wspace = info['wspace']
2537+
hspace = info['hspace']
25122538

25132539
if relative and (w_pad is not None or h_pad is not None):
2514-
renderer = _get_renderer(self)
2540+
renderer = _get_renderer(self).dpi
25152541
dpi = renderer.dpi
25162542
w_pad = w_pad * dpi / renderer.width
25172543
h_pad = h_pad * dpi / renderer.height
@@ -2786,11 +2812,9 @@ def draw(self, renderer):
27862812

27872813
try:
27882814
renderer.open_group('figure', gid=self.get_gid())
2789-
if self.get_constrained_layout() and self.axes:
2790-
self.execute_constrained_layout(renderer)
2791-
if self.get_tight_layout() and self.axes:
2815+
if self.axes and self.layout_engine is not None:
27922816
try:
2793-
self.tight_layout(**self._tight_parameters)
2817+
self.layout_engine.execute()
27942818
except ValueError:
27952819
pass
27962820
# ValueError can occur when resizing a window.
@@ -3123,6 +3147,7 @@ def handler(ev):
31233147

31243148
return None if event is None else event.name == "key_press_event"
31253149

3150+
@_api.deprecated("3.6", alternative="figure.layout_engine.execute()")
31263151
def execute_constrained_layout(self, renderer=None):
31273152
"""
31283153
Use ``layoutgrid`` to determine pos positions within Axes.
@@ -3133,22 +3158,11 @@ def execute_constrained_layout(self, renderer=None):
31333158
-------
31343159
layoutgrid : private debugging object
31353160
"""
3161+
if not isinstance(self.layout_engine, constrained_layout_engine):
3162+
return None
3163+
return self.layout_engine.execute()
31363164

3137-
from matplotlib._constrained_layout import do_constrained_layout
3138-
3139-
_log.debug('Executing constrainedlayout')
3140-
w_pad, h_pad, wspace, hspace = self.get_constrained_layout_pads()
3141-
# convert to unit-relative lengths
3142-
fig = self
3143-
width, height = fig.get_size_inches()
3144-
w_pad = w_pad / width
3145-
h_pad = h_pad / height
3146-
if renderer is None:
3147-
renderer = _get_renderer(fig)
3148-
return do_constrained_layout(fig, renderer, h_pad, w_pad,
3149-
hspace, wspace)
3150-
3151-
def tight_layout(self, *, pad=1.08, h_pad=None, w_pad=None, rect=None):
3165+
def tight_layout(self, **kwargs):
31523166
"""
31533167
Adjust the padding between and around subplots.
31543168
@@ -3170,24 +3184,19 @@ def tight_layout(self, *, pad=1.08, h_pad=None, w_pad=None, rect=None):
31703184
31713185
See Also
31723186
--------
3173-
.Figure.set_tight_layout
3187+
.Figure.set_layout_engine
31743188
.pyplot.tight_layout
31753189
"""
3176-
from contextlib import nullcontext
3177-
from .tight_layout import (
3178-
get_subplotspec_list, get_tight_layout_figure)
3190+
from .tight_layout import get_subplotspec_list
31793191
subplotspec_list = get_subplotspec_list(self.axes)
31803192
if None in subplotspec_list:
31813193
_api.warn_external("This figure includes Axes that are not "
31823194
"compatible with tight_layout, so results "
31833195
"might be incorrect.")
3184-
renderer = _get_renderer(self)
3185-
with getattr(renderer, "_draw_disabled", nullcontext)():
3186-
kwargs = get_tight_layout_figure(
3187-
self, self.axes, subplotspec_list, renderer,
3188-
pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect)
3189-
if kwargs:
3190-
self.subplots_adjust(**kwargs)
3196+
# note that here we do not _set_ the figures engine to tight_layout
3197+
# but rather just perform the layout in place for back compatibility.
3198+
engine = tight_layout_engine(fig=self, **kwargs)
3199+
engine.execute()
31913200

31923201

31933202
def figaspect(arg):

0 commit comments

Comments
 (0)