diff --git a/lib/matplotlib/_constrained_layout.py b/lib/matplotlib/_constrained_layout.py index 5623e12a3c41..8a3bddc1d5af 100644 --- a/lib/matplotlib/_constrained_layout.py +++ b/lib/matplotlib/_constrained_layout.py @@ -226,19 +226,12 @@ def make_layoutgrids_gs(layoutgrids, gs): 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], + parent=subspeclb, + parent_flush=True, name='gridspec', nrows=gs._nrows, ncols=gs._ncols, + parent_pos=(subplot_spec.rowspan, subplot_spec.colspan), width_ratios=gs.get_width_ratios(), height_ratios=gs.get_height_ratios()) return layoutgrids @@ -434,6 +427,14 @@ def make_layout_margins(layoutgrids, fig, renderer, *, w_pad=0, h_pad=0, if (cbp_rspan.start == ss.rowspan.start and cbbbox.y1 > bbox.y1): margin['top'] += cbbbox.y1 - bbox.y1 + + if layoutgrids[gs].parent_flush: + inner_lb = layoutgrids[gs] + parent_ss = gs._subplot_spec + parent_gs = parent_ss.get_gridspec() + parent_lb = layoutgrids[parent_gs] + match_nested_margins(inner_lb, parent_lb, parent_ss) + # pass the new margins down to the layout grid for the solution... layoutgrids[gs].edit_outer_margin_mins(margin, ss) @@ -457,6 +458,24 @@ def make_layout_margins(layoutgrids, fig, renderer, *, w_pad=0, h_pad=0, layoutgrids[fig].edit_margin_min('left', w) +def match_nested_margins(inner_lb, parent_lb, parent_ss): + def _match(loc, inner_cell, parent_cell): + inner_size = inner_lb.get_margins(loc, inner_cell) + parent_size = parent_lb.get_margins(loc, parent_cell) + size = max(parent_size, inner_size) + inner_lb.edit_margin_min(loc, size, inner_cell) + parent_lb.edit_margin_min(loc, size, parent_cell) + + _match('left', 0, parent_ss.colspan.start) + _match('leftcb', 0, parent_ss.colspan.start) + _match('right', -1, parent_ss.colspan.stop - 1) + _match('rightcb', -1, parent_ss.colspan.stop - 1) + _match('top', 0, parent_ss.rowspan.start) + _match('topcb', 0, parent_ss.rowspan.start) + _match('bottom', -1, parent_ss.rowspan.stop - 1) + _match('bottomcb', -1, parent_ss.rowspan.stop - 1) + + 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. diff --git a/lib/matplotlib/_layoutgrid.py b/lib/matplotlib/_layoutgrid.py index 8f81b14765b6..314bd551003a 100644 --- a/lib/matplotlib/_layoutgrid.py +++ b/lib/matplotlib/_layoutgrid.py @@ -34,12 +34,13 @@ class LayoutGrid: """ def __init__(self, parent=None, parent_pos=(0, 0), - parent_inner=False, name='', ncols=1, nrows=1, + parent_inner=False, parent_flush=False, name='', ncols=1, nrows=1, h_pad=None, w_pad=None, width_ratios=None, height_ratios=None): Variable = kiwi.Variable self.parent_pos = parent_pos self.parent_inner = parent_inner + self.parent_flush = parent_flush self.name = name + seq_id() if isinstance(parent, LayoutGrid): self.name = f'{parent.name}.{self.name}' @@ -202,6 +203,35 @@ def parent_constraints(self, parent): # from top to bottom self.tops[0] == top, self.bottoms[-1] == bottom] + if self.parent_flush: + left_outer = (parent.lefts[cols[0]] + + parent.margins['left'][cols[0]] + + parent.margins['leftcb'][cols[0]]) + right_outer = (parent.rights[cols[-1]] - + parent.margins['right'][cols[-1]] - + parent.margins['rightcb'][cols[-1]]) + top_outer = (parent.tops[rows[0]] - + parent.margins['top'][rows[0]] - + parent.margins['topcb'][rows[0]]) + bottom_outer = (parent.bottoms[rows[-1]] + + parent.margins['bottom'][rows[-1]] + + parent.margins['bottomcb'][rows[-1]]) + left_inner = (self.lefts[0] + + self.margins['left'][0] + + self.margins['leftcb'][0]) + right_inner = (self.rights[-1] - + self.margins['right'][-1] - + self.margins['rightcb'][-1]) + top_inner = (self.tops[0] - + self.margins['top'][0] - + self.margins['topcb'][0]) + bottom_inner = (self.bottoms[-1] + + self.margins['bottom'][-1] + + self.margins['bottomcb'][-1]) + hc += [left_outer == left_inner, + right_outer == right_inner, + top_outer == top_inner, + bottom_outer == bottom_inner] for c in hc: self.solver.addConstraint(c | 'required') diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11.png index f337d370dc33..e6e0c5968b7a 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11rat.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11rat.png index 534903300f7a..781ae251ccd6 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11rat.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout11rat.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout6.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout6.png index 6ba96e41a34d..45e9e9249c5c 100644 Binary files a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout6.png and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/constrained_layout6.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment1.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment1.png new file mode 100644 index 000000000000..40fcc8ed3309 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment1.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment2.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment2.png new file mode 100644 index 000000000000..db850015c47d Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment2.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment3.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment3.png new file mode 100644 index 000000000000..ad71b51906cf Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment3.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment4.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment4.png new file mode 100644 index 000000000000..def6cd239d47 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment4.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment5.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment5.png new file mode 100644 index 000000000000..e6604c77ce86 Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment5.png differ diff --git a/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment6.png b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment6.png new file mode 100644 index 000000000000..c492c7e9081a Binary files /dev/null and b/lib/matplotlib/tests/baseline_images/test_constrainedlayout/plot_nested_grid_alignment6.png differ diff --git a/lib/matplotlib/tests/test_constrainedlayout.py b/lib/matplotlib/tests/test_constrainedlayout.py index e42e2ee9bfd8..cb9010aa7977 100644 --- a/lib/matplotlib/tests/test_constrainedlayout.py +++ b/lib/matplotlib/tests/test_constrainedlayout.py @@ -719,3 +719,81 @@ def test_layout_leak(): gc.collect() assert not any(isinstance(obj, mpl._layoutgrid.LayoutGrid) for obj in gc.get_objects()) + + +def plot_nested_grid_alignment(top, middle, bottom_axes, bottom): + def add_subplots(axis, nx, ny): + axis.clear() + axis.get_xaxis().set_visible(False) + axis.get_yaxis().set_visible(False) + subplot_spec = axis.get_subplotspec() + subgrid_spec = subplot_spec.subgridspec(nx, ny) + axis_list = subgrid_spec.subplots().flatten().tolist() + for a in axis_list: + show_random_image(a) + + return axis_list + + def show_random_image(axis): + z = np.linspace(0, 1, 30).reshape(10, 3) + axis.pcolormesh(z, cmap="plasma") + + fig = plt.figure(figsize=[20, 12], layout="constrained") + fig.get_layout_engine().set(w_pad=0.2, h_pad=0.2) + + axes = fig.subplots(2, 2, width_ratios=[1, 2]).flatten().tolist() + for a in axes: + show_random_image(a) + show_random_image(axes[0]) + if top: + axes[0].set_title("title", fontsize=50) + axes[0].set_xlabel("xlabel", fontsize=50) + axes[0].get_xaxis().set_tick_params(labelsize=50) + axes[-1].set_ylabel("ylabel", fontsize=50) + axes[-1].get_yaxis().set_tick_params(labelsize=50) + axes[0].get_xaxis().set_visible(False) + axes = add_subplots(axes[1], 2, 2) + if middle: + axes[-1].set_title("title", fontsize=50) + axes[0].set_title("title", fontsize=50) + axes[0].set_ylabel("ylabel", fontsize=50) + axes[0].get_yaxis().set_tick_params(labelsize=30) + axes[1].set_ylabel("ylabel", fontsize=50) + axes[1].get_yaxis().set_tick_params(labelsize=30) + axes[-1].get_xaxis().set_tick_params(labelsize=50) + axes[-1].set_xlabel("xlabel", fontsize=50) + if bottom_axes: + axes = add_subplots(axes[2], 2, 3) + if bottom: + axes[3].set_ylabel("ylabel", fontsize=50) + axes[3].get_yaxis().set_tick_params(labelsize=50) + + +@image_comparison(['plot_nested_grid_alignment1.png']) +def test_nested_grid_alignment1(): + plot_nested_grid_alignment(False, False, False, False) + + +@image_comparison(['plot_nested_grid_alignment2.png']) +def test_nested_grid_alignment2(): + plot_nested_grid_alignment(True, False, False, False) + + +@image_comparison(['plot_nested_grid_alignment3.png']) +def test_nested_grid_alignment3(): + plot_nested_grid_alignment(False, True, False, False) + + +@image_comparison(['plot_nested_grid_alignment4.png']) +def test_nested_grid_alignment4(): + plot_nested_grid_alignment(True, False, True, False) + + +@image_comparison(['plot_nested_grid_alignment5.png']) +def test_nested_grid_alignment5(): + plot_nested_grid_alignment(False, True, True, False) + + +@image_comparison(['plot_nested_grid_alignment6.png']) +def test_nested_grid_alignment6(): + plot_nested_grid_alignment(False, False, True, True)