You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			806 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			806 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
Adjust subplot layouts so that there are no overlapping Axes or Axes
 | 
						|
decorations.  All Axes decorations are dealt with (labels, ticks, titles,
 | 
						|
ticklabels) and some dependent artists are also dealt with (colorbar,
 | 
						|
suptitle).
 | 
						|
 | 
						|
Layout is done via `~matplotlib.gridspec`, with one constraint per gridspec,
 | 
						|
so it is possible to have overlapping Axes if the gridspecs overlap (i.e.
 | 
						|
using `~matplotlib.gridspec.GridSpecFromSubplotSpec`).  Axes placed using
 | 
						|
``figure.subplots()`` or ``figure.add_subplots()`` will participate in the
 | 
						|
layout.  Axes manually placed via ``figure.add_axes()`` will not.
 | 
						|
 | 
						|
See Tutorial: :ref:`constrainedlayout_guide`
 | 
						|
 | 
						|
General idea:
 | 
						|
-------------
 | 
						|
 | 
						|
First, a figure has a gridspec that divides the figure into nrows and ncols,
 | 
						|
with heights and widths set by ``height_ratios`` and ``width_ratios``,
 | 
						|
often just set to 1 for an equal grid.
 | 
						|
 | 
						|
Subplotspecs that are derived from this gridspec can contain either a
 | 
						|
``SubPanel``, a ``GridSpecFromSubplotSpec``, or an ``Axes``.  The ``SubPanel``
 | 
						|
and ``GridSpecFromSubplotSpec`` are dealt with recursively and each contain an
 | 
						|
analogous layout.
 | 
						|
 | 
						|
Each ``GridSpec`` has a ``_layoutgrid`` attached to it.  The ``_layoutgrid``
 | 
						|
has the same logical layout as the ``GridSpec``.   Each row of the grid spec
 | 
						|
has a top and bottom "margin" and each column has a left and right "margin".
 | 
						|
The "inner" height of each row is constrained to be the same (or as modified
 | 
						|
by ``height_ratio``), and the "inner" width of each column is
 | 
						|
constrained to be the same (as modified by ``width_ratio``), where "inner"
 | 
						|
is the width or height of each column/row minus the size of the margins.
 | 
						|
 | 
						|
Then the size of the margins for each row and column are determined as the
 | 
						|
max width of the decorators on each Axes that has decorators in that margin.
 | 
						|
For instance, a normal Axes would have a left margin that includes the
 | 
						|
left ticklabels, and the ylabel if it exists.  The right margin may include a
 | 
						|
colorbar, the bottom margin the xaxis decorations, and the top margin the
 | 
						|
title.
 | 
						|
 | 
						|
With these constraints, the solver then finds appropriate bounds for the
 | 
						|
columns and rows.  It's possible that the margins take up the whole figure,
 | 
						|
in which case the algorithm is not applied and a warning is raised.
 | 
						|
 | 
						|
See the tutorial :ref:`constrainedlayout_guide`
 | 
						|
for more discussion of the algorithm with examples.
 | 
						|
