@@ -456,8 +456,15 @@ def update(self, **kwargs):
456456 self .set_height_ratios (hratios )
457457
458458 # Validate args
459- kwargs .pop ('ncols' , None )
460- kwargs .pop ('nrows' , None )
459+ nrows = kwargs .pop ('nrows' , None )
460+ ncols = kwargs .pop ('ncols' , None )
461+ nrows_current , ncols_current = self .get_active_geometry ()
462+ if (nrows is not None and nrows != nrows_current ) or (
463+ ncols is not None and ncols != ncols_current ):
464+ raise ValueError (
465+ f'Input geometry { (nrows , ncols )} does not match '
466+ f'current geometry { (nrows_current , ncols_current )} .'
467+ )
461468 self .left = kwargs .pop ('left' , None )
462469 self .right = kwargs .pop ('right' , None )
463470 self .bottom = kwargs .pop ('bottom' , None )
@@ -476,8 +483,8 @@ def update(self, **kwargs):
476483
477484def _canvas_preprocess (canvas , method ):
478485 """Return a pre-processer that can be used to override instance-level
479- canvas draw () and print_figure() methods. This applies tight layout and
480- aspect ratio-conserving adjustments and aligns labels. Required so that
486+ canvas draw_idle () and print_figure() methods. This applies tight layout
487+ and aspect ratio-conserving adjustments and aligns labels. Required so that
481488 the canvas methods instantiate renderers with the correct dimensions.
482489 Note that MacOSX currently `cannot be resized \
483490 <https://github.com/matplotlib/matplotlib/issues/15131>`__."""
@@ -491,28 +498,30 @@ def _canvas_preprocess(canvas, method):
491498 # override bbox and bbox_inches as *properties*, but these are really
492499 # complicated, dangerous, and result in unnecessary extra draws.
493500 def _preprocess (self , * args , ** kwargs ):
494- if method == 'draw_idle' and self ._is_idle_drawing :
495- return # copied from source code
496501 fig = self .figure # update even if not stale! needed after saves
497- if fig .stale and method == 'print_figure' :
498- # Needed for displaying already-drawn inline figures, for
499- # some reason tight layout algorithm gets it wrong otherwise.
500- # Concerned that draw_idle() might wait until after
501- # print_figure() is done, so we use draw().
502+ if method == 'print_figure' :
503+ # When re-generating inline figures, the tight layout algorithm
504+ # can get figure size *or* spacing wrong unless we force additional
505+ # draw! Seems to have no adverse effects when calling savefig.
502506 self .draw ()
503- renderer = fig ._get_renderer () # any renderer will do for now
504- for ax in fig ._iter_axes ():
505- ax ._draw_auto_legends_colorbars () # may insert panels
506- if rc ['backend' ] != 'nbAgg' :
507- fig ._adjust_aspect () # resizes figure
508- if fig ._auto_tight_layout :
509- fig ._align_axislabels (False ) # get proper label offset only
510- fig ._align_labels (renderer ) # position labels and suptitle
511- fig ._adjust_tight_layout (renderer )
512- fig ._align_axislabels (True ) # slide spanning labels across
513- fig ._align_labels (renderer ) # update figure-relative coordinates!
514- res = getattr (type (self ), method )(self , * args , ** kwargs )
515- return res
507+ if fig ._is_preprocessing :
508+ return
509+ with fig ._context_preprocessing ():
510+ renderer = fig ._get_renderer () # any renderer will do for now
511+ for ax in fig ._iter_axes ():
512+ ax ._draw_auto_legends_colorbars () # may insert panels
513+ resize = rc ['backend' ] != 'nbAgg'
514+ if resize :
515+ fig ._adjust_aspect () # resizes figure
516+ if fig ._auto_tight :
517+ fig ._adjust_tight_layout (renderer , resize = resize )
518+ fig ._align_axislabels (True )
519+ fig ._align_labels (renderer )
520+ fallback = _notNone (
521+ fig ._fallback_to_cm , rc ['mathtext.fallback_to_cm' ]
522+ )
523+ with rc .context ({'mathtext.fallback_to_cm' : fallback }):
524+ return getattr (type (self ), method )(self , * args , ** kwargs )
516525 return _preprocess .__get__ (canvas ) # ...I don't get it either
517526
518527
@@ -714,6 +723,7 @@ def _subplots_geometry(**kwargs):
714723class _hidelabels (object ):
715724 """Hide objects temporarily so they are ignored by the tight bounding box
716725 algorithm."""
726+ # NOTE: This will be removed when labels are implemented with AxesStack!
717727 def __init__ (self , * args ):
718728 self ._labels = args
719729
@@ -811,7 +821,7 @@ def __init__(self,
811821 self ._axpad = units (_notNone (axpad , rc ['subplots.axpad' ]))
812822 self ._panelpad = units (_notNone (panelpad , rc ['subplots.panelpad' ]))
813823 self ._auto_format = autoformat
814- self ._auto_tight_layout = _notNone (tight , rc ['tight' ])
824+ self ._auto_tight = _notNone (tight , rc ['tight' ])
815825 self ._include_panels = includepanels
816826 self ._fallback_to_cm = fallback_to_cm
817827 self ._ref_num = ref
@@ -996,44 +1006,51 @@ def _adjust_aspect(self):
9961006 return
9971007 ax = self ._axes_main [self ._ref_num - 1 ]
9981008 mode = ax .get_aspect ()
999- aspect = None
1000- if mode == 'equal' :
1001- xscale , yscale = ax .get_xscale (), ax .get_yscale ()
1002- if xscale == 'linear' and yscale == 'linear' :
1003- aspect = 1.0 / ax .get_data_ratio ()
1004- elif xscale == 'log' and yscale == 'log' :
1005- aspect = 1.0 / ax .get_data_ratio_log ()
1006- else :
1007- pass # matplotlib issues warning, forces aspect == 'auto'
1008- # Apply aspect
1009- if aspect is not None :
1010- aspect = round (aspect * 1e10 ) * 1e-10
1011- subplots_kw = self ._subplots_kw
1012- aspect_prev = round (subplots_kw ['aspect' ] * 1e10 ) * 1e-10
1013- if aspect != aspect_prev :
1014- subplots_kw ['aspect' ] = aspect
1015- figsize , gridspec_kw , _ = _subplots_geometry (** subplots_kw )
1016- self .set_size_inches (figsize , manual = False )
1017- self ._gridspec_main .update (** gridspec_kw )
1018-
1019- def _adjust_tight_layout (self , renderer ):
1009+ if mode != 'equal' :
1010+ return
1011+
1012+ # Compare to current aspect
1013+ subplots_kw = self ._subplots_kw
1014+ xscale , yscale = ax .get_xscale (), ax .get_yscale ()
1015+ if xscale == 'linear' and yscale == 'linear' :
1016+ aspect = 1.0 / ax .get_data_ratio ()
1017+ elif xscale == 'log' and yscale == 'log' :
1018+ aspect = 1.0 / ax .get_data_ratio_log ()
1019+ else :
1020+ pass # matplotlib issues warning, forces aspect == 'auto'
1021+ aspect = round (aspect * 1e10 ) * 1e-10
1022+ aspect_prev = round (subplots_kw ['aspect' ] * 1e10 ) * 1e-10
1023+ if aspect == aspect_prev :
1024+ return
1025+
1026+ # Apply new aspect
1027+ subplots_kw ['aspect' ] = aspect
1028+ figsize , gridspec_kw , _ = _subplots_geometry (** subplots_kw )
1029+ self .set_size_inches (figsize , auto = True )
1030+ self ._gridspec_main .update (** gridspec_kw )
1031+
1032+ def _adjust_tight_layout (self , renderer , resize = True ):
10201033 """Applies tight layout scaling that permits flexible figure
10211034 dimensions and preserves panel widths and subplot aspect ratios.
10221035 The `renderer` should be a `~matplotlib.backend_bases.RendererBase`
10231036 instance."""
10241037 # Initial stuff
10251038 axs = self ._iter_axes ()
1026- obox = self .bbox_inches # original bbox
1027- bbox = self .get_tightbbox (renderer )
1028- gridspec = self ._gridspec_main
10291039 subplots_kw = self ._subplots_kw
10301040 subplots_orig_kw = self ._subplots_orig_kw # tight layout overrides
10311041 if not axs or not subplots_kw or not subplots_orig_kw :
10321042 return
10331043
1044+ # Temporarily disable spanning labels and get correct
1045+ # positions for labels and suptitle
1046+ self ._align_axislabels (False )
1047+ self ._align_labels (renderer )
1048+
10341049 # Tight box *around* figure
10351050 # Get bounds from old bounding box
10361051 pad = self ._pad
1052+ obox = self .bbox_inches # original bbox
1053+ bbox = self .get_tightbbox (renderer )
10371054 left = bbox .xmin
10381055 bottom = bbox .ymin
10391056 right = obox .xmax - bbox .xmax
@@ -1051,6 +1068,7 @@ def _adjust_tight_layout(self, renderer):
10511068 # Get arrays storing gridspec spacing args
10521069 axpad = self ._axpad
10531070 panelpad = self ._panelpad
1071+ gridspec = self ._gridspec_main
10541072 nrows , ncols = gridspec .get_active_geometry ()
10551073 wspace , hspace = subplots_kw ['wspace' ], subplots_kw ['hspace' ]
10561074 wspace_orig = subplots_orig_kw ['wspace' ]
@@ -1125,13 +1143,20 @@ def _adjust_tight_layout(self, renderer):
11251143 jspace [i ] = space
11261144 spaces .append (jspace )
11271145
1128- # Apply new spaces
1146+ # Update geometry solver kwargs
11291147 subplots_kw .update ({
11301148 'wspace' : spaces [0 ], 'hspace' : spaces [1 ],
11311149 })
1150+ if not resize :
1151+ width , height = self .get_size_inches ()
1152+ subplots_kw = subplots_kw .copy ()
1153+ subplots_kw .update (width = width , height = height )
1154+
1155+ # Apply new spacing
11321156 figsize , gridspec_kw , _ = _subplots_geometry (** subplots_kw )
1157+ if resize :
1158+ self .set_size_inches (figsize , auto = True )
11331159 self ._gridspec_main .update (** gridspec_kw )
1134- self .set_size_inches (figsize , manual = False )
11351160
11361161 def _align_axislabels (self , b = True ):
11371162 """Align spanning *x* and *y* axis labels in the perpendicular
@@ -1411,7 +1436,7 @@ def _insert_row_column(
14111436
14121437 # Update figure
14131438 figsize , gridspec_kw , _ = _subplots_geometry (** subplots_kw )
1414- self .set_size_inches (figsize , manual = False )
1439+ self .set_size_inches (figsize , auto = True )
14151440 if exists :
14161441 gridspec = self ._gridspec_main
14171442 gridspec .update (** gridspec_kw )
@@ -1626,16 +1651,17 @@ def set_canvas(self, canvas):
16261651 # `~matplotlib.backend_bases.FigureCanvasBase.print_figure`
16271652 # methods. The latter is called by save() and by the inline backend.
16281653 # See `_canvas_preprocess` for details."""
1629- # NOTE: Use draw_idle() rather than draw() becuase latter is not
1630- # always called! For example, MacOSX uses _draw() and nbAgg does
1631- # not call draw() *or* _draw()! Not sure how it works actually.
1632- # Should be same because we piggyback draw() which *itself* defers
1633- # the event. Just make sure to check _is_idle_drawing!
1634- canvas .draw_idle = _canvas_preprocess (canvas , 'draw_idle' )
1654+ # NOTE: Cannot use draw_idle() because it causes *major* complications
1655+ # for qt5 backend. Even though usage is less consistent we *must*
1656+ # use draw() and _draw().
1657+ if hasattr (canvas , '_draw' ):
1658+ canvas ._draw = _canvas_preprocess (canvas , '_draw' )
1659+ else :
1660+ canvas .draw = _canvas_preprocess (canvas , 'draw' )
16351661 canvas .print_figure = _canvas_preprocess (canvas , 'print_figure' )
16361662 super ().set_canvas (canvas )
16371663
1638- def set_size_inches (self , w , h = None , forward = True , manual = True ):
1664+ def set_size_inches (self , w , h = None , forward = True , auto = False ):
16391665 # Set the figure size and, if this is being called manually or from
16401666 # an interactive backend, override the geometry tracker so users can
16411667 # use interactive backends. See #76. Undocumented because this is
@@ -1656,11 +1682,19 @@ def set_size_inches(self, w, h=None, forward=True, manual=True):
16561682 width_true , height_true = self .get_size_inches ()
16571683 width_trunc = int (self .bbox .width ) / self .dpi
16581684 height_trunc = int (self .bbox .height ) / self .dpi
1659- if (manual # have actually seen (width_true, heigh_trunc)!
1660- and width not in (width_true , width_trunc )
1661- and height not in (height_true , height_trunc )):
1662- self ._subplots_kw .update (width = width , height = height )
1663- super ().set_size_inches (width , height , forward = forward )
1685+ if auto :
1686+ with self ._context_resizing ():
1687+ super ().set_size_inches (width , height , forward = forward )
1688+ else :
1689+ if ( # can have internal resizing not associated with any draws
1690+ (width not in (width_true , width_trunc )
1691+ or height not in (height_true , height_trunc ))
1692+ and not self ._is_resizing
1693+ and not self .canvas ._is_idle_drawing # standard
1694+ and not getattr (self .canvas , '_draw_pending' , None ) # pyqt5
1695+ ):
1696+ self ._subplots_kw .update (width = width , height = height )
1697+ super ().set_size_inches (width , height , forward = forward )
16641698
16651699 def _iter_axes (self ):
16661700 """Iterates over all axes and panels in the figure belonging to the
0 commit comments