34
34
35
35
from matplotlib .axes import Axes , SubplotBase , subplot_class_factory
36
36
from matplotlib .gridspec import GridSpec
37
+ from matplotlib .layout_engine import (constrained_layout_engine ,
38
+ tight_layout_engine , LayoutEngine )
37
39
import matplotlib .legend as mlegend
38
40
from matplotlib .patches import Rectangle
39
41
from matplotlib .text import Text
40
42
from matplotlib .transforms import (Affine2D , Bbox , BboxTransformTo ,
41
43
TransformedBbox )
42
-
43
44
_log = logging .getLogger (__name__ )
44
45
45
46
@@ -1136,12 +1137,15 @@ def colorbar(self, mappable, cax=None, ax=None, use_gridspec=True, **kw):
1136
1137
"silence this warning, explicitly pass the 'ax' argument "
1137
1138
"to colorbar()." )
1138
1139
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
1139
1144
# Store the value of gca so that we can set it back later on.
1140
1145
if cax is None :
1141
1146
current_ax = self .gca ()
1142
1147
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 )):
1145
1149
cax , kw = cbar .make_axes_gridspec (ax , ** kw )
1146
1150
else :
1147
1151
cax , kw = cbar .make_axes (ax , ** kw )
@@ -1189,12 +1193,12 @@ def subplots_adjust(self, left=None, bottom=None, right=None, top=None,
1189
1193
The height of the padding between subplots,
1190
1194
as a fraction of the average Axes height.
1191
1195
"""
1192
- if self .get_constrained_layout ():
1193
- self .set_constrained_layout ( False )
1196
+ if ( self .layout_engine is not None and
1197
+ not self .layout_engine . adjust_compatible ):
1194
1198
_api .warn_external (
1195
- "This figure was using constrained_layout, but that is "
1199
+ "This figure was using a layout engine that is "
1196
1200
"incompatible with subplots_adjust and/or tight_layout; "
1197
- "disabling constrained_layout ." )
1201
+ "not calling subplots_adjust ." )
1198
1202
self .subplotpars .update (left , bottom , right , top , wspace , hspace )
1199
1203
for ax in self .axes :
1200
1204
if hasattr (ax , 'get_subplotspec' ):
@@ -2068,6 +2072,9 @@ def get_constrained_layout_pads(self, relative=False):
2068
2072
"""
2069
2073
return self ._parent .get_constrained_layout_pads (relative = relative )
2070
2074
2075
+ def get_layout_engine (self ):
2076
+ return self ._parent .get_layout_engine ()
2077
+
2071
2078
@property
2072
2079
def axes (self ):
2073
2080
"""
@@ -2224,24 +2231,31 @@ def __init__(self,
2224
2231
%(Figure:kwdoc)s
2225
2232
"""
2226
2233
super ().__init__ (** kwargs )
2227
-
2234
+ self . layout_engine = None
2228
2235
if layout is not None :
2229
2236
if tight_layout is not None :
2230
2237
_api .warn_external (
2231
2238
"The Figure parameters 'layout' and 'tight_layout' "
2232
2239
"cannot be used together. Please use 'layout' only." )
2233
- if constrained_layout is not None :
2240
+ elif constrained_layout is not None :
2234
2241
_api .warn_external (
2235
2242
"The Figure parameters 'layout' and 'constrained_layout' "
2236
2243
"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 )
2244
+ _api .check_in_list (['constrained' , 'tight' ], layout = layout )
2245
+ self .set_layout_engine (layout = layout )
2246
+ elif tight_layout is not None :
2247
+ if constrained_layout is not None :
2248
+ _api .warn_external (
2249
+ "The Figure parameters 'tight_layout' and "
2250
+ "'constrained_layout' cannot be used together. Please use "
2251
+ "'layout' parameter" )
2252
+ self .set_layout_engine (layout = 'tight' )
2253
+ if isinstance (tight_layout , dict ):
2254
+ self .get_layout_engine ().set_info (tight_layout )
2255
+ elif constrained_layout is not None :
2256
+ self .set_layout_engine (layout = 'constrained' )
2257
+ if isinstance (constrained_layout , dict ):
2258
+ self .get_layout_engine ().set_info (constrained_layout )
2245
2259
2246
2260
self .callbacks = cbook .CallbackRegistry ()
2247
2261
# Callbacks traditionally associated with the canvas (and exposed with
@@ -2292,20 +2306,37 @@ def __init__(self,
2292
2306
2293
2307
self .subplotpars = subplotpars
2294
2308
2295
- # constrained_layout:
2296
- self ._constrained = False
2297
-
2298
- self .set_tight_layout (tight_layout )
2299
-
2300
2309
self ._axstack = _AxesStack () # track all figure axes and current axes
2301
2310
self .clf ()
2302
2311
self ._cachedRenderer = None
2303
2312
2304
- self .set_constrained_layout (constrained_layout )
2305
-
2306
2313
# list of child gridspecs for this figure
2307
2314
self ._gridspecs = []
2308
2315
2316
+ def set_layout_engine (self , layout = None , ** kwargs ):
2317
+ """
2318
+ Set the layout engine... FIXME
2319
+ """
2320
+ if layout is None :
2321
+ if mpl .rcParams ['figure.autolayout' ]:
2322
+ layout = 'tight'
2323
+ elif mpl .rcParams ['figure.constrained_layout.use' ]:
2324
+ layout = 'constrained'
2325
+ else :
2326
+ self .layout_engine = None
2327
+ return
2328
+ if layout == 'tight' :
2329
+ self .layout_engine = tight_layout_engine (self , ** kwargs )
2330
+ elif layout == 'constrained' :
2331
+ self .layout_engine = constrained_layout_engine (self , ** kwargs )
2332
+ elif isinstance (layout , LayoutEngine ):
2333
+ self .layout_engine = layout
2334
+ else :
2335
+ raise ValueError (f"Invalid value for 'layout': { layout !r} " )
2336
+
2337
+ def get_layout_engine (self ):
2338
+ return self .layout_engine
2339
+
2309
2340
# TODO: I'd like to dynamically add the _repr_html_ method
2310
2341
# to the figure in the right context, but then IPython doesn't
2311
2342
# use it, for some reason.
@@ -2395,8 +2426,9 @@ def _set_dpi(self, dpi, forward=True):
2395
2426
2396
2427
def get_tight_layout (self ):
2397
2428
"""Return whether `.tight_layout` is called when drawing."""
2398
- return self ._tight
2429
+ return isinstance ( self .layout_engine , tight_layout_engine )
2399
2430
2431
+ @_api .deprecated ("3.6" , alternative = "set_layout_engine" )
2400
2432
def set_tight_layout (self , tight ):
2401
2433
"""
2402
2434
Set whether and how `.tight_layout` is called when drawing.
@@ -2411,8 +2443,9 @@ def set_tight_layout(self, tight):
2411
2443
"""
2412
2444
if tight is None :
2413
2445
tight = mpl .rcParams ['figure.autolayout' ]
2414
- self ._tight = bool (tight )
2415
- self ._tight_parameters = tight if isinstance (tight , dict ) else {}
2446
+ _tight_parameters = tight if isinstance (tight , dict ) else {}
2447
+ if bool (tight ):
2448
+ self .layout_engine = tight_layout_engine (self , _tight_parameters )
2416
2449
self .stale = True
2417
2450
2418
2451
def get_constrained_layout (self ):
@@ -2421,8 +2454,9 @@ def get_constrained_layout(self):
2421
2454
2422
2455
See :doc:`/tutorials/intermediate/constrainedlayout_guide`.
2423
2456
"""
2424
- return self ._constrained
2457
+ return isinstance ( self .layout_engine , constrained_layout_engine )
2425
2458
2459
+ @_api .deprecated ("3.6" , alternative = "set_layout_engine('constrained')" )
2426
2460
def set_constrained_layout (self , constrained ):
2427
2461
"""
2428
2462
Set whether ``constrained_layout`` is used upon drawing. If None,
@@ -2439,22 +2473,17 @@ def set_constrained_layout(self, constrained):
2439
2473
----------
2440
2474
constrained : bool or dict or None
2441
2475
"""
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
2447
2476
if constrained is None :
2448
2477
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 ()
2478
+ _constrained = bool (constrained )
2479
+ _parameters = constrained if isinstance (constrained , dict ) else {}
2480
+ if _constrained :
2481
+ self .layout_engine = constrained_layout_engine (self , _parameters )
2454
2482
self .stale = True
2455
2483
2456
- def set_constrained_layout_pads (self , * , w_pad = None , h_pad = None ,
2457
- wspace = None , hspace = None ):
2484
+ @_api .deprecated (
2485
+ "3.6" , alternative = "figure.layout_engine.set_info()" )
2486
+ def set_constrained_layout_pads (self , ** kwargs ):
2458
2487
"""
2459
2488
Set padding for ``constrained_layout``.
2460
2489
@@ -2482,18 +2511,13 @@ def set_constrained_layout_pads(self, *, w_pad=None, h_pad=None,
2482
2511
subplot width. The total padding ends up being h_pad + hspace.
2483
2512
2484
2513
"""
2514
+ if isinstance (self .layout_engine , constrained_layout_engine ):
2515
+ self .layout_engine .set_info (** kwargs )
2485
2516
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
-
2517
+ @_api .deprecated ("3.6" , alternative = "fig.layout_engine.get_info()" )
2494
2518
def get_constrained_layout_pads (self , relative = False ):
2495
2519
"""
2496
- Get padding for ``constrained_layout``.
2520
+ Get padding for ``constrained_layout`` if it is the ``layout_engine`` .
2497
2521
2498
2522
Returns a list of ``w_pad, h_pad`` in inches and
2499
2523
``wspace`` and ``hspace`` as fractions of the subplot.
@@ -2505,13 +2529,16 @@ def get_constrained_layout_pads(self, relative=False):
2505
2529
relative : bool
2506
2530
If `True`, then convert from inches to figure relative.
2507
2531
"""
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' ]
2532
+ if not isinstance (self .layout_engine , constrained_layout_engine ):
2533
+ return None , None , None , None
2534
+ info = self .layout_engine .get_info ()
2535
+ w_pad = info ['w_pad' ]
2536
+ h_pad = info ['h_pad' ]
2537
+ wspace = info ['wspace' ]
2538
+ hspace = info ['hspace' ]
2512
2539
2513
2540
if relative and (w_pad is not None or h_pad is not None ):
2514
- renderer = _get_renderer (self )
2541
+ renderer = _get_renderer (self ). dpi
2515
2542
dpi = renderer .dpi
2516
2543
w_pad = w_pad * dpi / renderer .width
2517
2544
h_pad = h_pad * dpi / renderer .height
@@ -2783,14 +2810,11 @@ def draw(self, renderer):
2783
2810
return
2784
2811
2785
2812
artists = self ._get_draw_artists (renderer )
2786
-
2787
2813
try :
2788
2814
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 :
2792
2816
try :
2793
- self .tight_layout ( ** self . _tight_parameters )
2817
+ self .layout_engine . execute ( )
2794
2818
except ValueError :
2795
2819
pass
2796
2820
# ValueError can occur when resizing a window.
@@ -3123,6 +3147,7 @@ def handler(ev):
3123
3147
3124
3148
return None if event is None else event .name == "key_press_event"
3125
3149
3150
+ @_api .deprecated ("3.6" , alternative = "figure.layout_engine.execute()" )
3126
3151
def execute_constrained_layout (self , renderer = None ):
3127
3152
"""
3128
3153
Use ``layoutgrid`` to determine pos positions within Axes.
@@ -3133,22 +3158,11 @@ def execute_constrained_layout(self, renderer=None):
3133
3158
-------
3134
3159
layoutgrid : private debugging object
3135
3160
"""
3161
+ if not isinstance (self .layout_engine , constrained_layout_engine ):
3162
+ return None
3163
+ return self .layout_engine .execute ()
3136
3164
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 ):
3152
3166
"""
3153
3167
Adjust the padding between and around subplots.
3154
3168
@@ -3170,24 +3184,19 @@ def tight_layout(self, *, pad=1.08, h_pad=None, w_pad=None, rect=None):
3170
3184
3171
3185
See Also
3172
3186
--------
3173
- .Figure.set_tight_layout
3187
+ .Figure.set_layout_engine
3174
3188
.pyplot.tight_layout
3175
3189
"""
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
3179
3191
subplotspec_list = get_subplotspec_list (self .axes )
3180
3192
if None in subplotspec_list :
3181
3193
_api .warn_external ("This figure includes Axes that are not "
3182
3194
"compatible with tight_layout, so results "
3183
3195
"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 ()
3191
3200
3192
3201
3193
3202
def figaspect (arg ):
0 commit comments