diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index fff6e763a6c8..d360eb66164e 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -19,6 +19,8 @@ from matplotlib import _api import matplotlib.transforms as mtransforms +import matplotlib._layoutgrid as mlayoutgrid + _log = logging.getLogger(__name__) @@ -83,20 +85,19 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad, A value of 0.2 for a three-column layout would have a space of 0.1 of the figure width between each column. If h/wspace < h/w_pad, then the pads are used instead. + + Returns + ------- + layoutgrid : private debugging structure """ - # list of unique gridspecs that contain child axes: - gss = set() - for ax in fig.axes: - if hasattr(ax, 'get_subplotspec'): - gs = ax.get_subplotspec().get_gridspec() - if gs._layoutgrid is not None: - gss.add(gs) - gss = list(gss) - if len(gss) == 0: + # make layoutgrid tree... + layoutgrids = make_layoutgrids(fig, None) + if not layoutgrids['hasgrids']: _api.warn_external('There are no gridspecs with layoutgrids. ' 'Possibly did not call parent GridSpec with the' ' "figure" keyword') + return for _ in range(2): # do the algorithm twice. This has to be done because decorations @@ -106,42 +107,132 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad, # make margins for all the axes and subfigures in the # figure. Add margins for colorbars... - _make_layout_margins(fig, renderer, h_pad=h_pad, w_pad=w_pad, - hspace=hspace, wspace=wspace) - _make_margin_suptitles(fig, renderer, h_pad=h_pad, w_pad=w_pad) + make_layout_margins(layoutgrids, fig, renderer, h_pad=h_pad, + w_pad=w_pad, hspace=hspace, wspace=wspace) + make_margin_suptitles(layoutgrids, fig, renderer, h_pad=h_pad, + w_pad=w_pad) # if a layout is such that a columns (or rows) margin has no # constraints, we need to make all such instances in the grid # match in margin size. - _match_submerged_margins(fig) + match_submerged_margins(layoutgrids, fig) # update all the variables in the layout. - fig._layoutgrid.update_variables() + layoutgrids[fig].update_variables() - if _check_no_collapsed_axes(fig): - _reposition_axes(fig, renderer, h_pad=h_pad, w_pad=w_pad, - hspace=hspace, wspace=wspace) + if check_no_collapsed_axes(layoutgrids, fig): + reposition_axes(layoutgrids, fig, renderer, h_pad=h_pad, + w_pad=w_pad, hspace=hspace, wspace=wspace) else: _api.warn_external('constrained_layout not applied because ' 'axes sizes collapsed to zero. Try making ' 'figure larger or axes decorations smaller.') - _reset_margins(fig) + reset_margins(layoutgrids, fig) + return layoutgrids + + +def make_layoutgrids(fig, layoutgrids): + """ + Make the layoutgrid tree. + + (Sub)Figures get a layoutgrid so we can have figure margins. + + Gridspecs that are attached to axes get a layoutgrid so axes + can have margins. + """ + + if layoutgrids is None: + layoutgrids = dict() + layoutgrids['hasgrids'] = False + if not hasattr(fig, '_parent'): + # top figure + layoutgrids[fig] = mlayoutgrid.LayoutGrid(parent=None, name='figlb') + else: + # subfigure + gs = fig._subplotspec.get_gridspec() + # it is possible the gridspec containing this subfigure hasn't + # been added to the tree yet: + layoutgrids = make_layoutgrids_gs(layoutgrids, gs) + # add the layoutgrid for the subfigure: + parentlb = layoutgrids[gs] + layoutgrids[fig] = mlayoutgrid.LayoutGrid( + parent=parentlb, + name='panellb', + parent_inner=True, + nrows=1, ncols=1, + parent_pos=(fig._subplotspec.rowspan, + fig._subplotspec.colspan)) + # recursively do all subfigures in this figure... + for sfig in fig.subfigs: + layoutgrids = make_layoutgrids(sfig, layoutgrids) + + # for each axes at the local level add its gridspec: + for ax in fig._localaxes.as_list(): + if hasattr(ax, 'get_subplotspec'): + gs = ax.get_subplotspec().get_gridspec() + layoutgrids = make_layoutgrids_gs(layoutgrids, gs) + + return layoutgrids -def _check_no_collapsed_axes(fig): +def make_layoutgrids_gs(layoutgrids, gs): + """ + Make the layoutgrid for a gridspec (and anything nested in the gridspec) + """ + + if gs in layoutgrids or gs.figure is None: + return layoutgrids + # in order to do constrained_layout there has to be at least *one* + # gridspec in the tree: + layoutgrids['hasgrids'] = True + if not hasattr(gs, '_subplot_spec'): + # normal gridspec + parent = layoutgrids[gs.figure] + layoutgrids[gs] = mlayoutgrid.LayoutGrid( + parent=parent, + parent_inner=True, + name='gridspec', + ncols=gs._ncols, nrows=gs._nrows, + width_ratios=gs.get_width_ratios(), + height_ratios=gs.get_height_ratios()) + else: + # this is a gridspecfromsubplotspec: + subplot_spec = gs._subplot_spec + parentgs = subplot_spec.get_gridspec() + # if a nested gridspec it is possible the parent is not in there yet: + if parentgs not in layoutgrids: + layoutgrids = make_layoutgrids_gs(layoutgrids, parentgs) + subspeclb = layoutgrids[parentgs] + # gridspecfromsubplotspec need an outer container: + if f'{gs}top' not in layoutgrids: + layoutgrids[f'{gs}top'] = mlayoutgrid.LayoutGrid( + parent=subspeclb, + name='top', + nrows=1, ncols=1, + parent_pos=(subplot_spec.rowspan, subplot_spec.colspan)) + layoutgrids[gs] = mlayoutgrid.LayoutGrid( + parent=layoutgrids[f'{gs}top'], + name='gridspec', + nrows=gs._nrows, ncols=gs._ncols, + width_ratios=gs.get_width_ratios(), + height_ratios=gs.get_height_ratios()) + return layoutgrids + + +def check_no_collapsed_axes(layoutgrids, fig): """ Check that no axes have collapsed to zero size. """ - for panel in fig.subfigs: - ok = _check_no_collapsed_axes(panel) + for sfig in fig.subfigs: + ok = check_no_collapsed_axes(layoutgrids, sfig) if not ok: return False for ax in fig.axes: if hasattr(ax, 'get_subplotspec'): gs = ax.get_subplotspec().get_gridspec() - lg = gs._layoutgrid - if lg is not None: + if gs in layoutgrids: + lg = layoutgrids[gs] for i in range(gs.nrows): for j in range(gs.ncols): bb = lg.get_inner_bbox(i, j) @@ -150,8 +241,8 @@ def _check_no_collapsed_axes(fig): return True -def _get_margin_from_padding(obj, *, w_pad=0, h_pad=0, - hspace=0, wspace=0): +def get_margin_from_padding(obj, *, w_pad=0, h_pad=0, + hspace=0, wspace=0): ss = obj._subplotspec gs = ss.get_gridspec() @@ -188,8 +279,8 @@ def _get_margin_from_padding(obj, *, w_pad=0, h_pad=0, return margin -def _make_layout_margins(fig, renderer, *, w_pad=0, h_pad=0, - hspace=0, wspace=0): +def make_layout_margins(layoutgrids, fig, renderer, *, w_pad=0, h_pad=0, + hspace=0, wspace=0): """ For each axes, make a margin between the *pos* layoutbox and the *axes* layoutbox be a minimum size that can accommodate the @@ -197,14 +288,15 @@ def _make_layout_margins(fig, renderer, *, w_pad=0, h_pad=0, Then make room for colorbars. """ - for panel in fig.subfigs: # recursively make child panel margins - ss = panel._subplotspec - _make_layout_margins(panel, renderer, w_pad=w_pad, h_pad=h_pad, - hspace=hspace, wspace=wspace) + for sfig in fig.subfigs: # recursively make child panel margins + ss = sfig._subplotspec + make_layout_margins(layoutgrids, sfig, renderer, + w_pad=w_pad, h_pad=h_pad, + hspace=hspace, wspace=wspace) - margins = _get_margin_from_padding(panel, w_pad=0, h_pad=0, - hspace=hspace, wspace=wspace) - panel._layoutgrid.parent.edit_outer_margin_mins(margins, ss) + margins = get_margin_from_padding(sfig, w_pad=0, h_pad=0, + hspace=hspace, wspace=wspace) + layoutgrids[sfig].parent.edit_outer_margin_mins(margins, ss) for ax in fig._localaxes.as_list(): if not hasattr(ax, 'get_subplotspec') or not ax.get_in_layout(): @@ -212,14 +304,13 @@ def _make_layout_margins(fig, renderer, *, w_pad=0, h_pad=0, ss = ax.get_subplotspec() gs = ss.get_gridspec() - nrows, ncols = gs.get_geometry() - if gs._layoutgrid is None: + if gs not in layoutgrids: return - margin = _get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad, - hspace=hspace, wspace=wspace) - pos, bbox = _get_pos_and_bbox(ax, renderer) + margin = get_margin_from_padding(ax, w_pad=w_pad, h_pad=h_pad, + hspace=hspace, wspace=wspace) + pos, bbox = get_pos_and_bbox(ax, renderer) # the margin is the distance between the bounding box of the axes # and its position (plus the padding from above) margin['left'] += pos.x0 - bbox.x0 @@ -232,11 +323,11 @@ def _make_layout_margins(fig, renderer, *, w_pad=0, h_pad=0, # padding margin, versus the margin for axes decorators. for cbax in ax._colorbars: # note pad is a fraction of the parent width... - pad = _colorbar_get_pad(cbax) + pad = colorbar_get_pad(layoutgrids, cbax) # colorbars can be child of more than one subplot spec: - cbp_rspan, cbp_cspan = _get_cb_parent_spans(cbax) + cbp_rspan, cbp_cspan = get_cb_parent_spans(cbax) loc = cbax._colorbar_info['location'] - cbpos, cbbbox = _get_pos_and_bbox(cbax, renderer) + cbpos, cbbbox = get_pos_and_bbox(cbax, renderer) if loc == 'right': if cbp_cspan.stop == ss.colspan.stop: # only increase if the colorbar is on the right edge @@ -269,10 +360,10 @@ def _make_layout_margins(fig, renderer, *, w_pad=0, h_pad=0, cbbbox.y1 > bbox.y1): margin['top'] += cbbbox.y1 - bbox.y1 # pass the new margins down to the layout grid for the solution... - gs._layoutgrid.edit_outer_margin_mins(margin, ss) + layoutgrids[gs].edit_outer_margin_mins(margin, ss) -def _make_margin_suptitles(fig, renderer, *, w_pad=0, h_pad=0): +def make_margin_suptitles(layoutgrids, fig, renderer, *, w_pad=0, h_pad=0): # Figure out how large the suptitle is and make the # top level figure margin larger. @@ -284,32 +375,34 @@ def _make_margin_suptitles(fig, renderer, *, w_pad=0, h_pad=0): h_pad_local = padbox.height w_pad_local = padbox.width - for panel in fig.subfigs: - _make_margin_suptitles(panel, renderer, w_pad=w_pad, h_pad=h_pad) + for sfig in fig.subfigs: + make_margin_suptitles(layoutgrids, sfig, renderer, + w_pad=w_pad, h_pad=h_pad) if fig._suptitle is not None and fig._suptitle.get_in_layout(): p = fig._suptitle.get_position() if getattr(fig._suptitle, '_autopos', False): fig._suptitle.set_position((p[0], 1 - h_pad_local)) bbox = inv_trans_fig(fig._suptitle.get_tightbbox(renderer)) - fig._layoutgrid.edit_margin_min('top', bbox.height + 2 * h_pad) + layoutgrids[fig].edit_margin_min('top', bbox.height + 2 * h_pad) if fig._supxlabel is not None and fig._supxlabel.get_in_layout(): p = fig._supxlabel.get_position() if getattr(fig._supxlabel, '_autopos', False): fig._supxlabel.set_position((p[0], h_pad_local)) bbox = inv_trans_fig(fig._supxlabel.get_tightbbox(renderer)) - fig._layoutgrid.edit_margin_min('bottom', bbox.height + 2 * h_pad) + layoutgrids[fig].edit_margin_min('bottom', + bbox.height + 2 * h_pad) if fig._supylabel is not None and fig._supylabel.get_in_layout(): p = fig._supylabel.get_position() if getattr(fig._supylabel, '_autopos', False): fig._supylabel.set_position((w_pad_local, p[1])) bbox = inv_trans_fig(fig._supylabel.get_tightbbox(renderer)) - fig._layoutgrid.edit_margin_min('left', bbox.width + 2 * w_pad) + layoutgrids[fig].edit_margin_min('left', bbox.width + 2 * w_pad) -def _match_submerged_margins(fig): +def match_submerged_margins(layoutgrids, fig): """ Make the margins that are submerged inside an Axes the same size. @@ -334,18 +427,18 @@ def _match_submerged_margins(fig): See test_constrained_layout::test_constrained_layout12 for an example. """ - for panel in fig.subfigs: - _match_submerged_margins(panel) + for sfig in fig.subfigs: + match_submerged_margins(layoutgrids, sfig) axs = [a for a in fig.get_axes() if (hasattr(a, 'get_subplotspec') and a.get_in_layout())] for ax1 in axs: ss1 = ax1.get_subplotspec() - lg1 = ss1.get_gridspec()._layoutgrid - if lg1 is None: + if ss1.get_gridspec() not in layoutgrids: axs.remove(ax1) continue + lg1 = layoutgrids[ss1.get_gridspec()] # interior columns: if len(ss1.colspan) > 1: @@ -359,7 +452,7 @@ def _match_submerged_margins(fig): ) for ax2 in axs: ss2 = ax2.get_subplotspec() - lg2 = ss2.get_gridspec()._layoutgrid + lg2 = layoutgrids[ss2.get_gridspec()] if lg2 is not None and len(ss2.colspan) > 1: maxsubl2 = np.max( lg2.margin_vals['left'][ss2.colspan[1:]] + @@ -389,7 +482,7 @@ def _match_submerged_margins(fig): for ax2 in axs: ss2 = ax2.get_subplotspec() - lg2 = ss2.get_gridspec()._layoutgrid + lg2 = layoutgrids[ss2.get_gridspec()] if lg2 is not None: if len(ss2.rowspan) > 1: maxsubt = np.max([np.max( @@ -406,7 +499,7 @@ def _match_submerged_margins(fig): lg1.edit_margin_min('bottom', maxsubb, cell=i) -def _get_cb_parent_spans(cbax): +def get_cb_parent_spans(cbax): """ Figure out which subplotspecs this colorbar belongs to: """ @@ -426,7 +519,7 @@ def _get_cb_parent_spans(cbax): return rowspan, colspan -def _get_pos_and_bbox(ax, renderer): +def get_pos_and_bbox(ax, renderer): """ Get the position and the bbox for the axes. @@ -459,18 +552,19 @@ def _get_pos_and_bbox(ax, renderer): return pos, bbox -def _reposition_axes(fig, renderer, *, w_pad=0, h_pad=0, hspace=0, wspace=0): +def reposition_axes(layoutgrids, fig, renderer, *, + w_pad=0, h_pad=0, hspace=0, wspace=0): """ Reposition all the axes based on the new inner bounding box. """ trans_fig_to_subfig = fig.transFigure - fig.transSubfigure for sfig in fig.subfigs: - bbox = sfig._layoutgrid.get_outer_bbox() + bbox = layoutgrids[sfig].get_outer_bbox() sfig._redo_transform_rel_fig( bbox=bbox.transformed(trans_fig_to_subfig)) - _reposition_axes(sfig, renderer, - w_pad=w_pad, h_pad=h_pad, - wspace=wspace, hspace=hspace) + reposition_axes(layoutgrids, sfig, renderer, + w_pad=w_pad, h_pad=h_pad, + wspace=wspace, hspace=hspace) for ax in fig._localaxes.as_list(): if not hasattr(ax, 'get_subplotspec') or not ax.get_in_layout(): @@ -481,10 +575,11 @@ def _reposition_axes(fig, renderer, *, w_pad=0, h_pad=0, hspace=0, wspace=0): ss = ax.get_subplotspec() gs = ss.get_gridspec() nrows, ncols = gs.get_geometry() - if gs._layoutgrid is None: + if gs not in layoutgrids: return - bbox = gs._layoutgrid.get_inner_bbox(rows=ss.rowspan, cols=ss.colspan) + bbox = layoutgrids[gs].get_inner_bbox(rows=ss.rowspan, + cols=ss.colspan) # transform from figure to panel for set_position: newbbox = trans_fig_to_subfig.transform_bbox(bbox) @@ -496,10 +591,11 @@ def _reposition_axes(fig, renderer, *, w_pad=0, h_pad=0, hspace=0, wspace=0): offset = {'left': 0, 'right': 0, 'bottom': 0, 'top': 0} for nn, cbax in enumerate(ax._colorbars[::-1]): if ax == cbax._colorbar_info['parents'][0]: - _reposition_colorbar(cbax, renderer, offset=offset) + reposition_colorbar(layoutgrids, cbax, renderer, + offset=offset) -def _reposition_colorbar(cbax, renderer, *, offset=None): +def reposition_colorbar(layoutgrids, cbax, renderer, *, offset=None): """ Place the colorbar in its new place. @@ -524,9 +620,10 @@ def _reposition_colorbar(cbax, renderer, *, offset=None): fig = cbax.figure trans_fig_to_subfig = fig.transFigure - fig.transSubfigure - cb_rspans, cb_cspans = _get_cb_parent_spans(cbax) - bboxparent = gs._layoutgrid.get_bbox_for_cb(rows=cb_rspans, cols=cb_cspans) - pb = gs._layoutgrid.get_inner_bbox(rows=cb_rspans, cols=cb_cspans) + cb_rspans, cb_cspans = get_cb_parent_spans(cbax) + bboxparent = layoutgrids[gs].get_bbox_for_cb(rows=cb_rspans, + cols=cb_cspans) + pb = layoutgrids[gs].get_inner_bbox(rows=cb_rspans, cols=cb_cspans) location = cbax._colorbar_info['location'] anchor = cbax._colorbar_info['anchor'] @@ -534,12 +631,12 @@ def _reposition_colorbar(cbax, renderer, *, offset=None): aspect = cbax._colorbar_info['aspect'] shrink = cbax._colorbar_info['shrink'] - cbpos, cbbbox = _get_pos_and_bbox(cbax, renderer) + cbpos, cbbbox = get_pos_and_bbox(cbax, renderer) # Colorbar gets put at extreme edge of outer bbox of the subplotspec # It needs to be moved in by: 1) a pad 2) its "margin" 3) by # any colorbars already added at this location: - cbpad = _colorbar_get_pad(cbax) + cbpad = colorbar_get_pad(layoutgrids, cbax) if location in ('left', 'right'): # fraction and shrink are fractions of parent pbcb = pb.shrunk(fraction, shrink).anchored(anchor, pb) @@ -583,30 +680,30 @@ def _reposition_colorbar(cbax, renderer, *, offset=None): return offset -def _reset_margins(fig): +def reset_margins(layoutgrids, fig): """ Reset the margins in the layoutboxes of fig. Margins are usually set as a minimum, so if the figure gets smaller the minimum needs to be zero in order for it to grow again. """ - for span in fig.subfigs: - _reset_margins(span) + for sfig in fig.subfigs: + reset_margins(layoutgrids, sfig) for ax in fig.axes: if hasattr(ax, 'get_subplotspec') and ax.get_in_layout(): ss = ax.get_subplotspec() gs = ss.get_gridspec() - if gs._layoutgrid is not None: - gs._layoutgrid.reset_margins() - fig._layoutgrid.reset_margins() + if gs in layoutgrids: + layoutgrids[gs].reset_margins() + layoutgrids[fig].reset_margins() -def _colorbar_get_pad(cax): +def colorbar_get_pad(layoutgrids, cax): parents = cax._colorbar_info['parents'] gs = parents[0].get_gridspec() - cb_rspans, cb_cspans = _get_cb_parent_spans(cax) - bboxouter = gs._layoutgrid.get_inner_bbox(rows=cb_rspans, cols=cb_cspans) + cb_rspans, cb_cspans = get_cb_parent_spans(cax) + bboxouter = layoutgrids[gs].get_inner_bbox(rows=cb_rspans, cols=cb_cspans) if cax._colorbar_info['location'] in ['right', 'left']: size = bboxouter.width diff --git a/lib/matplotlib/_layoutgrid.py b/lib/matplotlib/_layoutgrid.py index e46b3fe8c062..80a0ee2c86fb 100644 --- a/lib/matplotlib/_layoutgrid.py +++ b/lib/matplotlib/_layoutgrid.py @@ -22,7 +22,6 @@ import numpy as np from matplotlib.transforms import Bbox - _log = logging.getLogger(__name__) @@ -39,7 +38,9 @@ def __init__(self, parent=None, parent_pos=(0, 0), self.parent = parent self.parent_pos = parent_pos self.parent_inner = parent_inner - self.name = name + self.name = name + seq_id() + if parent is not None: + self.name = f'{parent.name}.{self.name}' self.nrows = nrows self.ncols = ncols self.height_ratios = np.atleast_1d(height_ratios) @@ -508,13 +509,14 @@ def print_children(lb): print_children(child) -def plot_children(fig, lg, level=0, printit=False): +def plot_children(fig, lg=None, level=0, printit=False): """Simple plotting to show where boxes are.""" import matplotlib.pyplot as plt import matplotlib.patches as mpatches - fig.canvas.draw() - + if lg is None: + _layoutgrids = fig.execute_constrained_layout() + lg = _layoutgrids[fig] colors = plt.rcParams["axes.prop_cycle"].by_key()["color"] col = colors[level] for i in range(lg.nrows): diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 60d3675325b4..f1a6711cb678 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -39,7 +39,6 @@ from matplotlib.text import Text from matplotlib.transforms import (Affine2D, Bbox, BboxTransformTo, TransformedBbox) -import matplotlib._layoutgrid as layoutgrid _log = logging.getLogger(__name__) @@ -196,9 +195,6 @@ def __init__(self, **kwargs): self._supxlabel = None self._supylabel = None - # constrained_layout: - self._layoutgrid = None - # groupers to keep track of x and y labels we want to align. # see self.align_xlabels and self.align_ylabels and # axis._get_tick_boxes_siblings @@ -2012,9 +2008,6 @@ def __init__(self, parent, subplotspec, *, self._set_artist_props(self.patch) self.patch.set_antialiased(False) - if parent._layoutgrid is not None: - self.init_layoutgrid() - @property def dpi(self): return self._parent.dpi @@ -2075,21 +2068,6 @@ def get_constrained_layout_pads(self, relative=False): """ return self._parent.get_constrained_layout_pads(relative=relative) - def init_layoutgrid(self): - """Initialize the layoutgrid for use in constrained_layout.""" - if self._layoutgrid is None: - gs = self._subplotspec.get_gridspec() - parent = gs._layoutgrid - if parent is not None: - self._layoutgrid = layoutgrid.LayoutGrid( - parent=parent, - name=(parent.name + '.' + 'panellb' + - layoutgrid.seq_id()), - parent_inner=True, - nrows=1, ncols=1, - parent_pos=(self._subplotspec.rowspan, - self._subplotspec.colspan)) - @property def axes(self): """ @@ -2272,7 +2250,6 @@ def __init__(self, self.subplotpars = subplotpars # constrained_layout: - self._layoutgrid = None self._constrained = False self.set_tight_layout(tight_layout) @@ -2431,9 +2408,6 @@ def set_constrained_layout(self, constrained): self.set_constrained_layout_pads(**constrained) else: self.set_constrained_layout_pads() - - self.init_layoutgrid() - self.stale = True def set_constrained_layout_pads(self, *, w_pad=None, h_pad=None, @@ -2494,10 +2468,10 @@ def get_constrained_layout_pads(self, relative=False): hspace = self._constrained_layout_pads['hspace'] if relative and (w_pad is not None or h_pad is not None): - renderer0 = layoutgrid.get_renderer(self) - dpi = renderer0.dpi - w_pad = w_pad * dpi / renderer0.width - h_pad = h_pad * dpi / renderer0.height + renderer = _get_renderer(self) + dpi = renderer.dpi + w_pad = w_pad * dpi / renderer.width + h_pad = h_pad * dpi / renderer.height return w_pad, h_pad, wspace, hspace @@ -2749,8 +2723,6 @@ def clf(self, keep_observers=False): self._supxlabel = None self._supylabel = None - if self.get_constrained_layout(): - self.init_layoutgrid() self.stale = True def clear(self, keep_observers=False): @@ -2833,11 +2805,6 @@ def __getstate__(self): if getattr(self.canvas, 'manager', None) \ in _pylab_helpers.Gcf.figs.values(): state['_restore_to_pylab'] = True - - # set all the layoutgrid information to None. kiwisolver objects can't - # be pickled, so we lose the layout options at this point. - state.pop('_layoutgrid', None) - return state def __setstate__(self, state): @@ -2853,7 +2820,6 @@ def __setstate__(self, state): # re-initialise some of the unstored state information FigureCanvasBase(self) # Set self.canvas. - self._layoutgrid = None if restore_to_pylab: # lazy import to avoid circularity @@ -3114,30 +3080,20 @@ def handler(ev): return None if event is None else event.name == "key_press_event" - def init_layoutgrid(self): - """Initialize the layoutgrid for use in constrained_layout.""" - del(self._layoutgrid) - self._layoutgrid = layoutgrid.LayoutGrid( - parent=None, name='figlb') - def execute_constrained_layout(self, renderer=None): """ Use ``layoutgrid`` to determine pos positions within Axes. See also `.set_constrained_layout_pads`. + + Returns + ------- + layoutgrid : private debugging object """ from matplotlib._constrained_layout import do_constrained_layout _log.debug('Executing constrainedlayout') - if self._layoutgrid is None: - _api.warn_external("Calling figure.constrained_layout, but " - "figure not setup to do constrained layout. " - "You either called GridSpec without the " - "figure keyword, you are using plt.subplot, " - "or you need to call figure or subplots " - "with the constrained_layout=True kwarg.") - return w_pad, h_pad, wspace, hspace = self.get_constrained_layout_pads() # convert to unit-relative lengths fig = self @@ -3146,7 +3102,8 @@ def execute_constrained_layout(self, renderer=None): h_pad = h_pad / height if renderer is None: renderer = _get_renderer(fig) - do_constrained_layout(fig, renderer, h_pad, w_pad, hspace, wspace) + return do_constrained_layout(fig, renderer, h_pad, w_pad, + hspace, wspace) def tight_layout(self, *, pad=1.08, h_pad=None, w_pad=None, rect=None): """ diff --git a/lib/matplotlib/gridspec.py b/lib/matplotlib/gridspec.py index eb0db3c09576..752048d64a59 100644 --- a/lib/matplotlib/gridspec.py +++ b/lib/matplotlib/gridspec.py @@ -18,8 +18,6 @@ import matplotlib as mpl from matplotlib import _api, _pylab_helpers, tight_layout, rcParams from matplotlib.transforms import Bbox -import matplotlib._layoutgrid as layoutgrid - _log = logging.getLogger(__name__) @@ -387,25 +385,8 @@ def __init__(self, nrows, ncols, figure=None, width_ratios=width_ratios, height_ratios=height_ratios) - # set up layoutgrid for constrained_layout: - self._layoutgrid = None - if self.figure is None or not self.figure.get_constrained_layout(): - self._layoutgrid = None - else: - self._toplayoutbox = self.figure._layoutgrid - self._layoutgrid = layoutgrid.LayoutGrid( - parent=self.figure._layoutgrid, - parent_inner=True, - name=(self.figure._layoutgrid.name + '.gridspec' + - layoutgrid.seq_id()), - ncols=ncols, nrows=nrows, width_ratios=width_ratios, - height_ratios=height_ratios) - _AllowedKeys = ["left", "bottom", "right", "top", "wspace", "hspace"] - def __getstate__(self): - return {**self.__dict__, "_layoutgrid": None} - def update(self, **kwargs): """ Update the subplot parameters of the grid. @@ -522,26 +503,6 @@ def __init__(self, nrows, ncols, super().__init__(nrows, ncols, width_ratios=width_ratios, height_ratios=height_ratios) - # do the layoutgrids for constrained_layout: - subspeclb = subplot_spec.get_gridspec()._layoutgrid - if subspeclb is None: - self._layoutgrid = None - else: - # this _toplayoutbox is a container that spans the cols and - # rows in the parent gridspec. Not yet implemented, - # but we do this so that it is possible to have subgridspec - # level artists. - self._toplayoutgrid = layoutgrid.LayoutGrid( - parent=subspeclb, - name=subspeclb.name + '.top' + layoutgrid.seq_id(), - nrows=1, ncols=1, - parent_pos=(subplot_spec.rowspan, subplot_spec.colspan)) - self._layoutgrid = layoutgrid.LayoutGrid( - parent=self._toplayoutgrid, - name=(self._toplayoutgrid.name + '.gridspec' + - layoutgrid.seq_id()), - nrows=nrows, ncols=ncols, - width_ratios=width_ratios, height_ratios=height_ratios) def get_subplot_params(self, figure=None): """Return a dictionary of subplot layout parameters.""" diff --git a/tutorials/intermediate/constrainedlayout_guide.py b/tutorials/intermediate/constrainedlayout_guide.py index 2c76c4c0327d..0e28d4759ea9 100644 --- a/tutorials/intermediate/constrainedlayout_guide.py +++ b/tutorials/intermediate/constrainedlayout_guide.py @@ -71,7 +71,6 @@ def example_plot(ax, fontsize=12, hide_labels=False): ax.set_ylabel('y-label', fontsize=fontsize) ax.set_title('Title', fontsize=fontsize) - fig, ax = plt.subplots(constrained_layout=False) example_plot(ax, fontsize=24) @@ -509,6 +508,7 @@ def docomplicated(suptitle=None): example_plot(ax3) example_plot(ax4) fig.suptitle('subplot2grid') +plt.show() ############################################################################### # Other Caveats @@ -589,7 +589,7 @@ def docomplicated(suptitle=None): fig, ax = plt.subplots(constrained_layout=True) example_plot(ax, fontsize=24) -plot_children(fig, fig._layoutgrid) +plot_children(fig) ####################################################################### # Simple case: two Axes @@ -604,7 +604,7 @@ def docomplicated(suptitle=None): fig, ax = plt.subplots(1, 2, constrained_layout=True) example_plot(ax[0], fontsize=32) example_plot(ax[1], fontsize=8) -plot_children(fig, fig._layoutgrid, printit=False) +plot_children(fig, printit=False) ####################################################################### # Two Axes and colorbar @@ -617,7 +617,7 @@ def docomplicated(suptitle=None): im = ax[0].pcolormesh(arr, **pc_kwargs) fig.colorbar(im, ax=ax[0], shrink=0.6) im = ax[1].pcolormesh(arr, **pc_kwargs) -plot_children(fig, fig._layoutgrid) +plot_children(fig) ####################################################################### # Colorbar associated with a Gridspec @@ -630,7 +630,7 @@ def docomplicated(suptitle=None): for ax in axs.flat: im = ax.pcolormesh(arr, **pc_kwargs) fig.colorbar(im, ax=axs, shrink=0.6) -plot_children(fig, fig._layoutgrid, printit=False) +plot_children(fig, printit=False) ####################################################################### # Uneven sized Axes @@ -655,7 +655,7 @@ def docomplicated(suptitle=None): im = ax.pcolormesh(arr, **pc_kwargs) ax = fig.add_subplot(gs[1, 1]) im = ax.pcolormesh(arr, **pc_kwargs) -plot_children(fig, fig._layoutgrid, printit=False) +plot_children(fig, printit=False) ####################################################################### # One case that requires finessing is if margins do not have any artists @@ -670,4 +670,5 @@ def docomplicated(suptitle=None): ax01 = fig.add_subplot(gs[0, 2:]) ax10 = fig.add_subplot(gs[1, 1:3]) example_plot(ax10, fontsize=14) -plot_children(fig, fig._layoutgrid) +plot_children(fig) +plt.show()