From 456a76661ff63aa1a00c506584bc1388c29a2802 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Sun, 12 Apr 2020 15:58:27 -0700 Subject: [PATCH 01/17] ENH: add squish option to constrained layout --- lib/matplotlib/_constrained_layout.py | 201 +++++++++++++++++++++++++- lib/matplotlib/_layoutbox.py | 8 +- lib/matplotlib/axes/_base.py | 2 + lib/matplotlib/axes/_subplots.py | 2 +- lib/matplotlib/colorbar.py | 13 +- lib/matplotlib/figure.py | 13 +- lib/matplotlib/gridspec.py | 1 + 7 files changed, 231 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index 90faebc1525a..abf7c7e9642e 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -48,8 +48,11 @@ import numpy as np import matplotlib.cbook as cbook +import matplotlib.colorbar as mcolorbar +import matplotlib.transforms as mtransforms import matplotlib._layoutbox as layoutbox + _log = logging.getLogger(__name__) @@ -69,7 +72,7 @@ def _axes_all_finite_sized(fig): ###################################################### def do_constrained_layout(fig, renderer, h_pad, w_pad, - hspace=None, wspace=None): + hspace=None, wspace=None, reset=True): """ Do the constrained_layout. Called at draw time in ``figure.constrained_layout()`` @@ -152,12 +155,16 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad, # 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. + bboxes = {} # need these for packing the layout later... for ax in fig.axes: _log.debug(ax._layoutbox) if ax._layoutbox is not None: # make margins for each layout box based on the size of # the decorators. - _make_layout_margins(ax, renderer, h_pad, w_pad) + bbox = _make_layout_margins(ax, renderer, h_pad, w_pad) + else: + bbox = None + bboxes[ax] = bbox # do layout for suptitle. suptitle = fig._suptitle @@ -198,6 +205,7 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad, _align_spines(fig, gs) fig._layoutbox.constrained_layout_called += 1 + # call the kiwi solver: fig._layoutbox.update_variables() # check if any axes collapsed to zero. If not, don't change positions: @@ -220,6 +228,7 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad, else: cbook._warn_external('constrained_layout not applied. At least ' 'one axes collapsed to zero width or height.') + _squish(fig, bboxes) def _make_ghost_gridspec_slots(fig, gs): @@ -254,6 +263,8 @@ def _make_layout_margins(ax, renderer, h_pad, w_pad): 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. + + Returns the bbox for some width/heigth calcs outside this loop. """ fig = ax.figure invTransFig = fig.transFigure.inverted().transform_bbox @@ -302,6 +313,7 @@ def _make_layout_margins(ax, renderer, h_pad, w_pad): ax._poslayoutbox.constrain_bottom_margin(0, strength='weak') ax._poslayoutbox.constrain_right_margin(0, strength='weak') ax._poslayoutbox.constrain_left_margin(0, strength='weak') + return bbox def _align_spines(fig, gs): @@ -660,3 +672,188 @@ def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05): strength='medium') return lb, lbpos + +def _squish(fig, bboxes, w_pad=0.00, h_pad=0.0): + import time + now = time.time() + renderer = fig.canvas.get_renderer() + gss = set() + invTransFig = fig.transFigure.inverted().transform_bbox + + colorbars = [] + for ax in fig.axes: + if hasattr(ax, '_colorbar_info'): + colorbars += [ax] + elif hasattr(ax, 'get_subplotspec'): + gs = ax.get_subplotspec().get_gridspec() + gss.add(gs) + for cba in ax._colorbars: + # get the bbox including the colorbar for this axis + if cba._colorbar_info['location'] == 'right': + bboxes[ax].x1 = bboxes[cba].x1 + if cba._colorbar_info['location'] == 'left': + bboxes[ax].x0 = bboxes[cba].x0 + if cba._colorbar_info['location'] == 'top': + bboxes[ax].y1 = bboxes[cba].y1 + if cba._colorbar_info['location'] == 'bottom': + bboxes[ax].y0 = bboxes[cba].y0 + + # we placed everything, but what if there are huge gaps... + for gs in gss: + axs = [ax for ax in fig.axes + if (hasattr(ax, 'get_subplotspec') + and ax.get_subplotspec().get_gridspec() == gs)] + nrows, ncols = gs.get_geometry() + # get widths: + dxs = np.zeros((nrows, ncols)) + dys = np.zeros((nrows, ncols)) + margxs = np.zeros((nrows, ncols)) + margys = np.zeros((nrows, ncols)) + + for i in range(nrows): + for j in range(ncols): + for ax in axs: + ss = ax.get_subplotspec() + if (i in ss.rowspan) and (j in ss.colspan): + di = ss.colspan[-1] - ss.colspan[0] + 1 + dj = ss.rowspan[-1] - ss.rowspan[0] + 1 + dxs[i, j] = bboxes[ax].bounds[2] / di + if ss.colspan[-1] < ncols - 1: + dxs[i, j] = dxs[i, j] + w_pad / di + dys[i, j] = bboxes[ax].bounds[3] / dj + if ss.rowspan[-1] < nrows - 1: + dys[i, j] = dys[i, j] + h_pad / dj + orpos = ax.get_position(original=True) + margxs[i, j] = bboxes[ax].x0 - orpos.x0 + margys[i, j] = bboxes[ax].y0 - orpos.y0 + + margxs = np.flipud(margxs) + margys = np.flipud(margys) + dys = np.flipud(dys) + dxs = np.flipud(dxs) + + ddxs = np.max(dxs, axis=0) + ddys = np.max(dys, axis=1) + dx = np.sum(ddxs) + dy = np.sum(ddys) + x1 = y1 = -np.Inf + x0 = y0 = np.Inf + + if (dx < dy) and (dx < 0.98): + margx = np.min(margxs, axis=0) + # Squish x! + extra = (1 - dx) / 2 + for ax in axs: + ss = ax.get_subplotspec() + orpos = ax.get_position(original=True) + x = extra + for j in range(0, ss.colspan[0]): + x += ddxs[j] + deltax = -orpos.x0 + x - margx[ss.colspan[0]] + orpos.x1 = orpos.x1 + deltax + orpos.x0 = orpos.x0 + deltax + # keep track of new bbox edges for placing colorbars + if bboxes[ax].x1 + deltax > x1: + x1 = bboxes[ax].x1 + deltax + if bboxes[ax].x0 + deltax < x0: + x0 = bboxes[ax].x0 + deltax + bboxes[ax].x0 = bboxes[ax].x0 + deltax + bboxes[ax].x1 = bboxes[ax].x1 + deltax + # Now set the new position. + ax._set_position(orpos, which='original') + # shift any colorbars belongig to this axis + for cba in ax._colorbars: + pos = cba.get_position(original=True) + if cba._colorbar_info['location'] in ['bottom', 'top']: + # shrink to make same size as active... + posac = ax.get_position(original=False) + dx = (1 - cba._colorbar_info['shrink']) * (posac.x1 - + posac.x0) / 2 + pos.x0 = posac.x0 + dx + pos.x1 = posac.x1 - dx + else: + pos.x0 = pos.x0 + deltax + pos.x1 = pos.x1 + deltax + cba._set_position(pos, which='original') + colorbars.remove(cba) + for cb in colorbars: + # shift any colorbars belonging to the gridspec. + pos = cb.get_position(original=True) + bbox = bboxes[cb] + if cb._colorbar_info['location'] == 'right': + marg = bbox.x0 - pos.x0 + x = x1 + marg + w_pad + pos.x1 = pos.x1 - pos.x0 + x + pos.x0 = x + elif cb._colorbar_info['location'] == 'left': + marg = bbox.x1 - pos.x1 + # left edge: + x = x0 - marg - w_pad + _dx = pos.x1 - pos.x0 + pos.x1 = x - marg + pos.x0 = x - marg - _dx + else: + marg = bbox.x0 - pos.x0 + pos.x0 = x0 - marg + marg = bbox.x1 - pos.x1 + pos.x1 = x1 - marg + cb._set_position(pos, which='original') + + if (dx > dy) and (dy < 0.98): + margy = np.min(margys, axis=1) + # Squish y! + extra = (1 - dy) / 2 + for ax in axs: + ss = ax.get_subplotspec() + orpos = ax.get_position(original=True) + y = extra + for j in range(0, nrows - ss.rowspan[-1] - 1): + y += ddys[j] + deltay = -orpos.y0 + y - margy[nrows - ss.rowspan[-1] - 1] + orpos.y1 = orpos.y1 + deltay + orpos.y0 = orpos.y0 + deltay + ax._set_position(orpos, which='original') + # keep track of new bbox edges for placing colorbars + if bboxes[ax].y1 + deltay > y1: + y1 = bboxes[ax].y1 + deltay + if bboxes[ax].y0 + deltay < y0: + y0 = bboxes[ax].y0 + deltay + bboxes[ax].y0 = bboxes[ax].y0 + deltay + bboxes[ax].y1 = bboxes[ax].y1 + deltay + # shift any colorbars belongig to this axis + for cba in ax._colorbars: + pos = cba.get_position(original=True) + if cba._colorbar_info['location'] in ['right', 'left']: + # shrink to make same size as active... + posac = ax.get_position(original=False) + dy = (1 - cba._colorbar_info['shrink']) * (posac.y1 - + posac.y0) / 2 + pos.y0 = posac.y0 + dy + pos.y1 = posac.y1 - dy + else: + pos.y0 = pos.y0 + deltay + pos.y1 = pos.y1 + deltay + cba._set_position(pos, which='original') + colorbars.remove(cba) + for cb in colorbars: + # shift any colorbars belonging to the gridspec. + pos = cb.get_position(original=True) + bbox = bboxes[cb] + if cb._colorbar_info['location'] == 'top': + marg = bbox.y0 - pos.y0 + y = y1 + marg + h_pad + pos.y1 = pos.y1 - pos.y0 + y + pos.y0 = y + elif cb._colorbar_info['location'] == 'bottom': + marg = bbox.y1 - pos.y1 + # left edge: + y = y0 - marg - h_pad + _dy = pos.y1 - pos.y0 + pos.y1 = y - marg + pos.y0 = y - marg - _dy + else: + marg = bbox.y0 - pos.y0 + pos.y0 = y0 - marg + marg = bbox.y1 - pos.y1 + pos.y1 = y1 - marg + cb._set_position(pos, which='original') diff --git a/lib/matplotlib/_layoutbox.py b/lib/matplotlib/_layoutbox.py index 0afa2e4829f2..93418e88c635 100644 --- a/lib/matplotlib/_layoutbox.py +++ b/lib/matplotlib/_layoutbox.py @@ -324,6 +324,7 @@ def constrain_width(self, width, strength='strong'): """ c = (self.width == width) self.solver.addConstraint(c | strength) + return c def constrain_width_min(self, width, strength='strong'): c = (self.width >= width) @@ -652,11 +653,13 @@ def print_tree(lb): print_tree(lb.parent) -def plot_children(fig, box, level=0, printit=True): +def plot_children(fig, box, level=0, printit=True, stop=1000): """Simple plotting to show where boxes are.""" import matplotlib import matplotlib.pyplot as plt + if stop < 0: + return if isinstance(fig, matplotlib.figure.Figure): ax = fig.add_axes([0., 0., 1., 1.]) ax.set_facecolor([1., 1., 1., 0.7]) @@ -665,6 +668,7 @@ def plot_children(fig, box, level=0, printit=True): else: ax = fig + import matplotlib.patches as patches colors = plt.rcParams["axes.prop_cycle"].by_key()["color"] if printit: @@ -692,4 +696,4 @@ def plot_children(fig, box, level=0, printit=True): ha='right', va='top', size=12-level, color=colors[level]) - plot_children(ax, child, level=level+1, printit=printit) + plot_children(ax, child, level=level+1, printit=printit, stop=stop-1) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index ef69e216a8b7..a5c02a69a3b7 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -567,6 +567,8 @@ def __init__(self, fig, rect, self._layoutbox = None self._poslayoutbox = None + self._colorbars = [] # a list of colorbars attached to this axes. + self._info = {} # dictionary of things we may want to keep track of def __getstate__(self): # The renderer should be re-created by the figure, and then cached at diff --git a/lib/matplotlib/axes/_subplots.py b/lib/matplotlib/axes/_subplots.py index e40754a2acbc..91f806cc0032 100644 --- a/lib/matplotlib/axes/_subplots.py +++ b/lib/matplotlib/axes/_subplots.py @@ -92,7 +92,7 @@ def get_gridspec(self): def update_params(self): """Update the subplot position from ``self.figure.subplotpars``.""" self.figbox, _, _, self.numRows, self.numCols = \ - self.get_subplotspec().get_position(self.figure, + self.get_subplotspec().get_position(self.figure, return_all=True) @cbook.deprecated("3.2", alternative="ax.get_subplotspec().rowspan.start") diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 7860631e0ce0..187c7608934c 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -459,6 +459,7 @@ def __init__(self, ax, cmap=None, np.empty((0, 2)), color=mpl.rcParams['axes.facecolor'], linewidth=0.01, zorder=-1) ax.add_artist(self.patch) + ax._colorbar = self self.dividers = None self.locator = None @@ -484,6 +485,7 @@ def __init__(self, ax, cmap=None, self.formatter = format # Assume it is a Formatter or None self.draw_all() + def _extend_lower(self): """Return whether the lower limit is open ended.""" return self.extend in ('both', 'min') @@ -1382,6 +1384,7 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15, Returns (cax, kw), the child axes and the reduced kw dictionary to be passed when creating the colorbar instance. """ + locations = ["left", "right", "top", "bottom"] if orientation is not None and location is not None: raise TypeError('position and orientation are mutually exclusive. ' @@ -1485,6 +1488,15 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15, ax.set_anchor(parent_anchor) cax = fig.add_axes(pbcb, label="") + # point the colorbar axes back at its parents and its location + cax._colorbar_info = {} + cax._colorbar_info['location'] = location + cax._colorbar_info['parents'] = parents + cax._colorbar_info['shrink'] = shrink + if len(parents) == 1: + # tell the parent it has a colorbar + ax._colorbars += [cax] + # OK, now make a layoutbox for the cb axis. Later, we will use this # to make the colorbar fit nicely. @@ -1504,7 +1516,6 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15, # the colorbar will be a sibling of this gridspec, so the # parent is the same parent as the gridspec. Either the figure, # or a subplotspec. - lb, lbpos = constrained_layout.layoutcolorbargridspec( parents, cax, shrink, aspect, location, pad) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index f7889aa5344e..1967be39fd21 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1725,7 +1725,14 @@ def draw(self, renderer): try: renderer.open_group('figure', gid=self.get_gid()) if self.get_constrained_layout() and self.axes: - self.execute_constrained_layout(renderer) + self.execute_constrained_layout(renderer, reset=True) + # yay extra draw! + self.patch.draw(renderer) + mimage._draw_list_compositing_images( + renderer, self, artists, self.suppressComposite) + self.execute_constrained_layout(renderer, reset=False) + + if self.get_tight_layout() and self.axes: try: self.tight_layout(**self._tight_parameters) @@ -2406,7 +2413,7 @@ def init_layoutbox(self): parent=None, name='figlb', artist=self) self._layoutbox.constrain_geometry(0., 0., 1., 1.) - def execute_constrained_layout(self, renderer=None): + def execute_constrained_layout(self, renderer=None, reset=True): """ Use ``layoutbox`` to determine pos positions within axes. @@ -2432,7 +2439,7 @@ def execute_constrained_layout(self, renderer=None): h_pad = h_pad / height if renderer is None: renderer = layoutbox.get_renderer(fig) - do_constrained_layout(fig, renderer, h_pad, w_pad, hspace, wspace) + do_constrained_layout(fig, renderer, h_pad, w_pad, hspace, wspace, reset=reset) @cbook._delete_parameter("3.2", "renderer") def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None, diff --git a/lib/matplotlib/gridspec.py b/lib/matplotlib/gridspec.py index b25bfe853534..8cc4775d73a1 100644 --- a/lib/matplotlib/gridspec.py +++ b/lib/matplotlib/gridspec.py @@ -195,6 +195,7 @@ def get_grid_positions(self, fig, raw=False): fig_tops, fig_bottoms = (top - cell_hs).reshape((-1, 2)).T fig_lefts, fig_rights = (left + cell_ws).reshape((-1, 2)).T + return fig_bottoms, fig_tops, fig_lefts, fig_rights def __getitem__(self, key): From 0594db478afdafb0d633af3d1d0138a4c6175b23 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Sun, 26 Apr 2020 12:36:30 -0700 Subject: [PATCH 02/17] Add kwarg option --- lib/matplotlib/_constrained_layout.py | 7 +++++-- lib/matplotlib/figure.py | 17 ++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index abf7c7e9642e..58b2644c9c78 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -72,7 +72,7 @@ def _axes_all_finite_sized(fig): ###################################################### def do_constrained_layout(fig, renderer, h_pad, w_pad, - hspace=None, wspace=None, reset=True): + hspace=None, wspace=None, squish=False): """ Do the constrained_layout. Called at draw time in ``figure.constrained_layout()`` @@ -92,6 +92,8 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad, hspace, wspace : float are in fractions of the subplot sizes. + squish : bool, default False + try to compress fixed-aspect axes. """ # Steps: @@ -228,7 +230,8 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad, else: cbook._warn_external('constrained_layout not applied. At least ' 'one axes collapsed to zero width or height.') - _squish(fig, bboxes) + if squish: + _squish(fig, bboxes) def _make_ghost_gridspec_slots(fig, gs): diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 1967be39fd21..854bdb261002 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -506,7 +506,7 @@ def set_constrained_layout(self, constrained): self._constrained_layout_pads['hspace'] = None if constrained is None: constrained = mpl.rcParams['figure.constrained_layout.use'] - self._constrained = bool(constrained) + self._constrained = constrained if isinstance(constrained, dict): self.set_constrained_layout_pads(**constrained) else: @@ -1725,12 +1725,10 @@ def draw(self, renderer): try: renderer.open_group('figure', gid=self.get_gid()) if self.get_constrained_layout() and self.axes: - self.execute_constrained_layout(renderer, reset=True) - # yay extra draw! - self.patch.draw(renderer) - mimage._draw_list_compositing_images( - renderer, self, artists, self.suppressComposite) - self.execute_constrained_layout(renderer, reset=False) + squish = False + if self.get_constrained_layout() == "squish": + squish = True + self.execute_constrained_layout(renderer, squish=squish) if self.get_tight_layout() and self.axes: @@ -2413,7 +2411,7 @@ def init_layoutbox(self): parent=None, name='figlb', artist=self) self._layoutbox.constrain_geometry(0., 0., 1., 1.) - def execute_constrained_layout(self, renderer=None, reset=True): + def execute_constrained_layout(self, renderer=None, squish=False): """ Use ``layoutbox`` to determine pos positions within axes. @@ -2439,7 +2437,8 @@ def execute_constrained_layout(self, renderer=None, reset=True): h_pad = h_pad / height if renderer is None: renderer = layoutbox.get_renderer(fig) - do_constrained_layout(fig, renderer, h_pad, w_pad, hspace, wspace, reset=reset) + do_constrained_layout(fig, renderer, h_pad, w_pad, hspace, wspace, + squish=squish) @cbook._delete_parameter("3.2", "renderer") def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None, From 4c27e80377d92749e23bd2369844cb81cc488fe6 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Sun, 26 Apr 2020 12:54:40 -0700 Subject: [PATCH 03/17] FIX: fix suptitle --- lib/matplotlib/_constrained_layout.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index 58b2644c9c78..0e8fa679ac5b 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -860,3 +860,13 @@ def _squish(fig, bboxes, w_pad=0.00, h_pad=0.0): marg = bbox.y1 - pos.y1 pos.y1 = y1 - marg cb._set_position(pos, which='original') + # need to do suptitles: + suptitle = fig._suptitle + do_suptitle = (suptitle is not None and + suptitle._layoutbox is not None and + suptitle.get_in_layout()) + if do_suptitle: + x, y = suptitle.get_position() + bbox = invTransFig(suptitle.get_window_extent(renderer)) + marg = y - bbox.y0 + suptitle.set_y(y1 + marg) From 15a31f9972099879321801afc94e8385ef6cba34 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Sun, 26 Apr 2020 13:08:23 -0700 Subject: [PATCH 04/17] FIX: fix wspace --- lib/matplotlib/_constrained_layout.py | 2 +- lib/matplotlib/axes/_base.py | 1 - lib/matplotlib/axes/_subplots.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index 0e8fa679ac5b..7c7190dbf726 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -231,7 +231,7 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad, cbook._warn_external('constrained_layout not applied. At least ' 'one axes collapsed to zero width or height.') if squish: - _squish(fig, bboxes) + _squish(fig, bboxes, w_pad=w_pad, h_pad=h_pad) def _make_ghost_gridspec_slots(fig, gs): diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index a5c02a69a3b7..c8d7522451ce 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -568,7 +568,6 @@ def __init__(self, fig, rect, self._layoutbox = None self._poslayoutbox = None self._colorbars = [] # a list of colorbars attached to this axes. - self._info = {} # dictionary of things we may want to keep track of def __getstate__(self): # The renderer should be re-created by the figure, and then cached at diff --git a/lib/matplotlib/axes/_subplots.py b/lib/matplotlib/axes/_subplots.py index 91f806cc0032..e40754a2acbc 100644 --- a/lib/matplotlib/axes/_subplots.py +++ b/lib/matplotlib/axes/_subplots.py @@ -92,7 +92,7 @@ def get_gridspec(self): def update_params(self): """Update the subplot position from ``self.figure.subplotpars``.""" self.figbox, _, _, self.numRows, self.numCols = \ - self.get_subplotspec().get_position(self.figure, + self.get_subplotspec().get_position(self.figure, return_all=True) @cbook.deprecated("3.2", alternative="ax.get_subplotspec().rowspan.start") From d995e39df38d26eca97a7f2918fa613fe4b104cb Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Fri, 1 May 2020 08:35:32 -0700 Subject: [PATCH 05/17] Rorg --- lib/matplotlib/_compress_axes.py | 220 ++++++++++++++++++++++++++ lib/matplotlib/_constrained_layout.py | 199 +---------------------- lib/matplotlib/figure.py | 98 +++++++++--- 3 files changed, 301 insertions(+), 216 deletions(-) create mode 100644 lib/matplotlib/_compress_axes.py diff --git a/lib/matplotlib/_compress_axes.py b/lib/matplotlib/_compress_axes.py new file mode 100644 index 000000000000..c7c5ccf3aeb3 --- /dev/null +++ b/lib/matplotlib/_compress_axes.py @@ -0,0 +1,220 @@ +import logging + +import numpy as np + +""" +This code attemprs to compress axes if they have excessive space between +axes, usually because the axes have fixed aspect ratios. +""" + +def compress_axes(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05): + """ + Utility that will attempt to compress axes on a figure together. + + w_pad, h_pad are inches and are the half distance to the next + axes in width and height respectively. + """ + + w, h = fig.get_size_inches() + w_pad = w_pad / w * 2 + h_pad = h_pad / h * 2 + print('compress', w_pad, h_pad) + + renderer = fig.canvas.get_renderer() + gss = set() + invTransFig = fig.transFigure.inverted().transform_bbox + + if bboxes is None: + bboxes = dict() + for ax in fig.axes: + bboxes[ax] = invTransFig(ax.get_tightbbox(renderer)) + + colorbars = [] + for ax in fig.axes: + if hasattr(ax, '_colorbar_info'): + colorbars += [ax] + elif hasattr(ax, 'get_subplotspec'): + gs = ax.get_subplotspec().get_gridspec() + gss.add(gs) + for cba in ax._colorbars: + # get the bbox including the colorbar for this axis + if cba._colorbar_info['location'] == 'right': + bboxes[ax].x1 = bboxes[cba].x1 + if cba._colorbar_info['location'] == 'left': + bboxes[ax].x0 = bboxes[cba].x0 + if cba._colorbar_info['location'] == 'top': + bboxes[ax].y1 = bboxes[cba].y1 + if cba._colorbar_info['location'] == 'bottom': + bboxes[ax].y0 = bboxes[cba].y0 + + # we placed everything, but what if there are huge gaps... + for gs in gss: + axs = [ax for ax in fig.axes + if (hasattr(ax, 'get_subplotspec') + and ax.get_subplotspec().get_gridspec() == gs)] + nrows, ncols = gs.get_geometry() + # get widths: + dxs = np.zeros((nrows, ncols)) + dys = np.zeros((nrows, ncols)) + margxs = np.zeros((nrows, ncols)) + margys = np.zeros((nrows, ncols)) + + for i in range(nrows): + for j in range(ncols): + for ax in axs: + ss = ax.get_subplotspec() + if (i in ss.rowspan) and (j in ss.colspan): + di = ss.colspan[-1] - ss.colspan[0] + 1 + dj = ss.rowspan[-1] - ss.rowspan[0] + 1 + dxs[i, j] = bboxes[ax].bounds[2] / di + if ss.colspan[-1] < ncols - 1: + dxs[i, j] = dxs[i, j] + w_pad / di + dys[i, j] = bboxes[ax].bounds[3] / dj + if ss.rowspan[0] > 0 : + dys[i, j] = dys[i, j] + h_pad / dj + orpos = ax.get_position(original=True) + margxs[i, j] = bboxes[ax].x0 - orpos.x0 + margys[i, j] = bboxes[ax].y0 - orpos.y0 + + margxs = np.flipud(margxs) + margys = np.flipud(margys) + dys = np.flipud(dys) + dxs = np.flipud(dxs) + + ddxs = np.max(dxs, axis=0) + ddys = np.max(dys, axis=1) + dx = np.sum(ddxs) + dy = np.sum(ddys) + x1 = y1 = -np.Inf + x0 = y0 = np.Inf + + if (dx < dy) and (dx < 0.9): + print('compress x') + margx = np.min(margxs, axis=0) + # Squish x! + extra = (1 - dx) / 2 + for ax in axs: + ss = ax.get_subplotspec() + orpos = ax.get_position(original=True) + x = extra + for j in range(0, ss.colspan[0]): + x += ddxs[j] + deltax = -orpos.x0 + x - margx[ss.colspan[0]] + orpos.x1 = orpos.x1 + deltax + orpos.x0 = orpos.x0 + deltax + # keep track of new bbox edges for placing colorbars + if bboxes[ax].x1 + deltax > x1: + x1 = bboxes[ax].x1 + deltax + if bboxes[ax].x0 + deltax < x0: + x0 = bboxes[ax].x0 + deltax + bboxes[ax].x0 = bboxes[ax].x0 + deltax + bboxes[ax].x1 = bboxes[ax].x1 + deltax + # Now set the new position. + ax._set_position(orpos, which='original') + # shift any colorbars belongig to this axis + for cba in ax._colorbars: + pos = cba.get_position(original=True) + if cba._colorbar_info['location'] in ['bottom', 'top']: + # shrink to make same size as active... + posac = ax.get_position(original=False) + dx = (1 - cba._colorbar_info['shrink']) * (posac.x1 - + posac.x0) / 2 + pos.x0 = posac.x0 + dx + pos.x1 = posac.x1 - dx + else: + pos.x0 = pos.x0 + deltax + pos.x1 = pos.x1 + deltax + cba._set_position(pos, which='original') + colorbars.remove(cba) + for cb in colorbars: + # shift any colorbars belonging to the gridspec. + pos = cb.get_position(original=True) + bbox = bboxes[cb] + if cb._colorbar_info['location'] == 'right': + marg = bbox.x0 - pos.x0 + x = x1 + marg + w_pad + pos.x1 = pos.x1 - pos.x0 + x + pos.x0 = x + elif cb._colorbar_info['location'] == 'left': + marg = bbox.x1 - pos.x1 + # left edge: + x = x0 - marg - w_pad + _dx = pos.x1 - pos.x0 + pos.x1 = x - marg + pos.x0 = x - marg - _dx + else: + marg = bbox.x0 - pos.x0 + pos.x0 = x0 - marg + marg = bbox.x1 - pos.x1 + pos.x1 = x1 - marg + cb._set_position(pos, which='original') + + if (dx > dy) and (dy < 0.9): + print('compress y') + margy = np.min(margys, axis=1) + # Squish y! + extra = (1 - dy) / 2 + for ax in axs: + ss = ax.get_subplotspec() + orpos = ax.get_position(original=True) + y = extra + for j in range(0, nrows - ss.rowspan[-1] - 1): + y += ddys[j] + deltay = -orpos.y0 + y - margy[nrows - ss.rowspan[-1] - 1] + orpos.y1 = orpos.y1 + deltay + orpos.y0 = orpos.y0 + deltay + ax._set_position(orpos, which='original') + # keep track of new bbox edges for placing colorbars + if bboxes[ax].y1 + deltay > y1: + y1 = bboxes[ax].y1 + deltay + if bboxes[ax].y0 + deltay < y0: + y0 = bboxes[ax].y0 + deltay + bboxes[ax].y0 = bboxes[ax].y0 + deltay + bboxes[ax].y1 = bboxes[ax].y1 + deltay + # shift any colorbars belongig to this axis + for cba in ax._colorbars: + pos = cba.get_position(original=True) + if cba._colorbar_info['location'] in ['right', 'left']: + # shrink to make same size as active... + posac = ax.get_position(original=False) + dy = (1 - cba._colorbar_info['shrink']) * (posac.y1 - + posac.y0) / 2 + pos.y0 = posac.y0 + dy + pos.y1 = posac.y1 - dy + else: + pos.y0 = pos.y0 + deltay + pos.y1 = pos.y1 + deltay + cba._set_position(pos, which='original') + colorbars.remove(cba) + for cb in colorbars: + # shift any colorbars belonging to the gridspec. + pos = cb.get_position(original=True) + bbox = bboxes[cb] + if cb._colorbar_info['location'] == 'top': + marg = bbox.y0 - pos.y0 + y = y1 + marg + h_pad + pos.y1 = pos.y1 - pos.y0 + y + pos.y0 = y + elif cb._colorbar_info['location'] == 'bottom': + marg = bbox.y1 - pos.y1 + # left edge: + y = y0 - marg - h_pad + _dy = pos.y1 - pos.y0 + pos.y1 = y - marg + pos.y0 = y - marg - _dy + else: + marg = bbox.y0 - pos.y0 + pos.y0 = y0 - marg + marg = bbox.y1 - pos.y1 + pos.y1 = y1 - marg + cb._set_position(pos, which='original') + # need to do suptitles: + suptitle = fig._suptitle + do_suptitle = (suptitle is not None and + suptitle._layoutbox is not None and + suptitle.get_in_layout()) + if do_suptitle: + x, y = suptitle.get_position() + bbox = invTransFig(suptitle.get_window_extent(renderer)) + marg = y - bbox.y0 + suptitle.set_y(y1 + marg) diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index 7c7190dbf726..a5a2293aedf4 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -230,8 +230,7 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad, else: cbook._warn_external('constrained_layout not applied. At least ' 'one axes collapsed to zero width or height.') - if squish: - _squish(fig, bboxes, w_pad=w_pad, h_pad=h_pad) + return bboxes def _make_ghost_gridspec_slots(fig, gs): @@ -269,6 +268,7 @@ def _make_layout_margins(ax, renderer, h_pad, w_pad): Returns the bbox for some width/heigth calcs outside this loop. """ + print('w_pad CL', w_pad, h_pad) fig = ax.figure invTransFig = fig.transFigure.inverted().transform_bbox pos = ax.get_position(original=True) @@ -675,198 +675,3 @@ def layoutcolorbargridspec(parents, cax, shrink, aspect, location, pad=0.05): strength='medium') return lb, lbpos - -def _squish(fig, bboxes, w_pad=0.00, h_pad=0.0): - import time - now = time.time() - renderer = fig.canvas.get_renderer() - gss = set() - invTransFig = fig.transFigure.inverted().transform_bbox - - colorbars = [] - for ax in fig.axes: - if hasattr(ax, '_colorbar_info'): - colorbars += [ax] - elif hasattr(ax, 'get_subplotspec'): - gs = ax.get_subplotspec().get_gridspec() - gss.add(gs) - for cba in ax._colorbars: - # get the bbox including the colorbar for this axis - if cba._colorbar_info['location'] == 'right': - bboxes[ax].x1 = bboxes[cba].x1 - if cba._colorbar_info['location'] == 'left': - bboxes[ax].x0 = bboxes[cba].x0 - if cba._colorbar_info['location'] == 'top': - bboxes[ax].y1 = bboxes[cba].y1 - if cba._colorbar_info['location'] == 'bottom': - bboxes[ax].y0 = bboxes[cba].y0 - - # we placed everything, but what if there are huge gaps... - for gs in gss: - axs = [ax for ax in fig.axes - if (hasattr(ax, 'get_subplotspec') - and ax.get_subplotspec().get_gridspec() == gs)] - nrows, ncols = gs.get_geometry() - # get widths: - dxs = np.zeros((nrows, ncols)) - dys = np.zeros((nrows, ncols)) - margxs = np.zeros((nrows, ncols)) - margys = np.zeros((nrows, ncols)) - - for i in range(nrows): - for j in range(ncols): - for ax in axs: - ss = ax.get_subplotspec() - if (i in ss.rowspan) and (j in ss.colspan): - di = ss.colspan[-1] - ss.colspan[0] + 1 - dj = ss.rowspan[-1] - ss.rowspan[0] + 1 - dxs[i, j] = bboxes[ax].bounds[2] / di - if ss.colspan[-1] < ncols - 1: - dxs[i, j] = dxs[i, j] + w_pad / di - dys[i, j] = bboxes[ax].bounds[3] / dj - if ss.rowspan[-1] < nrows - 1: - dys[i, j] = dys[i, j] + h_pad / dj - orpos = ax.get_position(original=True) - margxs[i, j] = bboxes[ax].x0 - orpos.x0 - margys[i, j] = bboxes[ax].y0 - orpos.y0 - - margxs = np.flipud(margxs) - margys = np.flipud(margys) - dys = np.flipud(dys) - dxs = np.flipud(dxs) - - ddxs = np.max(dxs, axis=0) - ddys = np.max(dys, axis=1) - dx = np.sum(ddxs) - dy = np.sum(ddys) - x1 = y1 = -np.Inf - x0 = y0 = np.Inf - - if (dx < dy) and (dx < 0.98): - margx = np.min(margxs, axis=0) - # Squish x! - extra = (1 - dx) / 2 - for ax in axs: - ss = ax.get_subplotspec() - orpos = ax.get_position(original=True) - x = extra - for j in range(0, ss.colspan[0]): - x += ddxs[j] - deltax = -orpos.x0 + x - margx[ss.colspan[0]] - orpos.x1 = orpos.x1 + deltax - orpos.x0 = orpos.x0 + deltax - # keep track of new bbox edges for placing colorbars - if bboxes[ax].x1 + deltax > x1: - x1 = bboxes[ax].x1 + deltax - if bboxes[ax].x0 + deltax < x0: - x0 = bboxes[ax].x0 + deltax - bboxes[ax].x0 = bboxes[ax].x0 + deltax - bboxes[ax].x1 = bboxes[ax].x1 + deltax - # Now set the new position. - ax._set_position(orpos, which='original') - # shift any colorbars belongig to this axis - for cba in ax._colorbars: - pos = cba.get_position(original=True) - if cba._colorbar_info['location'] in ['bottom', 'top']: - # shrink to make same size as active... - posac = ax.get_position(original=False) - dx = (1 - cba._colorbar_info['shrink']) * (posac.x1 - - posac.x0) / 2 - pos.x0 = posac.x0 + dx - pos.x1 = posac.x1 - dx - else: - pos.x0 = pos.x0 + deltax - pos.x1 = pos.x1 + deltax - cba._set_position(pos, which='original') - colorbars.remove(cba) - for cb in colorbars: - # shift any colorbars belonging to the gridspec. - pos = cb.get_position(original=True) - bbox = bboxes[cb] - if cb._colorbar_info['location'] == 'right': - marg = bbox.x0 - pos.x0 - x = x1 + marg + w_pad - pos.x1 = pos.x1 - pos.x0 + x - pos.x0 = x - elif cb._colorbar_info['location'] == 'left': - marg = bbox.x1 - pos.x1 - # left edge: - x = x0 - marg - w_pad - _dx = pos.x1 - pos.x0 - pos.x1 = x - marg - pos.x0 = x - marg - _dx - else: - marg = bbox.x0 - pos.x0 - pos.x0 = x0 - marg - marg = bbox.x1 - pos.x1 - pos.x1 = x1 - marg - cb._set_position(pos, which='original') - - if (dx > dy) and (dy < 0.98): - margy = np.min(margys, axis=1) - # Squish y! - extra = (1 - dy) / 2 - for ax in axs: - ss = ax.get_subplotspec() - orpos = ax.get_position(original=True) - y = extra - for j in range(0, nrows - ss.rowspan[-1] - 1): - y += ddys[j] - deltay = -orpos.y0 + y - margy[nrows - ss.rowspan[-1] - 1] - orpos.y1 = orpos.y1 + deltay - orpos.y0 = orpos.y0 + deltay - ax._set_position(orpos, which='original') - # keep track of new bbox edges for placing colorbars - if bboxes[ax].y1 + deltay > y1: - y1 = bboxes[ax].y1 + deltay - if bboxes[ax].y0 + deltay < y0: - y0 = bboxes[ax].y0 + deltay - bboxes[ax].y0 = bboxes[ax].y0 + deltay - bboxes[ax].y1 = bboxes[ax].y1 + deltay - # shift any colorbars belongig to this axis - for cba in ax._colorbars: - pos = cba.get_position(original=True) - if cba._colorbar_info['location'] in ['right', 'left']: - # shrink to make same size as active... - posac = ax.get_position(original=False) - dy = (1 - cba._colorbar_info['shrink']) * (posac.y1 - - posac.y0) / 2 - pos.y0 = posac.y0 + dy - pos.y1 = posac.y1 - dy - else: - pos.y0 = pos.y0 + deltay - pos.y1 = pos.y1 + deltay - cba._set_position(pos, which='original') - colorbars.remove(cba) - for cb in colorbars: - # shift any colorbars belonging to the gridspec. - pos = cb.get_position(original=True) - bbox = bboxes[cb] - if cb._colorbar_info['location'] == 'top': - marg = bbox.y0 - pos.y0 - y = y1 + marg + h_pad - pos.y1 = pos.y1 - pos.y0 + y - pos.y0 = y - elif cb._colorbar_info['location'] == 'bottom': - marg = bbox.y1 - pos.y1 - # left edge: - y = y0 - marg - h_pad - _dy = pos.y1 - pos.y0 - pos.y1 = y - marg - pos.y0 = y - marg - _dy - else: - marg = bbox.y0 - pos.y0 - pos.y0 = y0 - marg - marg = bbox.y1 - pos.y1 - pos.y1 = y1 - marg - cb._set_position(pos, which='original') - # need to do suptitles: - suptitle = fig._suptitle - do_suptitle = (suptitle is not None and - suptitle._layoutbox is not None and - suptitle.get_in_layout()) - if do_suptitle: - x, y = suptitle.get_position() - bbox = invTransFig(suptitle.get_window_extent(renderer)) - marg = y - bbox.y0 - suptitle.set_y(y1 + marg) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 854bdb261002..f22c5e226df8 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -10,6 +10,7 @@ import logging from numbers import Integral +import warnings import numpy as np @@ -23,8 +24,10 @@ FigureCanvasBase, NonGuiException, MouseButton) import matplotlib.cbook as cbook import matplotlib.colorbar as cbar +from matplotlib.font_manager import FontProperties import matplotlib.image as mimage + from matplotlib.axes import Axes, SubplotBase, subplot_class_factory from matplotlib.blocking_input import BlockingMouseInput, BlockingKeyMouseInput from matplotlib.gridspec import GridSpec, SubplotSpec @@ -256,6 +259,7 @@ def __init__(self, subplotpars=None, # rc figure.subplot.* tight_layout=None, # rc figure.autolayout constrained_layout=None, # rc figure.constrained_layout.use + compress_axes=None, ): """ Parameters @@ -297,6 +301,11 @@ def __init__(self, :doc:`/tutorials/intermediate/constrainedlayout_guide` for examples. (Note: does not work with `add_subplot` or `~.pyplot.subplot2grid`.) + + compress_axes : bool, default: :rc:`figure.compress_axes.use` + If ``True`` attempt to pack axes as close to one another + as possible. Useful when axes have fixed aspect ratio and the + default layouts leave excess space between axes. """ super().__init__() # remove the non-figure artist _axes property @@ -349,6 +358,9 @@ def __init__(self, # set in set_constrained_layout_pads() self.set_constrained_layout(constrained_layout) + self._compress_axes = compress_axes + self._axpos_cache = None + self.set_tight_layout(tight_layout) self._axstack = _AxesStack() # track all figure axes and current axes @@ -1724,19 +1736,38 @@ def draw(self, renderer): try: renderer.open_group('figure', gid=self.get_gid()) - if self.get_constrained_layout() and self.axes: - squish = False - if self.get_constrained_layout() == "squish": - squish = True - self.execute_constrained_layout(renderer, squish=squish) - - - if self.get_tight_layout() and self.axes: - try: - self.tight_layout(**self._tight_parameters) - except ValueError: - pass - # ValueError can occur when resizing a window. + bboxes = None + if self.axes: + adjusted = False + if self.get_tight_layout(): + try: + w_pad, h_pad = self.tight_layout( + **self._tight_parameters) + # convert pads to inches... + fs = FontProperties(size=mpl.rcParams["font.size"]) + fs = fs.get_size_in_points() / 72 + w, h = self.get_size_inches() + w_pad = w_pad * fs + h_pad = h_pad * fs + + w_pad = w_pad / 2 + h_pad = h_pad / 2 + adjusted = True + except ValueError: + pass + elif self.get_constrained_layout(): + bboxes = self.execute_constrained_layout(renderer) + adjusted = True + w_pad, h_pad, _, _ = self.get_constrained_layout_pads() + + if self.get_compress_axes(): + if not adjusted: + h_pad = 0.05 + w_pad = 0.05 + self.subplots_adjust() + + self.execute_compress_axes(bboxes=bboxes, w_pad=w_pad, + h_pad=h_pad) self.patch.draw(renderer) mimage._draw_list_compositing_images( @@ -2411,7 +2442,7 @@ def init_layoutbox(self): parent=None, name='figlb', artist=self) self._layoutbox.constrain_geometry(0., 0., 1., 1.) - def execute_constrained_layout(self, renderer=None, squish=False): + def execute_constrained_layout(self, renderer=None): """ Use ``layoutbox`` to determine pos positions within axes. @@ -2431,14 +2462,32 @@ def execute_constrained_layout(self, renderer=None, squish=False): return w_pad, h_pad, wspace, hspace = self.get_constrained_layout_pads() # convert to unit-relative lengths - fig = self - width, height = fig.get_size_inches() + width, height = self.get_size_inches() + print('before', w_pad, h_pad) w_pad = w_pad / width h_pad = h_pad / height + print('after', w_pad, h_pad) if renderer is None: - renderer = layoutbox.get_renderer(fig) - do_constrained_layout(fig, renderer, h_pad, w_pad, hspace, wspace, - squish=squish) + renderer = layoutbox.get_renderer(self) + bboxes = do_constrained_layout(self, renderer, h_pad, w_pad, hspace, + wspace) + return bboxes + + def execute_compress_axes(self, *, bboxes=None, h_pad=0.05, w_pad=0.05): + """ + Execute the axes compression for the figure. + + TODO: + + Parameters + ---------- + + bboxes : list of bboxes | optional + tight bboxes of the axes in the figure; saves recalculating them. + + """ + from matplotlib._compress_axes import compress_axes + compress_axes(self, bboxes=bboxes, w_pad=w_pad, h_pad=h_pad) @cbook._delete_parameter("3.2", "renderer") def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None, @@ -2485,9 +2534,17 @@ def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None, kwargs = get_tight_layout_figure( self, self.axes, subplotspec_list, renderer, pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect) + if kwargs: self.subplots_adjust(**kwargs) + if w_pad is None: + w_pad = pad + if h_pad is None: + h_pad = pad + + return w_pad, h_pad + def align_xlabels(self, axs=None): """ Align the ylabels of subplots in the same subplot column if label @@ -2677,6 +2734,9 @@ def add_gridspec(self, nrows, ncols, **kwargs): self._gridspecs.append(gs) return gs + def get_compress_axes(self): + return self._compress_axes + def figaspect(arg): """ From a95ac4a6b3b5d676ff39049e5541e61f5a490ec2 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Sun, 3 May 2020 16:11:27 -0700 Subject: [PATCH 06/17] FIX: --- ...{_compress_axes.py => _compress_layout.py} | 52 ++++++---- lib/matplotlib/_constrained_layout.py | 1 - lib/matplotlib/colorbar.py | 9 +- lib/matplotlib/figure.py | 95 ++++++++++--------- 4 files changed, 91 insertions(+), 66 deletions(-) rename lib/matplotlib/{_compress_axes.py => _compress_layout.py} (84%) diff --git a/lib/matplotlib/_compress_axes.py b/lib/matplotlib/_compress_layout.py similarity index 84% rename from lib/matplotlib/_compress_axes.py rename to lib/matplotlib/_compress_layout.py index c7c5ccf3aeb3..2b22c9c3f4a2 100644 --- a/lib/matplotlib/_compress_axes.py +++ b/lib/matplotlib/_compress_layout.py @@ -7,7 +7,8 @@ axes, usually because the axes have fixed aspect ratios. """ -def compress_axes(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05): +def compress_layout(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05, + wspace=0, hspace=0): """ Utility that will attempt to compress axes on a figure together. @@ -18,7 +19,6 @@ def compress_axes(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05): w, h = fig.get_size_inches() w_pad = w_pad / w * 2 h_pad = h_pad / h * 2 - print('compress', w_pad, h_pad) renderer = fig.canvas.get_renderer() gss = set() @@ -49,6 +49,7 @@ def compress_axes(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05): # we placed everything, but what if there are huge gaps... for gs in gss: + print('gs', gs) axs = [ax for ax in fig.axes if (hasattr(ax, 'get_subplotspec') and ax.get_subplotspec().get_gridspec() == gs)] @@ -64,17 +65,19 @@ def compress_axes(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05): for ax in axs: ss = ax.get_subplotspec() if (i in ss.rowspan) and (j in ss.colspan): + orpos = ax.get_position(original=True) di = ss.colspan[-1] - ss.colspan[0] + 1 dj = ss.rowspan[-1] - ss.rowspan[0] + 1 dxs[i, j] = bboxes[ax].bounds[2] / di + pad = np.max([w_pad, wspace * bboxes[ax].bounds[2]]) if ss.colspan[-1] < ncols - 1: - dxs[i, j] = dxs[i, j] + w_pad / di + dxs[i, j] = dxs[i, j] + pad / di dys[i, j] = bboxes[ax].bounds[3] / dj + pad = np.max([h_pad, hspace * bboxes[ax].bounds[3]]) if ss.rowspan[0] > 0 : - dys[i, j] = dys[i, j] + h_pad / dj - orpos = ax.get_position(original=True) - margxs[i, j] = bboxes[ax].x0 - orpos.x0 - margys[i, j] = bboxes[ax].y0 - orpos.y0 + dys[i, j] = dys[i, j] + pad / dj + margxs[i, j] = orpos.x0 - bboxes[ax].x0 + margys[i, j] = orpos.y0 - bboxes[ax].y0 margxs = np.flipud(margxs) margys = np.flipud(margys) @@ -89,8 +92,7 @@ def compress_axes(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05): x0 = y0 = np.Inf if (dx < dy) and (dx < 0.9): - print('compress x') - margx = np.min(margxs, axis=0) + margx = np.max(margxs, axis=0) # Squish x! extra = (1 - dx) / 2 for ax in axs: @@ -99,7 +101,7 @@ def compress_axes(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05): x = extra for j in range(0, ss.colspan[0]): x += ddxs[j] - deltax = -orpos.x0 + x - margx[ss.colspan[0]] + deltax = -orpos.x0 + x + margx[ss.colspan[0]] orpos.x1 = orpos.x1 + deltax orpos.x0 = orpos.x0 + deltax # keep track of new bbox edges for placing colorbars @@ -143,24 +145,26 @@ def compress_axes(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05): pos.x1 = x - marg pos.x0 = x - marg - _dx else: + ddx = (x1 - x0) * (1 - cb._colorbar_info['shrink']) / 2 marg = bbox.x0 - pos.x0 - pos.x0 = x0 - marg + pos.x0 = x0 - marg + ddx marg = bbox.x1 - pos.x1 - pos.x1 = x1 - marg + pos.x1 = x1 - marg - ddx cb._set_position(pos, which='original') if (dx > dy) and (dy < 0.9): - print('compress y') margy = np.min(margys, axis=1) # Squish y! + print('squish y') extra = (1 - dy) / 2 - for ax in axs: + for nn, ax in enumerate(axs): + print(f'Axes {nn}') ss = ax.get_subplotspec() orpos = ax.get_position(original=True) y = extra for j in range(0, nrows - ss.rowspan[-1] - 1): y += ddys[j] - deltay = -orpos.y0 + y - margy[nrows - ss.rowspan[-1] - 1] + deltay = -orpos.y0 + y + margy[nrows - ss.rowspan[-1] - 1] orpos.y1 = orpos.y1 + deltay orpos.y0 = orpos.y0 + deltay ax._set_position(orpos, which='original') @@ -173,20 +177,24 @@ def compress_axes(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05): bboxes[ax].y1 = bboxes[ax].y1 + deltay # shift any colorbars belongig to this axis for cba in ax._colorbars: + print('ax', ax, 'cba', cba) pos = cba.get_position(original=True) if cba._colorbar_info['location'] in ['right', 'left']: # shrink to make same size as active... posac = ax.get_position(original=False) - dy = (1 - cba._colorbar_info['shrink']) * (posac.y1 - + ddy_ = (1 - cba._colorbar_info['shrink']) * (posac.y1 - posac.y0) / 2 - pos.y0 = posac.y0 + dy - pos.y1 = posac.y1 - dy + pos.y0 = posac.y0 + ddy_ + pos.y1 = posac.y1 - ddy_ else: pos.y0 = pos.y0 + deltay pos.y1 = pos.y1 + deltay + print('pos', pos) cba._set_position(pos, which='original') colorbars.remove(cba) + # print(colorbars) for cb in colorbars: + print('In here?') # shift any colorbars belonging to the gridspec. pos = cb.get_position(original=True) bbox = bboxes[cb] @@ -203,10 +211,12 @@ def compress_axes(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05): pos.y1 = y - marg pos.y0 = y - marg - _dy else: + ddy = (y1 - y0) * (1 - cb._colorbar_info['shrink']) / 2 marg = bbox.y0 - pos.y0 - pos.y0 = y0 - marg + pos.y0 = y0 - marg + ddy marg = bbox.y1 - pos.y1 - pos.y1 = y1 - marg + pos.y1 = y1 - marg - ddy + cb._set_position(pos, which='original') # need to do suptitles: suptitle = fig._suptitle @@ -217,4 +227,4 @@ def compress_axes(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05): x, y = suptitle.get_position() bbox = invTransFig(suptitle.get_window_extent(renderer)) marg = y - bbox.y0 - suptitle.set_y(y1 + marg) + suptitle.set_y(y1 + marg + h_pad) diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index a5a2293aedf4..790c7193fc89 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -268,7 +268,6 @@ def _make_layout_margins(ax, renderer, h_pad, w_pad): Returns the bbox for some width/heigth calcs outside this loop. """ - print('w_pad CL', w_pad, h_pad) fig = ax.figure invTransFig = fig.transFigure.inverted().transform_bbox pos = ax.get_position(original=True) diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 187c7608934c..703f8927d86e 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -1496,7 +1496,8 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15, if len(parents) == 1: # tell the parent it has a colorbar ax._colorbars += [cax] - + else: + print('Not addings!') # OK, now make a layoutbox for the cb axis. Later, we will use this # to make the colorbar fit nicely. @@ -1613,6 +1614,12 @@ def make_axes_gridspec(parent, *, fraction=0.15, shrink=1.0, aspect=20, **kw): fig = parent.get_figure() cax = fig.add_subplot(gs2[1], label="") cax.set_aspect(aspect, anchor=anchor, adjustable='box') + cax._colorbar_info = {} + location = 'right' if orientation=='vertical' else 'bottom' + cax._colorbar_info['location'] = location + cax._colorbar_info['parents'] = [parent] + cax._colorbar_info['shrink'] = shrink + parent._colorbars += [cax] return cax, kw diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index f22c5e226df8..ae853097a5ce 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -259,7 +259,7 @@ def __init__(self, subplotpars=None, # rc figure.subplot.* tight_layout=None, # rc figure.autolayout constrained_layout=None, # rc figure.constrained_layout.use - compress_axes=None, + compress_layout=None, ): """ Parameters @@ -302,7 +302,7 @@ def __init__(self, for examples. (Note: does not work with `add_subplot` or `~.pyplot.subplot2grid`.) - compress_axes : bool, default: :rc:`figure.compress_axes.use` + compress_layout : bool, default: False If ``True`` attempt to pack axes as close to one another as possible. Useful when axes have fixed aspect ratio and the default layouts leave excess space between axes. @@ -358,7 +358,7 @@ def __init__(self, # set in set_constrained_layout_pads() self.set_constrained_layout(constrained_layout) - self._compress_axes = compress_axes + self._compress_layout = compress_layout self._axpos_cache = None self.set_tight_layout(tight_layout) @@ -1736,39 +1736,7 @@ def draw(self, renderer): try: renderer.open_group('figure', gid=self.get_gid()) - bboxes = None - if self.axes: - adjusted = False - if self.get_tight_layout(): - try: - w_pad, h_pad = self.tight_layout( - **self._tight_parameters) - # convert pads to inches... - fs = FontProperties(size=mpl.rcParams["font.size"]) - fs = fs.get_size_in_points() / 72 - w, h = self.get_size_inches() - w_pad = w_pad * fs - h_pad = h_pad * fs - - w_pad = w_pad / 2 - h_pad = h_pad / 2 - adjusted = True - except ValueError: - pass - elif self.get_constrained_layout(): - bboxes = self.execute_constrained_layout(renderer) - adjusted = True - w_pad, h_pad, _, _ = self.get_constrained_layout_pads() - - if self.get_compress_axes(): - if not adjusted: - h_pad = 0.05 - w_pad = 0.05 - self.subplots_adjust() - - self.execute_compress_axes(bboxes=bboxes, w_pad=w_pad, - h_pad=h_pad) - + self._execute_layouts(renderer) self.patch.draw(renderer) mimage._draw_list_compositing_images( renderer, self, artists, self.suppressComposite) @@ -2442,6 +2410,47 @@ def init_layoutbox(self): parent=None, name='figlb', artist=self) self._layoutbox.constrain_geometry(0., 0., 1., 1.) + def _execute_layouts(self, renderer): + """ + Called by figure.draw. Is the place where all the + layout logic gets carried out... + """ + bboxes = None + if self.axes: + adjusted = False + if self.get_tight_layout(): + try: + w_pad, h_pad = self.tight_layout( + **self._tight_parameters) + # convert pads to inches... + fs = FontProperties(size=mpl.rcParams["font.size"]) + fs = fs.get_size_in_points() / 72 + w, h = self.get_size_inches() + w_pad = w_pad * fs / 2 + h_pad = h_pad * fs / 2 + wspace = 0 + hspace = 0 + adjusted = True + except ValueError: + pass + elif self.get_constrained_layout(): + bboxes = self.execute_constrained_layout(renderer) + adjusted = True + w_pad, h_pad, wspace, hspace = \ + self.get_constrained_layout_pads() + + if self.get_compress_layout(): + if not adjusted: + h_pad = 0.02 + w_pad = 0.02 + wspace = self.subplotpars.wspace + hspace = self.subplotpars.hspace + self.subplots_adjust() + + self.execute_compress_layout(bboxes=bboxes, w_pad=w_pad, + h_pad=h_pad, wspace=wspace, + hspace=hspace) + def execute_constrained_layout(self, renderer=None): """ Use ``layoutbox`` to determine pos positions within axes. @@ -2463,17 +2472,16 @@ def execute_constrained_layout(self, renderer=None): w_pad, h_pad, wspace, hspace = self.get_constrained_layout_pads() # convert to unit-relative lengths width, height = self.get_size_inches() - print('before', w_pad, h_pad) w_pad = w_pad / width h_pad = h_pad / height - print('after', w_pad, h_pad) if renderer is None: renderer = layoutbox.get_renderer(self) bboxes = do_constrained_layout(self, renderer, h_pad, w_pad, hspace, wspace) return bboxes - def execute_compress_axes(self, *, bboxes=None, h_pad=0.05, w_pad=0.05): + def execute_compress_layout(self, *, bboxes=None, w_pad=0.05, h_pad=0.05, + wspace=0, hspace=0): """ Execute the axes compression for the figure. @@ -2486,8 +2494,9 @@ def execute_compress_axes(self, *, bboxes=None, h_pad=0.05, w_pad=0.05): tight bboxes of the axes in the figure; saves recalculating them. """ - from matplotlib._compress_axes import compress_axes - compress_axes(self, bboxes=bboxes, w_pad=w_pad, h_pad=h_pad) + from matplotlib._compress_layout import compress_layout + compress_layout(self, bboxes=bboxes, w_pad=w_pad, h_pad=h_pad, + wspace=wspace, hspace=hspace) @cbook._delete_parameter("3.2", "renderer") def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None, @@ -2734,8 +2743,8 @@ def add_gridspec(self, nrows, ncols, **kwargs): self._gridspecs.append(gs) return gs - def get_compress_axes(self): - return self._compress_axes + def get_compress_layout(self): + return self._compress_layout def figaspect(arg): From 2f76ffd0280758d54d41669af8324c6e27dd9b84 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Sun, 3 May 2020 16:19:07 -0700 Subject: [PATCH 07/17] FIX: --- lib/matplotlib/_constrained_layout.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index 790c7193fc89..8a3c57278d6c 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -48,8 +48,6 @@ import numpy as np import matplotlib.cbook as cbook -import matplotlib.colorbar as mcolorbar -import matplotlib.transforms as mtransforms import matplotlib._layoutbox as layoutbox From c700f7e8ec20052b28647dd1e210db6a3116095e Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 5 May 2020 13:38:37 -0700 Subject: [PATCH 08/17] DOC: add example --- .../compress_axes.py | 93 +++++++++++++++++++ lib/matplotlib/_compress_layout.py | 6 -- lib/matplotlib/colorbar.py | 2 - 3 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 examples/subplots_axes_and_figures/compress_axes.py diff --git a/examples/subplots_axes_and_figures/compress_axes.py b/examples/subplots_axes_and_figures/compress_axes.py new file mode 100644 index 000000000000..0750eab246d1 --- /dev/null +++ b/examples/subplots_axes_and_figures/compress_axes.py @@ -0,0 +1,93 @@ +""" +=========================== +Compress axes layout option +=========================== + +If a grid of subplot axes have fixed aspect ratios, the axes will usually +be too far apart in one of the dimensions. For simple layouts +the ``compress_layout=True`` option to `.Figure.figure` or `.subplots` can +try to compress that dimension so the axes are a similar distance apart in +both dimensions. +""" + +import matplotlib.pyplot as plt +import numpy as np + +############################################################################# +# +# The default behavior with constrained_layout. Note how there is a large +# horizontal space between the axes. + +fig, axs = plt.subplots(2, 2, figsize=(5, 3), facecolor='0.75', + sharex=True, sharey=True, constrained_layout=True) +for ax in axs.flat: + ax.set_aspect(1.0) +plt.show() + +############################################################################# +# +# Adding ``compress_layout=True`` attempts to collapse this space: + +fig, axs = plt.subplots(2, 2, figsize=(5, 3), facecolor='0.75', + sharex=True, sharey=True, constrained_layout=True, + compress_layout=True) +for ax in axs.flat: + ax.set_aspect(1.0) +plt.show() + +############################################################################# +# +# Compatibility +# ------------- +# +# Currently this works with ``constrained_layout=True`` for simple layouts +# that do not have nested gridspec layouts +# (:doc:`gallery/subplots_axes_and_figures/gridspec_nested.html`). This +# includes simple colorbar layouts with ``constrained_layout``: + +fig, axs = plt.subplots(2, 2, figsize=(5, 3), facecolor='0.75', + sharex=True, sharey=True, constrained_layout=True, + compress_layout=True) +for ax in axs.flat: + ax.set_aspect(1.0) + pc = ax.pcolormesh(np.random.randn(20,20)) + fig.colorbar(pc, ax=ax) +plt.show() + +fig, axs = plt.subplots(2, 2, figsize=(5, 3), facecolor='0.75', + sharex=True, sharey=True, constrained_layout=True, + compress_layout=True) +for ax in axs.flat: + ax.set_aspect(1.0) + pc = ax.pcolormesh(np.random.randn(20,20)) +fig.colorbar(pc, ax=axs) +plt.show() + +############################################################################# +# Compatibility is currently not as good with ``tight_layout`` or no layout +# manager, primarily because colorbars are implimented with nested gridspecs. + +for tl in [True, False]: + fig, axs = plt.subplots(2, 2, figsize=(5, 3), facecolor='0.75', + sharex=True, sharey=True, tight_layout=tl, + compress_layout=True) + for ax in axs.flat: + ax.set_aspect(1.0) + pc = ax.pcolormesh(np.random.randn(20,20)) + fig.colorbar(pc, ax=ax) + fig.suptitle(f'Tight Layout: {tl}') +plt.show() + +############################################################################# +# However, both work with simple layouts that do not have colorbars. + +for tl in [True, False]: + fig, axs = plt.subplots(2, 2, figsize=(5, 3), facecolor='0.75', + sharex=True, sharey=True, tight_layout=tl, + compress_layout=True) + for ax in axs.flat: + ax.set_aspect(1.0) + pc = ax.pcolormesh(np.random.randn(20,20)) + # fig.colorbar(pc, ax=ax) + fig.suptitle(f'Tight Layout: {tl}') +plt.show() diff --git a/lib/matplotlib/_compress_layout.py b/lib/matplotlib/_compress_layout.py index 2b22c9c3f4a2..70ad5c0ceb19 100644 --- a/lib/matplotlib/_compress_layout.py +++ b/lib/matplotlib/_compress_layout.py @@ -49,7 +49,6 @@ def compress_layout(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05, # we placed everything, but what if there are huge gaps... for gs in gss: - print('gs', gs) axs = [ax for ax in fig.axes if (hasattr(ax, 'get_subplotspec') and ax.get_subplotspec().get_gridspec() == gs)] @@ -155,10 +154,8 @@ def compress_layout(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05, if (dx > dy) and (dy < 0.9): margy = np.min(margys, axis=1) # Squish y! - print('squish y') extra = (1 - dy) / 2 for nn, ax in enumerate(axs): - print(f'Axes {nn}') ss = ax.get_subplotspec() orpos = ax.get_position(original=True) y = extra @@ -177,7 +174,6 @@ def compress_layout(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05, bboxes[ax].y1 = bboxes[ax].y1 + deltay # shift any colorbars belongig to this axis for cba in ax._colorbars: - print('ax', ax, 'cba', cba) pos = cba.get_position(original=True) if cba._colorbar_info['location'] in ['right', 'left']: # shrink to make same size as active... @@ -189,12 +185,10 @@ def compress_layout(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05, else: pos.y0 = pos.y0 + deltay pos.y1 = pos.y1 + deltay - print('pos', pos) cba._set_position(pos, which='original') colorbars.remove(cba) # print(colorbars) for cb in colorbars: - print('In here?') # shift any colorbars belonging to the gridspec. pos = cb.get_position(original=True) bbox = bboxes[cb] diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 703f8927d86e..8fe0c763a333 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -1496,8 +1496,6 @@ def make_axes(parents, location=None, orientation=None, fraction=0.15, if len(parents) == 1: # tell the parent it has a colorbar ax._colorbars += [cax] - else: - print('Not addings!') # OK, now make a layoutbox for the cb axis. Later, we will use this # to make the colorbar fit nicely. From 261088a36fb3b24d30bb54f93515d0e8d3a16ac4 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 5 May 2020 13:40:06 -0700 Subject: [PATCH 09/17] DOC: add example --- examples/subplots_axes_and_figures/compress_axes.py | 4 ++-- lib/matplotlib/figure.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/subplots_axes_and_figures/compress_axes.py b/examples/subplots_axes_and_figures/compress_axes.py index 0750eab246d1..ce3aef4ed5fc 100644 --- a/examples/subplots_axes_and_figures/compress_axes.py +++ b/examples/subplots_axes_and_figures/compress_axes.py @@ -42,7 +42,7 @@ # # Currently this works with ``constrained_layout=True`` for simple layouts # that do not have nested gridspec layouts -# (:doc:`gallery/subplots_axes_and_figures/gridspec_nested.html`). This +# (:doc:`/gallery/subplots_axes_and_figures/gridspec_nested`). This # includes simple colorbar layouts with ``constrained_layout``: fig, axs = plt.subplots(2, 2, figsize=(5, 3), facecolor='0.75', @@ -79,7 +79,7 @@ plt.show() ############################################################################# -# However, both work with simple layouts that do not have colorbars. +# However, both work with simple layouts that do not have colorbars. for tl in [True, False]: fig, axs = plt.subplots(2, 2, figsize=(5, 3), facecolor='0.75', diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index ae853097a5ce..90e26efa9a14 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -305,7 +305,8 @@ def __init__(self, compress_layout : bool, default: False If ``True`` attempt to pack axes as close to one another as possible. Useful when axes have fixed aspect ratio and the - default layouts leave excess space between axes. + default layouts leave excess space between axes. See + :doc:`/gallery/subplots_axes_and_figures/compress_axes` """ super().__init__() # remove the non-figure artist _axes property From 5aa604d6ca5aab895e91258ffec0d7ca8c9a548c Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 5 May 2020 13:50:54 -0700 Subject: [PATCH 10/17] DOC: add example --- examples/subplots_axes_and_figures/compress_axes.py | 6 +++--- lib/matplotlib/_compress_layout.py | 3 +-- lib/matplotlib/colorbar.py | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/subplots_axes_and_figures/compress_axes.py b/examples/subplots_axes_and_figures/compress_axes.py index ce3aef4ed5fc..18606ed7c1d7 100644 --- a/examples/subplots_axes_and_figures/compress_axes.py +++ b/examples/subplots_axes_and_figures/compress_axes.py @@ -59,7 +59,7 @@ compress_layout=True) for ax in axs.flat: ax.set_aspect(1.0) - pc = ax.pcolormesh(np.random.randn(20,20)) + pc = ax.pcolormesh(np.random.randn(20, 20)) fig.colorbar(pc, ax=axs) plt.show() @@ -73,7 +73,7 @@ compress_layout=True) for ax in axs.flat: ax.set_aspect(1.0) - pc = ax.pcolormesh(np.random.randn(20,20)) + pc = ax.pcolormesh(np.random.randn(20, 20)) fig.colorbar(pc, ax=ax) fig.suptitle(f'Tight Layout: {tl}') plt.show() @@ -87,7 +87,7 @@ compress_layout=True) for ax in axs.flat: ax.set_aspect(1.0) - pc = ax.pcolormesh(np.random.randn(20,20)) + pc = ax.pcolormesh(np.random.randn(20, 20)) # fig.colorbar(pc, ax=ax) fig.suptitle(f'Tight Layout: {tl}') plt.show() diff --git a/lib/matplotlib/_compress_layout.py b/lib/matplotlib/_compress_layout.py index 70ad5c0ceb19..4d8d1137bf51 100644 --- a/lib/matplotlib/_compress_layout.py +++ b/lib/matplotlib/_compress_layout.py @@ -1,5 +1,3 @@ -import logging - import numpy as np """ @@ -7,6 +5,7 @@ axes, usually because the axes have fixed aspect ratios. """ + def compress_layout(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05, wspace=0, hspace=0): """ diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 8fe0c763a333..2d3d778a8de0 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -1613,7 +1613,7 @@ def make_axes_gridspec(parent, *, fraction=0.15, shrink=1.0, aspect=20, **kw): cax = fig.add_subplot(gs2[1], label="") cax.set_aspect(aspect, anchor=anchor, adjustable='box') cax._colorbar_info = {} - location = 'right' if orientation=='vertical' else 'bottom' + location = 'right' if orientation == 'vertical' else 'bottom' cax._colorbar_info['location'] = location cax._colorbar_info['parents'] = [parent] cax._colorbar_info['shrink'] = shrink From 864365ac4531371c1d7f95e833dc4c9e15986df2 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 5 May 2020 14:01:39 -0700 Subject: [PATCH 11/17] TST --- .../tests/test_constrainedlayout.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/lib/matplotlib/tests/test_constrainedlayout.py b/lib/matplotlib/tests/test_constrainedlayout.py index 46e6b9663ef3..d9de235f50d2 100644 --- a/lib/matplotlib/tests/test_constrainedlayout.py +++ b/lib/matplotlib/tests/test_constrainedlayout.py @@ -399,3 +399,50 @@ def test_hidden_axes(): np.testing.assert_allclose( extents1, [0.045552, 0.548288, 0.47319, 0.982638], rtol=1e-5) + +def test_compressed(): + # Test that the compressed layout option works: + fig, axs = plt.subplots(2, 2, constrained_layout=True, + compress_layout=True, + sharex=True, sharey=True, + figsize=(5, 3)) + + for ax in axs.flat: + ax.set_aspect(1.) + fig.canvas.draw() + extents = np.copy(axs[0, 0].get_position().extents) + np.testing.assert_allclose(extents, + [0.243692, 0.571899, 0.479719, 0.965277], rtol=1e-5) + + +def test_compressed_cbars(): + # Test that the compressed layout option works: + fig, axs = plt.subplots(2, 2, constrained_layout=True, + compress_layout=True, + sharex=True, sharey=True, + figsize=(5, 3)) + + for ax in axs.flat: + ax.set_aspect(1.) + pc = ax.pcolormesh(np.random.randn(20, 20)) + fig.colorbar(pc, ax=ax) + fig.canvas.draw() + extents = np.copy(axs[0, 0].get_position().extents) + np.testing.assert_allclose(extents, + [0.119849, 0.571899, 0.355875, 0.965277], rtol=1e-5) + +def test_compressed_onecbar(): + # Test that the compressed layout option works: + fig, axs = plt.subplots(2, 2, constrained_layout=True, + compress_layout=True, + sharex=True, sharey=True, + figsize=(5, 3)) + + for ax in axs.flat: + ax.set_aspect(1.) + pc = ax.pcolormesh(np.random.randn(20, 20)) + fig.colorbar(pc, ax=axs) + fig.canvas.draw() + extents = np.copy(axs[0, 0].get_position().extents) + np.testing.assert_allclose(extents, + [0.255883, 0.571899, 0.491909, 0.965277], rtol=1e-5) From 8d04411f2e4471ec0423027c0c7582dde8682fbc Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 5 May 2020 14:08:18 -0700 Subject: [PATCH 12/17] TST/FLK8 --- .../subplots_axes_and_figures/compress_axes.py | 2 +- lib/matplotlib/_compress_layout.py | 18 +++++++++--------- lib/matplotlib/_constrained_layout.py | 2 +- lib/matplotlib/figure.py | 1 - lib/matplotlib/tests/test_constrainedlayout.py | 2 ++ 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/examples/subplots_axes_and_figures/compress_axes.py b/examples/subplots_axes_and_figures/compress_axes.py index 18606ed7c1d7..ae93616d8687 100644 --- a/examples/subplots_axes_and_figures/compress_axes.py +++ b/examples/subplots_axes_and_figures/compress_axes.py @@ -50,7 +50,7 @@ compress_layout=True) for ax in axs.flat: ax.set_aspect(1.0) - pc = ax.pcolormesh(np.random.randn(20,20)) + pc = ax.pcolormesh(np.random.randn(20, 20)) fig.colorbar(pc, ax=ax) plt.show() diff --git a/lib/matplotlib/_compress_layout.py b/lib/matplotlib/_compress_layout.py index 4d8d1137bf51..6ae345acb174 100644 --- a/lib/matplotlib/_compress_layout.py +++ b/lib/matplotlib/_compress_layout.py @@ -72,7 +72,7 @@ def compress_layout(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05, dxs[i, j] = dxs[i, j] + pad / di dys[i, j] = bboxes[ax].bounds[3] / dj pad = np.max([h_pad, hspace * bboxes[ax].bounds[3]]) - if ss.rowspan[0] > 0 : + if ss.rowspan[0] > 0: dys[i, j] = dys[i, j] + pad / dj margxs[i, j] = orpos.x0 - bboxes[ax].x0 margys[i, j] = orpos.y0 - bboxes[ax].y0 @@ -117,8 +117,8 @@ def compress_layout(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05, if cba._colorbar_info['location'] in ['bottom', 'top']: # shrink to make same size as active... posac = ax.get_position(original=False) - dx = (1 - cba._colorbar_info['shrink']) * (posac.x1 - - posac.x0) / 2 + dx = ((1 - cba._colorbar_info['shrink']) * + (posac.x1 posac.x0) / 2) pos.x0 = posac.x0 + dx pos.x1 = posac.x1 - dx else: @@ -143,7 +143,7 @@ def compress_layout(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05, pos.x1 = x - marg pos.x0 = x - marg - _dx else: - ddx = (x1 - x0) * (1 - cb._colorbar_info['shrink']) / 2 + ddx = (x1 - x0) * (1 - cb._colorbar_info['shrink']) / 2 marg = bbox.x0 - pos.x0 pos.x0 = x0 - marg + ddx marg = bbox.x1 - pos.x1 @@ -177,10 +177,10 @@ def compress_layout(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05, if cba._colorbar_info['location'] in ['right', 'left']: # shrink to make same size as active... posac = ax.get_position(original=False) - ddy_ = (1 - cba._colorbar_info['shrink']) * (posac.y1 - - posac.y0) / 2 - pos.y0 = posac.y0 + ddy_ - pos.y1 = posac.y1 - ddy_ + ddy = ((1 - cba._colorbar_info['shrink']) * + (posac.y1 - posac.y0) / 2) + pos.y0 = posac.y0 + ddy + pos.y1 = posac.y1 - ddy else: pos.y0 = pos.y0 + deltay pos.y1 = pos.y1 + deltay @@ -204,7 +204,7 @@ def compress_layout(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05, pos.y1 = y - marg pos.y0 = y - marg - _dy else: - ddy = (y1 - y0) * (1 - cb._colorbar_info['shrink']) / 2 + ddy = (y1 - y0) * (1 - cb._colorbar_info['shrink']) / 2 marg = bbox.y0 - pos.y0 pos.y0 = y0 - marg + ddy marg = bbox.y1 - pos.y1 diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index 8a3c57278d6c..cd22825c9207 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -264,7 +264,7 @@ def _make_layout_margins(ax, renderer, h_pad, w_pad): *axes* layoutbox be a minimum size that can accommodate the decorations on the axis. - Returns the bbox for some width/heigth calcs outside this loop. + Returns the bbox for some width/height calcs outside this loop. """ fig = ax.figure invTransFig = fig.transFigure.inverted().transform_bbox diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 90e26efa9a14..8235e960ff52 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -10,7 +10,6 @@ import logging from numbers import Integral -import warnings import numpy as np diff --git a/lib/matplotlib/tests/test_constrainedlayout.py b/lib/matplotlib/tests/test_constrainedlayout.py index d9de235f50d2..2f24f1c0b987 100644 --- a/lib/matplotlib/tests/test_constrainedlayout.py +++ b/lib/matplotlib/tests/test_constrainedlayout.py @@ -400,6 +400,7 @@ def test_hidden_axes(): np.testing.assert_allclose( extents1, [0.045552, 0.548288, 0.47319, 0.982638], rtol=1e-5) + def test_compressed(): # Test that the compressed layout option works: fig, axs = plt.subplots(2, 2, constrained_layout=True, @@ -431,6 +432,7 @@ def test_compressed_cbars(): np.testing.assert_allclose(extents, [0.119849, 0.571899, 0.355875, 0.965277], rtol=1e-5) + def test_compressed_onecbar(): # Test that the compressed layout option works: fig, axs = plt.subplots(2, 2, constrained_layout=True, From 523ffeed9481e508c6142929ff71be70be794fd5 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 5 May 2020 15:33:46 -0700 Subject: [PATCH 13/17] TST --- lib/matplotlib/_compress_layout.py | 2 +- lib/matplotlib/tests/test_constrainedlayout.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/_compress_layout.py b/lib/matplotlib/_compress_layout.py index 6ae345acb174..d1c5258cabbb 100644 --- a/lib/matplotlib/_compress_layout.py +++ b/lib/matplotlib/_compress_layout.py @@ -118,7 +118,7 @@ def compress_layout(fig, *, bboxes=None, w_pad=0.05, h_pad=0.05, # shrink to make same size as active... posac = ax.get_position(original=False) dx = ((1 - cba._colorbar_info['shrink']) * - (posac.x1 posac.x0) / 2) + (posac.x1 - posac.x0) / 2) pos.x0 = posac.x0 + dx pos.x1 = posac.x1 - dx else: diff --git a/lib/matplotlib/tests/test_constrainedlayout.py b/lib/matplotlib/tests/test_constrainedlayout.py index 2f24f1c0b987..98db9972b357 100644 --- a/lib/matplotlib/tests/test_constrainedlayout.py +++ b/lib/matplotlib/tests/test_constrainedlayout.py @@ -413,7 +413,7 @@ def test_compressed(): fig.canvas.draw() extents = np.copy(axs[0, 0].get_position().extents) np.testing.assert_allclose(extents, - [0.243692, 0.571899, 0.479719, 0.965277], rtol=1e-5) + [0.243692, 0.571899, 0.479719, 0.965277], rtol=1e-5) def test_compressed_cbars(): @@ -430,7 +430,7 @@ def test_compressed_cbars(): fig.canvas.draw() extents = np.copy(axs[0, 0].get_position().extents) np.testing.assert_allclose(extents, - [0.119849, 0.571899, 0.355875, 0.965277], rtol=1e-5) + [0.119849, 0.571899, 0.355875, 0.965277], rtol=1e-5) def test_compressed_onecbar(): @@ -447,4 +447,4 @@ def test_compressed_onecbar(): fig.canvas.draw() extents = np.copy(axs[0, 0].get_position().extents) np.testing.assert_allclose(extents, - [0.255883, 0.571899, 0.491909, 0.965277], rtol=1e-5) + [0.255883, 0.571899, 0.491909, 0.965277], rtol=1e-5) From 66e7c7ab148f24637a66e99d97c5f5c47fc42936 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 5 May 2020 19:51:51 -0700 Subject: [PATCH 14/17] TST --- lib/matplotlib/tests/test_constrainedlayout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_constrainedlayout.py b/lib/matplotlib/tests/test_constrainedlayout.py index 98db9972b357..a83d9a692888 100644 --- a/lib/matplotlib/tests/test_constrainedlayout.py +++ b/lib/matplotlib/tests/test_constrainedlayout.py @@ -447,4 +447,4 @@ def test_compressed_onecbar(): fig.canvas.draw() extents = np.copy(axs[0, 0].get_position().extents) np.testing.assert_allclose(extents, - [0.255883, 0.571899, 0.491909, 0.965277], rtol=1e-5) + [0.2558, 0.5719, 0.4920, 0.9653], rtol=1e-5, atol=1e-3) From a3690b3c9f778ce469ad7afff0da5a5472630103 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 5 May 2020 19:54:22 -0700 Subject: [PATCH 15/17] FOC --- examples/subplots_axes_and_figures/compress_axes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/subplots_axes_and_figures/compress_axes.py b/examples/subplots_axes_and_figures/compress_axes.py index ae93616d8687..f69f8aaad1e5 100644 --- a/examples/subplots_axes_and_figures/compress_axes.py +++ b/examples/subplots_axes_and_figures/compress_axes.py @@ -5,8 +5,8 @@ If a grid of subplot axes have fixed aspect ratios, the axes will usually be too far apart in one of the dimensions. For simple layouts -the ``compress_layout=True`` option to `.Figure.figure` or `.subplots` can -try to compress that dimension so the axes are a similar distance apart in +the ``compress_layout=True`` option to `.Figure` or `.Figure.subplots` +can try to compress that dimension so the axes are a similar distance apart in both dimensions. """ From 49fd211155b25285a6cd0161d2dd85559ac7d718 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 5 May 2020 20:01:43 -0700 Subject: [PATCH 16/17] FOC --- lib/matplotlib/tests/test_constrainedlayout.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_constrainedlayout.py b/lib/matplotlib/tests/test_constrainedlayout.py index a83d9a692888..cd1000e16196 100644 --- a/lib/matplotlib/tests/test_constrainedlayout.py +++ b/lib/matplotlib/tests/test_constrainedlayout.py @@ -413,7 +413,8 @@ def test_compressed(): fig.canvas.draw() extents = np.copy(axs[0, 0].get_position().extents) np.testing.assert_allclose(extents, - [0.243692, 0.571899, 0.479719, 0.965277], rtol=1e-5) + [0.243692, 0.571899, 0.479719, 0.965277], + rtol=1e-5) def test_compressed_cbars(): @@ -430,7 +431,8 @@ def test_compressed_cbars(): fig.canvas.draw() extents = np.copy(axs[0, 0].get_position().extents) np.testing.assert_allclose(extents, - [0.119849, 0.571899, 0.355875, 0.965277], rtol=1e-5) + [0.119849, 0.571899, 0.355875, 0.965277], + rtol=1e-5) def test_compressed_onecbar(): @@ -447,4 +449,5 @@ def test_compressed_onecbar(): fig.canvas.draw() extents = np.copy(axs[0, 0].get_position().extents) np.testing.assert_allclose(extents, - [0.2558, 0.5719, 0.4920, 0.9653], rtol=1e-5, atol=1e-3) + [0.2558, 0.5719, 0.4920, 0.9653], + rtol=1e-5, atol=1e-3) From c7200b97c8a2a6b7189e098cf9bb48bfa89ac62b Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 5 May 2020 22:09:36 -0700 Subject: [PATCH 17/17] TST --- lib/matplotlib/tests/test_constrainedlayout.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_constrainedlayout.py b/lib/matplotlib/tests/test_constrainedlayout.py index cd1000e16196..e5acd0c02b8c 100644 --- a/lib/matplotlib/tests/test_constrainedlayout.py +++ b/lib/matplotlib/tests/test_constrainedlayout.py @@ -432,7 +432,7 @@ def test_compressed_cbars(): extents = np.copy(axs[0, 0].get_position().extents) np.testing.assert_allclose(extents, [0.119849, 0.571899, 0.355875, 0.965277], - rtol=1e-5) + rtol=1e-5, atol=1e-3) def test_compressed_onecbar():