39
39
from matplotlib .text import Text
40
40
from matplotlib .transforms import (Affine2D ,Bbox ,BboxTransformTo ,
41
41
TransformedBbox )
42
+ from matplotlib .layout_engine import (constrained_layout_engine ,
43
+ tight_layout_engine ,LayoutEngine )
42
44
43
45
_log = logging .getLogger (__name__ )
44
46
@@ -1144,12 +1146,15 @@ def colorbar(self, mappable, cax=None, ax=None, use_gridspec=True, **kw):
1144
1146
"silence this warning, explicitly pass the 'ax' argument "
1145
1147
"to colorbar()." )
1146
1148
1149
+ if use_gridspec :
1150
+ if (self .layout_engine is not None and
1151
+ not self .layout_engine .colorbar_gridspec ):
1152
+ use_gridspec = False
1147
1153
# Store the value of gca so that we can set it back later on.
1148
1154
if cax is None :
1149
1155
current_ax = self .gca ()
1150
1156
kw ['userax' ]= False
1151
- if (use_gridspec and isinstance (ax ,SubplotBase )
1152
- and not self .get_constrained_layout ()):
1157
+ if (use_gridspec and isinstance (ax ,SubplotBase )):
1153
1158
cax ,kw = cbar .make_axes_gridspec (ax ,** kw )
1154
1159
else :
1155
1160
cax ,kw = cbar .make_axes (ax ,** kw )
@@ -1197,12 +1202,13 @@ def subplots_adjust(self, left=None, bottom=None, right=None, top=None,
1197
1202
The height of the padding between subplots,
1198
1203
as a fraction of the average Axes height.
1199
1204
"""
1200
- if self .get_constrained_layout ():
1201
- self .set_constrained_layout ( False )
1205
+ if ( self .layout_engine and
1206
+ not self .layout_engine . adjust_compatible ):
1202
1207
_api .warn_external (
1203
- "This figure was usingconstrained_layout, but that is "
1208
+ "This figure was usinga layout engine that is "
1204
1209
"incompatible with subplots_adjust and/or tight_layout; "
1205
- "disabling constrained_layout." )
1210
+ "not calling subplots_adjust" )
1211
+ return
1206
1212
self .subplotpars .update (left ,bottom ,right ,top ,wspace ,hspace )
1207
1213
for ax in self .axes :
1208
1214
if isinstance (ax ,SubplotBase ):
@@ -2207,6 +2213,7 @@ def __init__(self,
2207
2213
"""
2208
2214
super ().__init__ ()
2209
2215
2216
+ self .layout_engine = None
2210
2217
if layout is not None :
2211
2218
if tight_layout is not None :
2212
2219
_api .warn_external (
@@ -2216,14 +2223,15 @@ def __init__(self,
2216
2223
_api .warn_external (
2217
2224
"The Figure parameters 'layout' and 'constrained_layout' "
2218
2225
"cannot be used together. Please use 'layout' only." )
2219
- if layout == 'constrained' :
2220
- tight_layout = False
2221
- constrained_layout = True
2222
- elif layout == 'tight' :
2223
- tight_layout = True
2224
- constrained_layout = False
2225
- else :
2226
- raise ValueError (f"Invalid value for 'layout':{ layout !r} " )
2226
+ self .set_layout_engine (layout )
2227
+ elif tight_layout is not None :
2228
+ self .set_layout_engine (layout = 'tight' )
2229
+ if isinstance (tight_layout ,dict ):
2230
+ self .get_layout_engine ().set_info (tight_layout )
2231
+ elif constrained_layout is not None :
2232
+ self .set_layout_engine (layout = 'constrained' )
2233
+ if isinstance (constrained_layout ,dict ):
2234
+ self .get_layout_engine ().set_info (constrained_layout )
2227
2235
2228
2236
self .callbacks = cbook .CallbackRegistry ()
2229
2237
# Callbacks traditionally associated with the canvas (and exposed with
@@ -2274,20 +2282,37 @@ def __init__(self,
2274
2282
2275
2283
self .subplotpars = subplotpars
2276
2284
2277
- # constrained_layout:
2278
- self ._constrained = False
2279
-
2280
- self .set_tight_layout (tight_layout )
2281
-
2282
2285
self ._axstack = _AxesStack ()# track all figure axes and current axes
2283
2286
self .clf ()
2284
2287
self ._cachedRenderer = None
2285
2288
2286
- self .set_constrained_layout (constrained_layout )
2287
-
2288
2289
# list of child gridspecs for this figure
2289
2290
self ._gridspecs = []
2290
2291
2292
+ def set_layout_engine (self ,layout = None ,** kwargs ):
2293
+ """
2294
+ Set the layout engine... FIXME
2295
+ """
2296
+ if layout is None :
2297
+ if mpl .rcParams ['figure.autolayout' ]:
2298
+ layout = 'tight'
2299
+ elif mpl .rcParams ['figure.constrained_layout.use' ]:
2300
+ layout = 'constrained'
2301
+ else :
2302
+ self .layout_engine = None
2303
+ return
2304
+ if layout == 'tight' :
2305
+ self .layout_engine = tight_layout_engine (self ,** kwargs )
2306
+ elif layout == 'constrained' :
2307
+ self .layout_engine = constrained_layout_engine (self ,** kwargs )
2308
+ elif isinstance (layout ,LayoutEngine ):
2309
+ self .layout_engine = layout
2310
+ else :
2311
+ raise ValueError (f"Invalid value for 'layout':{ layout !r} " )
2312
+
2313
+ def get_layout_engine (self ):
2314
+ return self .layout_engine
2315
+
2291
2316
# TODO: I'd like to dynamically add the _repr_html_ method
2292
2317
# to the figure in the right context, but then IPython doesn't
2293
2318
# use it, for some reason.
@@ -2377,8 +2402,10 @@ def _set_dpi(self, dpi, forward=True):
2377
2402
2378
2403
def get_tight_layout (self ):
2379
2404
"""Return whether `.tight_layout` is called when drawing."""
2380
- return self ._tight
2405
+ return isinstance ( self .layout_engine , tight_layout_engine )
2381
2406
2407
+ @_api .deprecated (
2408
+ "3.5" ,alternative = "set_layout_engine" )
2382
2409
def set_tight_layout (self ,tight ):
2383
2410
"""
2384
2411
Set whether and how `.tight_layout` is called when drawing.
@@ -2393,8 +2420,9 @@ def set_tight_layout(self, tight):
2393
2420
"""
2394
2421
if tight is None :
2395
2422
tight = mpl .rcParams ['figure.autolayout' ]
2396
- self ._tight = bool (tight )
2397
- self ._tight_parameters = tight if isinstance (tight ,dict )else {}
2423
+ _tight_parameters = tight if isinstance (tight ,dict )else {}
2424
+ if bool (tight ):
2425
+ self .layout_engine = tight_layout_engine (self ,_tight_parameters )
2398
2426
self .stale = True
2399
2427
2400
2428
def get_constrained_layout (self ):
@@ -2403,8 +2431,10 @@ def get_constrained_layout(self):
2403
2431
2404
2432
See :doc:`/tutorials/intermediate/constrainedlayout_guide`.
2405
2433
"""
2406
- return self ._constrained
2434
+ return isinstance ( self .layout_engine , constrained_layout_engine )
2407
2435
2436
+ @_api .deprecated (
2437
+ "3.5" ,alternative = "set_layout_engine('constrained')" )
2408
2438
def set_constrained_layout (self ,constrained ):
2409
2439
"""
2410
2440
Set whether ``constrained_layout`` is used upon drawing. If None,
@@ -2421,20 +2451,16 @@ def set_constrained_layout(self, constrained):
2421
2451
----------
2422
2452
constrained : bool or dict or None
2423
2453
"""
2424
- self ._constrained_layout_pads = dict ()
2425
- self ._constrained_layout_pads ['w_pad' ]= None
2426
- self ._constrained_layout_pads ['h_pad' ]= None
2427
- self ._constrained_layout_pads ['wspace' ]= None
2428
- self ._constrained_layout_pads ['hspace' ]= None
2429
2454
if constrained is None :
2430
2455
constrained = mpl .rcParams ['figure.constrained_layout.use' ]
2431
- self ._constrained = bool (constrained )
2432
- if isinstance (constrained ,dict ):
2433
- self .set_constrained_layout_pads (** constrained )
2434
- else :
2435
- self .set_constrained_layout_pads ()
2456
+ _constrained = bool (constrained )
2457
+ _parameters = constrained if isinstance (constrained ,dict )else {}
2458
+ if _constrained :
2459
+ self .layout_engine = constrained_layout_engine (self ,_parameters )
2436
2460
self .stale = True
2437
2461
2462
+ @_api .deprecated (
2463
+ "3.5" ,alternative = "figure.layout_engine.set_info()" )
2438
2464
def set_constrained_layout_pads (self ,** kwargs ):
2439
2465
"""
2440
2466
Set padding for ``constrained_layout``. Note the kwargs can be passed
@@ -2461,18 +2487,15 @@ def set_constrained_layout_pads(self, **kwargs):
2461
2487
subplot width. The total padding ends up being h_pad + hspace.
2462
2488
2463
2489
"""
2490
+ if isinstance (self .layout_engine ,constrained_layout_engine ):
2491
+ self .layout_engine .set_info (** kwargs )
2464
2492
2465
- todo = ['w_pad' ,'h_pad' ,'wspace' ,'hspace' ]
2466
- for td in todo :
2467
- if td in kwargs and kwargs [td ]is not None :
2468
- self ._constrained_layout_pads [td ]= kwargs [td ]
2469
- else :
2470
- self ._constrained_layout_pads [td ]= (
2471
- mpl .rcParams ['figure.constrained_layout.' + td ])
2472
-
2493
+ @_api .deprecated (
2494
+ "3.5" ,alternative = "fig.layout_engine.get_info()" )
2473
2495
def get_constrained_layout_pads (self ,relative = False ):
2474
2496
"""
2475
- Get padding for ``constrained_layout``.
2497
+ Get padding for ``constrained_layout`` if it is the
2498
+ ``layout_engine``.
2476
2499
2477
2500
Returns a list of ``w_pad, h_pad`` in inches and
2478
2501
``wspace`` and ``hspace`` as fractions of the subplot.
@@ -2484,10 +2507,14 @@ def get_constrained_layout_pads(self, relative=False):
2484
2507
relative : bool
2485
2508
If `True`, then convert from inches to figure relative.
2486
2509
"""
2487
- w_pad = self ._constrained_layout_pads ['w_pad' ]
2488
- h_pad = self ._constrained_layout_pads ['h_pad' ]
2489
- wspace = self ._constrained_layout_pads ['wspace' ]
2490
- hspace = self ._constrained_layout_pads ['hspace' ]
2510
+
2511
+ if not isinstance (self .layout_engine ,constrained_layout_engine ):
2512
+ return None ,None ,None ,None
2513
+ info = self .layout_engine .get_info ()
2514
+ w_pad = info ['w_pad' ]
2515
+ h_pad = info ['h_pad' ]
2516
+ wspace = info ['wspace' ]
2517
+ hspace = info ['hspace' ]
2491
2518
2492
2519
if relative and (w_pad is not None or h_pad is not None ):
2493
2520
renderer = _get_renderer (self )
@@ -2765,14 +2792,12 @@ def draw(self, renderer):
2765
2792
2766
2793
try :
2767
2794
renderer .open_group ('figure' ,gid = self .get_gid ())
2768
- if self .get_constrained_layout ()and self .axes :
2769
- self .execute_constrained_layout (renderer )
2770
- if self .get_tight_layout ()and self .axes :
2795
+ if self .axes and self .layout_engine is not None :
2771
2796
try :
2772
- self .tight_layout (** self ._tight_parameters )
2797
+ pass
2798
+ self .layout_engine .execute ()
2773
2799
except ValueError :
2774
2800
pass
2775
- # ValueError can occur when resizing a window.
2776
2801
2777
2802
self .patch .draw (renderer )
2778
2803
mimage ._draw_list_compositing_images (
@@ -3102,6 +3127,8 @@ def handler(ev):
3102
3127
3103
3128
return None if event is None else event .name == "key_press_event"
3104
3129
3130
+ @_api .deprecated (
3131
+ "3.5" ,alternative = "figure.layout_engine.execute()" )
3105
3132
def execute_constrained_layout (self ,renderer = None ):
3106
3133
"""
3107
3134
Use ``layoutgrid`` to determine pos positions within Axes.
@@ -3112,22 +3139,12 @@ def execute_constrained_layout(self, renderer=None):
3112
3139
-------
3113
3140
layoutgrid : private debugging object
3114
3141
"""
3142
+ if not isinstance (self .layout_engine ,constrained_layout_engine ):
3143
+ return None
3115
3144
3116
- from matplotlib ._constrained_layout import do_constrained_layout
3117
-
3118
- _log .debug ('Executing constrainedlayout' )
3119
- w_pad ,h_pad ,wspace ,hspace = self .get_constrained_layout_pads ()
3120
- # convert to unit-relative lengths
3121
- fig = self
3122
- width ,height = fig .get_size_inches ()
3123
- w_pad = w_pad / width
3124
- h_pad = h_pad / height
3125
- if renderer is None :
3126
- renderer = _get_renderer (fig )
3127
- return do_constrained_layout (fig ,renderer ,h_pad ,w_pad ,
3128
- hspace ,wspace )
3145
+ return self .layout_engine .execute ()
3129
3146
3130
- def tight_layout (self ,* , pad = 1.08 , h_pad = None , w_pad = None , rect = None ):
3147
+ def tight_layout (self ,** kwargs ):
3131
3148
"""
3132
3149
Adjust the padding between and around subplots.
3133
3150
@@ -3149,25 +3166,20 @@ def tight_layout(self, *, pad=1.08, h_pad=None, w_pad=None, rect=None):
3149
3166
3150
3167
See Also
3151
3168
--------
3152
- .Figure.set_tight_layout
3169
+ .Figure.set_layout_engine
3153
3170
.pyplot.tight_layout
3154
3171
"""
3155
- from contextlib import nullcontext
3156
- from .tight_layout import (
3157
- get_subplotspec_list ,get_tight_layout_figure )
3172
+ from .tight_layout import get_subplotspec_list
3173
+
3158
3174
subplotspec_list = get_subplotspec_list (self .axes )
3159
3175
if None in subplotspec_list :
3160
3176
_api .warn_external ("This figure includes Axes that are not "
3161
3177
"compatible with tight_layout, so results "
3162
3178
"might be incorrect." )
3163
- renderer = _get_renderer (self )
3164
- with getattr (renderer ,"_draw_disabled" ,nullcontext )():
3165
- kwargs = get_tight_layout_figure (
3166
- self ,self .axes ,subplotspec_list ,renderer ,
3167
- pad = pad ,h_pad = h_pad ,w_pad = w_pad ,rect = rect )
3168
- if kwargs :
3169
- self .subplots_adjust (** kwargs )
3170
-
3179
+ # note that here we do not _set_ the figures engine to tight_layout
3180
+ # but rather just perform the layout in place for back compatibility.
3181
+ engine = tight_layout_engine (fig = self ,** kwargs )
3182
+ engine .execute ()
3171
3183
3172
3184
def figaspect (arg ):
3173
3185
"""