"""
 | 
						|
 | 
						|
import logging
 | 
						|
 | 
						|
import numpy as np
 | 
						|
 | 
						|
from matplotlib import _api, artist as martist
 | 
						|
import matplotlib.transforms as mtransforms
 | 
						|
import matplotlib._layoutgrid as mlayoutgrid
 | 
						|
 | 
						|
 | 
						|
_log = logging.getLogger(__name__)
 | 
						|
 | 
						|
 | 
						|
######################################################
 | 
						|
def do_constrained_layout(fig, h_pad, w_pad,
 | 
						|
                          hspace=None, wspace=None, rect=(0, 0, 1, 1),
 | 
						|
                          compress=False):
 | 
						|
    """
 | 
						|
    Do the constrained_layout.  Called at draw time in
 | 
						|
     ``figure.constrained_layout()``
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    fig : `~matplotlib.figure.Figure`
 | 
						|
        `.Figure` instance to do the layout in.
 | 
						|
 | 
						|
    h_pad, w_pad : float
 | 
						|
      Padding around the Axes elements in figure-normalized units.
 | 
						|
 | 
						|
    hspace, wspace : float
 | 
						|
       Fraction of the figure to dedicate to space between the
 | 
						|
       Axes.  These are evenly spread between the gaps between the Axes.
 | 
						|
       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.
 | 
						|
 | 
						|
    rect : tuple of 4 floats
 | 
						|
        Rectangle in figure coordinates to perform constrained layout in
 | 
						|
        [left, bottom, width, height], each from 0-1.
 | 
						|
 | 
						|
    compress : bool
 | 
						|
        Whether to shift Axes so that white space in between them is
 | 
						|
        removed. This is useful for simple grids of fixed-aspect Axes (e.g.
 | 
						|
        a grid of images).
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    layoutgrid : private debugging structure
 | 
						|
    """
 | 
						|
 | 
						|
    renderer = fig._get_renderer()
 | 
						|
    # make layoutgrid tree...
 | 
						|
    layoutgrids = make_layoutgrids(fig, None, rect=rect)
 | 
						|
    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
 | 
						|
        # change size after the first re-position (i.e. x/yticklabels get
 | 
						|
        # larger/smaller).  This second reposition tends to be much milder,
 | 
						|
        # so doing twice makes things work OK.
 | 
						|
 | 
						|
        # make margins for all the Axes and subfigures in the
 | 
						|
        # figure.  Add margins for colorbars...
 | 
						|
        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(layoutgrids, fig)
 | 
						|
 | 
						|
        # update all the variables in the layout.
 | 
						|
        layoutgrids[fig].update_variables()
 | 
						|
 | 
						|
        warn_collapsed = ('constrained_layout not applied because '
 | 
						|
                          'axes sizes collapsed to zero.  Try making '
 | 
						|
                          'figure larger or Axes decorations smaller.')
 | 
						|
        if check_no_collapsed_axes(layoutgrids, fig):
 | 
						|
            reposition_axes(layoutgrids, fig, renderer, h_pad=h_pad,
 | 
						|
                            w_pad=w_pad, hspace=hspace, wspace=wspace)
 | 
						|
            if compress:
 | 
						|
                layoutgrids = compress_fixed_aspect(layoutgrids, fig)
 | 
						|
                layoutgrids[fig].update_variables()
 | 
						|
                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(warn_collapsed)
 | 
						|
 | 
						|
                if ((suptitle := fig._suptitle) is not None and
 | 
						|
                        suptitle.get_in_layout() and suptitle._autopos):
 | 
						|
                    x, _ = suptitle.get_position()
 | 
						|
                    suptitle.set_position(
 | 
						|
                        (x, layoutgrids[fig].get_inner_bbox().y1 + h_pad))
 | 
						|
                    suptitle.set_verticalalignment('bottom')
 | 
						|
        else:
 | 
						|
            _api.warn_external(warn_collapsed)
 | 
						|
        reset_margins(layoutgrids, fig)
 | 
						|
    return layoutgrids
 | 
						|
 | 
						|
 | 
						|
def make_layoutgrids(fig, layoutgrids, rect=(0, 0, 1, 1)):
 | 
						|
    """
 | 
						|
    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;  pass rect as parent to allow user-specified
 | 
						|
        # margins
 | 
						|
        layoutgrids[fig] = mlayoutgrid.LayoutGrid(parent=rect, 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:
 | 
						|
        gs = ax.get_gridspec()
 | 
						|
        if gs is not None:
 | 
						|
            layoutgrids = make_layoutgrids_gs(layoutgrids, gs)
 | 
						|
 | 
						|
    return layoutgrids
 | 
						|
 | 
						|
 | 
						|
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:
 | 
						|
        # get a unique representation:
 | 
						|
        rep = (gs, 'top')
 | 
						|
        if rep not in layoutgrids:
 | 
						|
            layoutgrids[rep] = mlayoutgrid.LayoutGrid(
 | 
						|
                parent=subspeclb,
 | 
						|
                name='top',
 | 
						|
                nrows=1, ncols=1,
 | 
						|
                parent_pos=(subplot_spec.rowspan, subplot_spec.colspan))
 | 
						|
        layoutgrids[gs] = mlayoutgrid.LayoutGrid(
 | 
						|
                parent=layoutgrids[rep],
 | 
						|
                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 sfig in fig.subfigs:
 | 
						|
        ok = check_no_collapsed_axes(layoutgrids, sfig)
 | 
						|
        if not ok:
 | 
						|
            return False
 | 
						|
    for ax in fig.axes:
 | 
						|
        gs = ax.get_gridspec()
 | 
						|
        if gs in layoutgrids:  # also implies gs is not None.
 | 
						|
            lg = layoutgrids[gs]
 | 
						|
            for i in range(gs.nrows):
 | 
						|
                for j in range(gs.ncols):
 | 
						|
                    bb = lg.get_inner_bbox(i, j)
 | 
						|
                    if bb.width <= 0 or bb.height <= 0:
 | 
						|
                        return False
 | 
						|
    return True
 | 
						|
 | 
						|
 | 
						|
def compress_fixed_aspect(layoutgrids, fig):
 | 
						|
    gs = None
 | 
						|
    for ax in fig.axes:
 | 
						|
        if ax.get_subplotspec() is None:
 | 
						|
            continue
 | 
						|
        ax.apply_aspect()
 | 
						|
        sub = ax.get_subplotspec()
 | 
						|
        _gs = sub.get_gridspec()
 | 
						|
        if gs is None:
 | 
						|
            gs = _gs
 | 
						|
            extraw = np.zeros(gs.ncols)
 | 
						|
            extrah = np.zeros(gs.nrows)
 | 
						|
        elif _gs != gs:
 | 
						|
            raise ValueError('Cannot do compressed layout if Axes are not'
 | 
						|
                             'all from the same gridspec')
 | 
						|
        orig = ax.get_position(original=True)
 | 
						|
        actual = ax.get_position(original=False)
 | 
						|
        dw = orig.width - actual.width
 | 
						|
        if dw > 0:
 | 
						|
            extraw[sub.colspan] = np.maximum(extraw[sub.colspan], dw)
 | 
						|
        dh = orig.height - actual.height
 | 
						|
        if dh > 0:
 | 
						|
            extrah[sub.rowspan] = np.maximum(extrah[sub.rowspan], dh)
 | 
						|
 | 
						|
    if gs is None:
 | 
						|
        raise ValueError('Cannot do compressed layout if no Axes '
 | 
						|
                         'are part of a gridspec.')
 | 
						|
    w = np.sum(extraw) / 2
 | 
						|
    layoutgrids[fig].edit_margin_min('left', w)
 | 
						|
    layoutgrids[fig].edit_margin_min('right', w)
 | 
						|
 | 
						|
    h = np.sum(extrah) / 2
 | 
						|
    layoutgrids[fig].edit_margin_min('top', h)
 | 
						|
    layoutgrids[fig].edit_margin_min('bottom', h)
 | 
						|
    return layoutgrids
 | 
						|
 | 
						|
 | 
						|
def get_margin_from_padding(obj, *, w_pad=0, h_pad=0,
 | 
						|
                            hspace=0, wspace=0):
 | 
						|
 | 
						|
    ss = obj._subplotspec
 | 
						|
    gs = ss.get_gridspec()
 | 
						|
 | 
						|
    if hasattr(gs, 'hspace'):
 | 
						|
        _hspace = (gs.hspace if gs.hspace is not None else hspace)
 | 
						|
        _wspace = (gs.wspace if gs.wspace is not None else wspace)
 | 
						|
    else:
 | 
						|
        _hspace = (gs._hspace if gs._hspace is not None else hspace)
 | 
						|
        _wspace = (gs._wspace if gs._wspace is not None else wspace)
 | 
						|
 | 
						|
    _wspace = _wspace / 2
 | 
						|
    _hspace = _hspace / 2
 | 
						|
 | 
						|
    nrows, ncols = gs.get_geometry()
 | 
						|
    # there are two margins for each direction.  The "cb"
 | 
						|
    # margins are for pads and colorbars, the non-"cb" are
 | 
						|
    # for the Axes decorations (labels etc).
 | 
						|
    margin = {'leftcb': w_pad, 'rightcb': w_pad,
 | 
						|
              'bottomcb': h_pad, 'topcb': h_pad,
 | 
						|
              'left': 0, 'right': 0,
 | 
						|
              'top': 0, 'bottom': 0}
 | 
						|
    if _wspace / ncols > w_pad:
 | 
						|
        if ss.colspan.start > 0:
 | 
						|
            margin['leftcb'] = _wspace / ncols
 | 
						|
        if ss.colspan.stop < ncols:
 | 
						|
            margin['rightcb'] = _wspace / ncols
 | 
						|
    if _hspace / nrows > h_pad:
 | 
						|
        if ss.rowspan.stop < nrows:
 | 
						|
            margin['bottomcb'] = _hspace / nrows
 | 
						|
        if ss.rowspan.start > 0:
 | 
						|
            margin['topcb'] = _hspace / nrows
 | 
						|
 | 
						|
    return margin
 | 
						|
 | 
						|
 | 
						|
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
 | 
						|
    decorations on the axis.
 | 
						|
 | 
						|
    Then make room for colorbars.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    layoutgrids : dict
 | 
						|
    fig : `~matplotlib.figure.Figure`
 | 
						|
        `.Figure` instance to do the layout in.
 | 
						|
    renderer : `~matplotlib.backend_bases.RendererBase` subclass.
 | 
						|
        The renderer to use.
 | 
						|
    w_pad, h_pad : float, default: 0
 | 
						|
        Width and height padding (in fraction of figure).
 | 
						|
    hspace, wspace : float, default: 0
 | 
						|
        Width and height padding as fraction of figure size divided by
 | 
						|
        number of columns or rows.
 | 
						|
    """
 | 
						|
    for sfig in fig.subfigs:  # recursively make child panel margins
 | 
						|
        ss = sfig._subplotspec
 | 
						|
        gs = ss.get_gridspec()
 | 
						|
 | 
						|
        make_layout_margins(layoutgrids, sfig, renderer,
 | 
						|
                            w_pad=w_pad, h_pad=h_pad,
 | 
						|
                            hspace=hspace, wspace=wspace)
 | 
						|
 | 
						|
        margins = get_margin_from_padding(sfig, w_pad=0, h_pad=0,
 | 
						|
                                          hspace=hspace, wspace=wspace)
 | 
						|
        layoutgrids[gs].edit_outer_margin_mins(margins, ss)
 | 
						|
 | 
						|
    for ax in fig._localaxes:
 | 
						|
        if not ax.get_subplotspec() or not ax.get_in_layout():
 | 
						|
            continue
 | 
						|
 | 
						|
        ss = ax.get_subplotspec()
 | 
						|
        gs = ss.get_gridspec()
 | 
						|
 | 
						|
        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)
 | 
						|
        # 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
 | 
						|
        margin['right'] += bbox.x1 - pos.x1
 | 
						|
        # remember that rows are ordered from top:
 | 
						|
        margin['bottom'] += pos.y0 - bbox.y0
 | 
						|
        margin['top'] += bbox.y1 - pos.y1
 | 
						|
 | 
						|
        # make margin for colorbars.  These margins go in the
 | 
						|
        # 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(layoutgrids, cbax)
 | 
						|
            # colorbars can be child of more than one subplot spec:
 | 
						|
            cbp_rspan, cbp_cspan = get_cb_parent_spans(cbax)
 | 
						|
            loc = cbax._colorbar_info['location']
 | 
						|
            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
 | 
						|
                    margin['rightcb'] += cbbbox.width + pad
 | 
						|
            elif loc == 'left':
 | 
						|
                if cbp_cspan.start == ss.colspan.start:
 | 
						|
                    # only increase if the colorbar is on the left edge
 | 
						|
                    margin['leftcb'] += cbbbox.width + pad
 | 
						|
            elif loc == 'top':
 | 
						|
                if cbp_rspan.start == ss.rowspan.start:
 | 
						|
                    margin['topcb'] += cbbbox.height + pad
 | 
						|
            else:
 | 
						|
                if cbp_rspan.stop == ss.rowspan.stop:
 | 
						|
                    margin['bottomcb'] += cbbbox.height + pad
 | 
						|
            # If the colorbars are wider than the parent box in the
 | 
						|
            # cross direction
 | 
						|
            if loc in ['top', 'bottom']:
 | 
						|
                if (cbp_cspan.start == ss.colspan.start and
 | 
						|
                        cbbbox.x0 < bbox.x0):
 | 
						|
                    margin['left'] += bbox.x0 - cbbbox.x0
 | 
						|
                if (cbp_cspan.stop == ss.colspan.stop and
 | 
						|
                        cbbbox.x1 > bbox.x1):
 | 
						|
                    margin['right'] += cbbbox.x1 - bbox.x1
 | 
						|
            # or taller:
 | 
						|
            if loc in ['left', 'right']:
 | 
						|
                if (cbp_rspan.stop == ss.rowspan.stop and
 | 
						|
                        cbbbox.y0 < bbox.y0):
 | 
						|
                    margin['bottom'] += bbox.y0 - cbbbox.y0
 | 
						|
                if (cbp_rspan.start == ss.rowspan.start and
 | 
						|
                        cbbbox.y1 > bbox.y1):
 | 
						|
                    margin['top'] += cbbbox.y1 - bbox.y1
 | 
						|
        # pass the new margins down to the layout grid for the solution...
 | 
						|
        layoutgrids[gs].edit_outer_margin_mins(margin, ss)
 | 
						|
 | 
						|
    # make margins for figure-level legends:
 | 
						|
    for leg in fig.legends:
 | 
						|
        inv_trans_fig = None
 | 
						|
        if leg._outside_loc and leg._bbox_to_anchor is None:
 | 
						|
            if inv_trans_fig is None:
 | 
						|
                inv_trans_fig = fig.transFigure.inverted().transform_bbox
 | 
						|
            bbox = inv_trans_fig(leg.get_tightbbox(renderer))
 | 
						|
            w = bbox.width + 2 * w_pad
 | 
						|
            h = bbox.height + 2 * h_pad
 | 
						|
            legendloc = leg._outside_loc
 | 
						|
            if legendloc == 'lower':
 | 
						|
                layoutgrids[fig].edit_margin_min('bottom', h)
 | 
						|
            elif legendloc == 'upper':
 | 
						|
                layoutgrids[fig].edit_margin_min('top', h)
 | 
						|
            if legendloc == 'right':
 | 
						|
                layoutgrids[fig].edit_margin_min('right', w)
 | 
						|
            elif legendloc == 'left':
 | 
						|
                layoutgrids[fig].edit_margin_min('left', w)
 | 
						|
 | 
						|
 | 
						|
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.
 | 
						|
 | 
						|
    inv_trans_fig = fig.transFigure.inverted().transform_bbox
 | 
						|
    # get the h_pad and w_pad as distances in the local subfigure coordinates:
 | 
						|
    padbox = mtransforms.Bbox([[0, 0], [w_pad, h_pad]])
 | 
						|
    padbox = (fig.transFigure -
 | 
						|
              fig.transSubfigure).transform_bbox(padbox)
 | 
						|
    h_pad_local = padbox.height
 | 
						|
    w_pad_local = padbox.width
 | 
						|
 | 
						|
    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))
 | 
						|
            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))
 | 
						|
            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))
 | 
						|
            layoutgrids[fig].edit_margin_min('left', bbox.width + 2 * w_pad)
 | 
						|
 | 
						|
 | 
						|
def match_submerged_margins(layoutgrids, fig):
 | 
						|
    """
 | 
						|
    Make the margins that are submerged inside an Axes the same size.
 | 
						|
 | 
						|
    This allows Axes that span two columns (or rows) that are offset
 | 
						|
    from one another to have the same size.
 | 
						|
 | 
						|
    This gives the proper layout for something like::
 | 
						|
        fig = plt.figure(constrained_layout=True)
 | 
						|
        axs = fig.subplot_mosaic("AAAB\nCCDD")
 | 
						|
 | 
						|
    Without this routine, the Axes D will be wider than C, because the
 | 
						|
    margin width between the two columns in C has no width by default,
 | 
						|
    whereas the margins between the two columns of D are set by the
 | 
						|
    width of the margin between A and B. However, obviously the user would
 | 
						|
    like C and D to be the same size, so we need to add constraints to these
 | 
						|
    "submerged" margins.
 | 
						|
 | 
						|
    This routine makes all the interior margins the same, and the spacing
 | 
						|
    between the three columns in A and the two column in C are all set to the
 | 
						|
    margins between the two columns of D.
 | 
						|
 | 
						|
    See test_constrained_layout::test_constrained_layout12 for an example.
 | 
						|
    """
 | 
						|
 | 
						|
    axsdone = []
 | 
						|
    for sfig in fig.subfigs:
 | 
						|
        axsdone += match_submerged_margins(layoutgrids, sfig)
 | 
						|
 | 
						|
    axs = [a for a in fig.get_axes()
 | 
						|
           if (a.get_subplotspec() is not None and a.get_in_layout() and
 | 
						|
               a not in axsdone)]
 | 
						|
 | 
						|
    for ax1 in axs:
 | 
						|
        ss1 = ax1.get_subplotspec()
 | 
						|
        if ss1.get_gridspec() not in layoutgrids:
 | 
						|
            axs.remove(ax1)
 | 
						|
            continue
 | 
						|
        lg1 = layoutgrids[ss1.get_gridspec()]
 | 
						|
 | 
						|
        # interior columns:
 | 
						|
        if len(ss1.colspan) > 1:
 | 
						|
            maxsubl = np.max(
 | 
						|
                lg1.margin_vals['left'][ss1.colspan[1:]] +
 | 
						|
                lg1.margin_vals['leftcb'][ss1.colspan[1:]]
 | 
						|
            )
 | 
						|
            maxsubr = np.max(
 | 
						|
                lg1.margin_vals['right'][ss1.colspan[:-1]] +
 | 
						|
                lg1.margin_vals['rightcb'][ss1.colspan[:-1]]
 | 
						|
            )
 | 
						|
            for ax2 in axs:
 | 
						|
                ss2 = ax2.get_subplotspec()
 | 
						|
                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:]] +
 | 
						|
                        lg2.margin_vals['leftcb'][ss2.colspan[1:]])
 | 
						|
                    if maxsubl2 > maxsubl:
 | 
						|
                        maxsubl = maxsubl2
 | 
						|
                    maxsubr2 = np.max(
 | 
						|
                        lg2.margin_vals['right'][ss2.colspan[:-1]] +
 | 
						|
                        lg2.margin_vals['rightcb'][ss2.colspan[:-1]])
 | 
						|
                    if maxsubr2 > maxsubr:
 | 
						|
                        maxsubr = maxsubr2
 | 
						|
            for i in ss1.colspan[1:]:
 | 
						|
                lg1.edit_margin_min('left', maxsubl, cell=i)
 | 
						|
            for i in ss1.colspan[:-1]:
 | 
						|
                lg1.edit_margin_min('right', maxsubr, cell=i)
 | 
						|
 | 
						|
        # interior rows:
 | 
						|
        if len(ss1.rowspan) > 1:
 | 
						|
            maxsubt = np.max(
 | 
						|
                lg1.margin_vals['top'][ss1.rowspan[1:]] +
 | 
						|
                lg1.margin_vals['topcb'][ss1.rowspan[1:]]
 | 
						|
            )
 | 
						|
            maxsubb = np.max(
 | 
						|
                lg1.margin_vals['bottom'][ss1.rowspan[:-1]] +
 | 
						|
                lg1.margin_vals['bottomcb'][ss1.rowspan[:-1]]
 | 
						|
            )
 | 
						|
 | 
						|
            for ax2 in axs:
 | 
						|
                ss2 = ax2.get_subplotspec()
 | 
						|
                lg2 = layoutgrids[ss2.get_gridspec()]
 | 
						|
                if lg2 is not None:
 | 
						|
                    if len(ss2.rowspan) > 1:
 | 
						|
                        maxsubt = np.max([np.max(
 | 
						|
                            lg2.margin_vals['top'][ss2.rowspan[1:]] +
 | 
						|
                            lg2.margin_vals['topcb'][ss2.rowspan[1:]]
 | 
						|
                        ), maxsubt])
 | 
						|
                        maxsubb = np.max([np.max(
 | 
						|
                            lg2.margin_vals['bottom'][ss2.rowspan[:-1]] +
 | 
						|
                            lg2.margin_vals['bottomcb'][ss2.rowspan[:-1]]
 | 
						|
                        ), maxsubb])
 | 
						|
            for i in ss1.rowspan[1:]:
 | 
						|
                lg1.edit_margin_min('top', maxsubt, cell=i)
 | 
						|
            for i in ss1.rowspan[:-1]:
 | 
						|
                lg1.edit_margin_min('bottom', maxsubb, cell=i)
 | 
						|
 | 
						|
    return axs
 | 
						|
 | 
						|
 | 
						|
def get_cb_parent_spans(cbax):
 | 
						|
    """
 | 
						|
    Figure out which subplotspecs this colorbar belongs to.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    cbax : `~matplotlib.axes.Axes`
 | 
						|
        Axes for the colorbar.
 | 
						|
    """
 | 
						|
    rowstart = np.inf
 | 
						|
    rowstop = -np.inf
 | 
						|
    colstart = np.inf
 | 
						|
    colstop = -np.inf
 | 
						|
    for parent in cbax._colorbar_info['parents']:
 | 
						|
        ss = parent.get_subplotspec()
 | 
						|
        rowstart = min(ss.rowspan.start, rowstart)
 | 
						|
        rowstop = max(ss.rowspan.stop, rowstop)
 | 
						|
        colstart = min(ss.colspan.start, colstart)
 | 
						|
        colstop = max(ss.colspan.stop, colstop)
 | 
						|
 | 
						|
    rowspan = range(rowstart, rowstop)
 | 
						|
    colspan = range(colstart, colstop)
 | 
						|
    return rowspan, colspan
 | 
						|
 | 
						|
 | 
						|
def get_pos_and_bbox(ax, renderer):
 | 
						|
    """
 | 
						|
    Get the position and the bbox for the Axes.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    ax : `~matplotlib.axes.Axes`
 | 
						|
    renderer : `~matplotlib.backend_bases.RendererBase` subclass.
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    pos : `~matplotlib.transforms.Bbox`
 | 
						|
        Position in figure coordinates.
 | 
						|
    bbox : `~matplotlib.transforms.Bbox`
 | 
						|
        Tight bounding box in figure coordinates.
 | 
						|
    """
 | 
						|
    fig = ax.get_figure(root=False)
 | 
						|
    pos = ax.get_position(original=True)
 | 
						|
    # pos is in panel co-ords, but we need in figure for the layout
 | 
						|
    pos = pos.transformed(fig.transSubfigure - fig.transFigure)
 | 
						|
    tightbbox = martist._get_tightbbox_for_layout_only(ax, renderer)
 | 
						|
    if tightbbox is None:
 | 
						|
        bbox = pos
 | 
						|
    else:
 | 
						|
        bbox = tightbbox.transformed(fig.transFigure.inverted())
 | 
						|
    return pos, bbox
 | 
						|
 | 
						|
 | 
						|
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 = layoutgrids[sfig].get_outer_bbox()
 | 
						|
        sfig._redo_transform_rel_fig(
 | 
						|
            bbox=bbox.transformed(trans_fig_to_subfig))
 | 
						|
        reposition_axes(layoutgrids, sfig, renderer,
 | 
						|
                        w_pad=w_pad, h_pad=h_pad,
 | 
						|
                        wspace=wspace, hspace=hspace)
 | 
						|
 | 
						|
    for ax in fig._localaxes:
 | 
						|
        if ax.get_subplotspec() is None or not ax.get_in_layout():
 | 
						|
            continue
 | 
						|
 | 
						|
        # grid bbox is in Figure coordinates, but we specify in panel
 | 
						|
        # coordinates...
 | 
						|
        ss = ax.get_subplotspec()
 | 
						|
        gs = ss.get_gridspec()
 | 
						|
        if gs not in layoutgrids:
 | 
						|
            return
 | 
						|
 | 
						|
        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)
 | 
						|
        ax._set_position(newbbox)
 | 
						|
 | 
						|
        # move the colorbars:
 | 
						|
        # we need to keep track of oldw and oldh if there is more than
 | 
						|
        # one colorbar:
 | 
						|
        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(layoutgrids, cbax, renderer,
 | 
						|
                                    offset=offset)
 | 
						|
 | 
						|
 | 
						|
def reposition_colorbar(layoutgrids, cbax, renderer, *, offset=None):
 | 
						|
    """
 | 
						|
    Place the colorbar in its new place.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    layoutgrids : dict
 | 
						|
    cbax : `~matplotlib.axes.Axes`
 | 
						|
        Axes for the colorbar.
 | 
						|
    renderer : `~matplotlib.backend_bases.RendererBase` subclass.
 | 
						|
        The renderer to use.
 | 
						|
    offset : array-like
 | 
						|
        Offset the colorbar needs to be pushed to in order to
 | 
						|
        account for multiple colorbars.
 | 
						|
    """
 | 
						|
 | 
						|
    parents = cbax._colorbar_info['parents']
 | 
						|
    gs = parents[0].get_gridspec()
 | 
						|
    fig = cbax.get_figure(root=False)
 | 
						|
    trans_fig_to_subfig = fig.transFigure - fig.transSubfigure
 | 
						|
 | 
						|
    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']
 | 
						|
    fraction = cbax._colorbar_info['fraction']
 | 
						|
    aspect = cbax._colorbar_info['aspect']
 | 
						|
    shrink = cbax._colorbar_info['shrink']
 | 
						|
 | 
						|
    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(layoutgrids, cbax)
 | 
						|
    if location in ('left', 'right'):
 | 
						|
        # fraction and shrink are fractions of parent
 | 
						|
        pbcb = pb.shrunk(fraction, shrink).anchored(anchor, pb)
 | 
						|
        # The colorbar is at the left side of the parent.  Need
 | 
						|
        # to translate to right (or left)
 | 
						|
        if location == 'right':
 | 
						|
            lmargin = cbpos.x0 - cbbbox.x0
 | 
						|
            dx = bboxparent.x1 - pbcb.x0 + offset['right']
 | 
						|
            dx += cbpad + lmargin
 | 
						|
            offset['right'] += cbbbox.width + cbpad
 | 
						|
            pbcb = pbcb.translated(dx, 0)
 | 
						|
        else:
 | 
						|
            lmargin = cbpos.x0 - cbbbox.x0
 | 
						|
            dx = bboxparent.x0 - pbcb.x0  # edge of parent
 | 
						|
            dx += -cbbbox.width - cbpad + lmargin - offset['left']
 | 
						|
            offset['left'] += cbbbox.width + cbpad
 | 
						|
            pbcb = pbcb.translated(dx, 0)
 | 
						|
    else:  # horizontal axes:
 | 
						|
        pbcb = pb.shrunk(shrink, fraction).anchored(anchor, pb)
 | 
						|
        if location == 'top':
 | 
						|
            bmargin = cbpos.y0 - cbbbox.y0
 | 
						|
            dy = bboxparent.y1 - pbcb.y0 + offset['top']
 | 
						|
            dy += cbpad + bmargin
 | 
						|
            offset['top'] += cbbbox.height + cbpad
 | 
						|
            pbcb = pbcb.translated(0, dy)
 | 
						|
        else:
 | 
						|
            bmargin = cbpos.y0 - cbbbox.y0
 | 
						|
            dy = bboxparent.y0 - pbcb.y0
 | 
						|
            dy += -cbbbox.height - cbpad + bmargin - offset['bottom']
 | 
						|
            offset['bottom'] += cbbbox.height + cbpad
 | 
						|
            pbcb = pbcb.translated(0, dy)
 | 
						|
 | 
						|
    pbcb = trans_fig_to_subfig.transform_bbox(pbcb)
 | 
						|
    cbax.set_transform(fig.transSubfigure)
 | 
						|
    cbax._set_position(pbcb)
 | 
						|
    cbax.set_anchor(anchor)
 | 
						|
    if location in ['bottom', 'top']:
 | 
						|
        aspect = 1 / aspect
 | 
						|
    cbax.set_box_aspect(aspect)
 | 
						|
    cbax.set_aspect('auto')
 | 
						|
    return offset
 | 
						|
 | 
						|
 | 
						|
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 sfig in fig.subfigs:
 | 
						|
        reset_margins(layoutgrids, sfig)
 | 
						|
    for ax in fig.axes:
 | 
						|
        if ax.get_in_layout():
 | 
						|
            gs = ax.get_gridspec()
 | 
						|
            if gs in layoutgrids:  # also implies gs is not None.
 | 
						|
                layoutgrids[gs].reset_margins()
 | 
						|
    layoutgrids[fig].reset_margins()
 | 
						|
 | 
						|
 | 
						|
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 = layoutgrids[gs].get_inner_bbox(rows=cb_rspans, cols=cb_cspans)
 | 
						|
 | 
						|
    if cax._colorbar_info['location'] in ['right', 'left']:
 | 
						|
        size = bboxouter.width
 | 
						|
    else:
 | 
						|
        size = bboxouter.height
 | 
						|
 | 
						|
    return cax._colorbar_info['pad'] * size
 |