From 1e383499c7e43b7383316f84238cc4599db2bd78 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 20 Mar 2022 16:21:42 -0400 Subject: [PATCH 01/12] test, first commit --- README2 | 1 + 1 file changed, 1 insertion(+) create mode 100644 README2 diff --git a/README2 b/README2 new file mode 100644 index 000000000000..5f12aa4163dd --- /dev/null +++ b/README2 @@ -0,0 +1 @@ +ana and caroline From 3183036f5df2c7fbfa9dc57c764608b7f9a093d3 Mon Sep 17 00:00:00 2001 From: bromberc Date: Sun, 20 Mar 2022 16:27:12 -0400 Subject: [PATCH 02/12] first --- README2 | 1 + 1 file changed, 1 insertion(+) diff --git a/README2 b/README2 index 5f12aa4163dd..dd4136e69f94 100644 --- a/README2 +++ b/README2 @@ -1 +1,2 @@ ana and caroline +test git push From fff5a42d3ed4c9f52bf4f80e6c2a395a71eab615 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 27 Mar 2022 15:12:23 -0400 Subject: [PATCH 03/12] update test --- lib/matplotlib/figure.py | 55 ++++++++++++++++++++++++++++++++++++++++ test_align_titles.py | 18 +++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 test_align_titles.py diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 974e46d6e4b3..24824cc89749 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -174,6 +174,7 @@ def __init__(self, **kwargs): # see self.align_xlabels and self.align_ylabels and # axis._get_tick_boxes_siblings self._align_label_groups = {"x": cbook.Grouper(), "y": cbook.Grouper()} + self._align_title_groups = {"title": cbook.Grouper()} self.figure = self # list of child gridspecs for this figure @@ -1241,6 +1242,60 @@ def align_xlabels(self, axs=None): # grouper for groups of xlabels to align self._align_label_groups['x'].join(ax, axc) + def align_titles(self, axs=None): + """ + Align the titles of subplots in the same subplot column if title + alignment is being done automatically (i.e. the title position is + not manually set). + + Alignment persists for draw events after this is called. + + Parameters + ---------- + axs : list of `~matplotlib.axes.Axes` + Optional list of (or ndarray) `~matplotlib.axes.Axes` + to align the titles. + Default is to align all Axes on the figure. + + See Also + -------- + matplotlib.figure.Figure.align_xlabels + matplotlib.figure.Figure.align_ylabels + matplotlib.figure.Figure.align_labels + + Notes + ----- + This assumes that ``axs`` are from the same `.GridSpec`, so that + their `.SubplotSpec` positions correspond to figure positions. + + Examples + -------- + Example with titles:: + + fig, axs = subplots(1, 2, + subplot_kw={"xlabel": "x", "ylabel": "y", "title": "t"}) + axs[0].imshow(zeros((3, 5))) + axs[1].imshow(zeros((5, 3))) + fig.align_labels() + fig.align_titles() + """ + if axs is None: + axs = self.axes + axs = np.ravel(axs) + axs = [ax for ax in axs if hasattr(ax, 'get_subplotspec')] + locs = ['left', 'center', 'right'] + for ax in axs: + for loc in locs: + if ax.get_title(loc=loc): + _log.debug(' Working on: %s', ax.get_title(loc=loc)) + rowspan = ax.get_subplotspec().rowspan + for axc in axs: + for loc in locs: + rowspanc = axc.get_subplotspec().rowspan + if rowspan.start == rowspanc.start or \ + rowspan.stop == rowspanc.stop: + self._align_title_groups['title'].join(ax, axc) + def align_ylabels(self, axs=None): """ Align the ylabels of subplots in the same subplot column if label diff --git a/test_align_titles.py b/test_align_titles.py new file mode 100644 index 000000000000..ec2957af27ea --- /dev/null +++ b/test_align_titles.py @@ -0,0 +1,18 @@ +import matplotlib as plt +fig, axs = plt.subplots(2, 2, + subplot_kw={"xlabel": "x", "ylabel": "", "title": "t"}) +print(axs.shape) +axs[0][0].imshow(plt.zeros((3, 5))) +axs[0][1].imshow(plt.zeros((5, 3))) +axs[1][0].imshow(plt.zeros((1, 2))) +axs[1][1].imshow(plt.zeros((2, 1))) +axs[0][0].set_title('t2') +rowspan1 = axs[0][0].get_subplotspec().rowspan +print(rowspan1, rowspan1.start, rowspan1.stop) +rowspan2 = axs[1][1].get_subplotspec().rowspan +print(rowspan2, rowspan2.start, rowspan2.stop) + +fig.align_labels() +fig.align_titles() +plt.show() +print("DONE") From e0a20ccbf6207f6edfdb7688dae756af5bb4d9cc Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 29 Mar 2022 20:25:11 -0400 Subject: [PATCH 04/12] copy a bbox function to axesBase from figure --- lib/matplotlib/axes/_base.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 3be469189a3b..a7e382eaa27a 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2941,6 +2941,32 @@ def handle_single_axis( scaley, self._shared_axes["y"], 'y', self.yaxis, self._ymargin, y_stickies, self.set_ybound) + def _get_titles_siblings(self, renderer): + """ + Get the bounding boxes for this `.axis` and its siblings + as set by `.Figure.align_titles`. + + By default it just gets bboxes for self. + """ + # Get the Grouper keeping track of title groups for this figure. + axis_names = [ + name for name, axis in self.axes._axis_map.items() + if name in self.figure._align_label_groups and axis is self] + if len(axis_names) != 1: + return [], [] + axis_name, = axis_names + grouper = self.figure._align_label_groups[axis_name] + bboxes = [] + bboxes2 = [] + # If we want to align labels from other Axes: + for ax in grouper.get_siblings(self.axes): + axis = getattr(ax, f"{axis_name}axis") + ticks_to_draw = axis._update_ticks() + tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer) + bboxes.extend(tlb) + bboxes2.extend(tlb2) + return bboxes, bboxes2 + def _update_title_position(self, renderer): """ Update the title position based on the bounding box enclosing @@ -3003,6 +3029,7 @@ def _update_title_position(self, renderer): title.set_position((x, y)) ymax = max(title.get_position()[1] for title in titles) + # TODO: and for titles in sibling axes according to grouper for title in titles: # now line up all the titles at the highest baseline. x, _ = title.get_position() From 8f71fd1bcf2b4098b5942731502e06d717602844 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 3 Apr 2022 16:25:24 -0400 Subject: [PATCH 05/12] add broken code? commented out --- lib/matplotlib/axes/_base.py | 39 +++++++++++------------------------- 1 file changed, 12 insertions(+), 27 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index a7e382eaa27a..3e336fe5dfd0 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2941,32 +2941,6 @@ def handle_single_axis( scaley, self._shared_axes["y"], 'y', self.yaxis, self._ymargin, y_stickies, self.set_ybound) - def _get_titles_siblings(self, renderer): - """ - Get the bounding boxes for this `.axis` and its siblings - as set by `.Figure.align_titles`. - - By default it just gets bboxes for self. - """ - # Get the Grouper keeping track of title groups for this figure. - axis_names = [ - name for name, axis in self.axes._axis_map.items() - if name in self.figure._align_label_groups and axis is self] - if len(axis_names) != 1: - return [], [] - axis_name, = axis_names - grouper = self.figure._align_label_groups[axis_name] - bboxes = [] - bboxes2 = [] - # If we want to align labels from other Axes: - for ax in grouper.get_siblings(self.axes): - axis = getattr(ax, f"{axis_name}axis") - ticks_to_draw = axis._update_ticks() - tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer) - bboxes.extend(tlb) - bboxes2.extend(tlb2) - return bboxes, bboxes2 - def _update_title_position(self, renderer): """ Update the title position based on the bounding box enclosing @@ -2976,7 +2950,18 @@ def _update_title_position(self, renderer): _log.debug('title position was updated manually, not adjusting') return - titles = (self.title, self._left_title, self._right_title) + titles = [] + + # Get grouped axes for alignment if align_titles was previously called + # grouped_axs = self.figure._align_title_groups.get_siblings(self) + # if(grouped_axs is not None): + # titles = [] + # for ax in grouped_axs: + # titles.extend([ax.title, ax._left_title, ax._right_title]) + # else: + # titles = [self.title, self._left_title, self._right_title] + + titles = [self.title, self._left_title, self._right_title] for title in titles: x, _ = title.get_position() From f5de111d6bb22ad5c7b155f780bca498f6f939c2 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 3 Apr 2022 19:08:56 -0400 Subject: [PATCH 06/12] align titles works --- lib/matplotlib/axes/_base.py | 23 +++++++++++++---------- lib/matplotlib/figure.py | 4 ++-- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 3e336fe5dfd0..bb4478b412ea 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2952,15 +2952,6 @@ def _update_title_position(self, renderer): titles = [] - # Get grouped axes for alignment if align_titles was previously called - # grouped_axs = self.figure._align_title_groups.get_siblings(self) - # if(grouped_axs is not None): - # titles = [] - # for ax in grouped_axs: - # titles.extend([ax.title, ax._left_title, ax._right_title]) - # else: - # titles = [self.title, self._left_title, self._right_title] - titles = [self.title, self._left_title, self._right_title] for title in titles: @@ -3014,12 +3005,24 @@ def _update_title_position(self, renderer): title.set_position((x, y)) ymax = max(title.get_position()[1] for title in titles) - # TODO: and for titles in sibling axes according to grouper for title in titles: # now line up all the titles at the highest baseline. x, _ = title.get_position() title.set_position((x, ymax)) + # Assign + grouped_axs = self.figure._align_title_groups.get_siblings(self) + ymax = None + ax_max = None + for ax in grouped_axs: + if ymax is None or ax.bbox.ymax > ymax: + ymax = ax.bbox.ymax + ax_max = ax + # print("SELF FIRST: ", self.bbox.xmax) + # print("OTHER: ", ax_max.bbox.xmax) + self.bbox = ax_max.bbox + # print("SELF AFTER: ", self.bbox.xmax) + # Drawing @martist.allow_rasterization def draw(self, renderer): diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 24824cc89749..1dce61634957 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -174,7 +174,7 @@ def __init__(self, **kwargs): # see self.align_xlabels and self.align_ylabels and # axis._get_tick_boxes_siblings self._align_label_groups = {"x": cbook.Grouper(), "y": cbook.Grouper()} - self._align_title_groups = {"title": cbook.Grouper()} + self._align_title_groups = cbook.Grouper() self.figure = self # list of child gridspecs for this figure @@ -1294,7 +1294,7 @@ def align_titles(self, axs=None): rowspanc = axc.get_subplotspec().rowspan if rowspan.start == rowspanc.start or \ rowspan.stop == rowspanc.stop: - self._align_title_groups['title'].join(ax, axc) + self._align_title_groups.join(ax, axc) def align_ylabels(self, axs=None): """ From 8b27ae368985bf0773c17492148c85da04b85c59 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 4 Apr 2022 15:04:24 -0400 Subject: [PATCH 07/12] finish code and add tests --- lib/matplotlib/axes/_base.py | 13 +++++------- lib/matplotlib/tests/test_figure.py | 33 +++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index bb4478b412ea..b484f990b82c 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -3004,24 +3004,21 @@ def _update_title_position(self, renderer): (0., 2 * top - title.get_window_extent(renderer).ymin)) title.set_position((x, y)) + grouped_axs = self.figure._align_title_groups.get_siblings(self) ymax = max(title.get_position()[1] for title in titles) for title in titles: # now line up all the titles at the highest baseline. x, _ = title.get_position() title.set_position((x, ymax)) - # Assign - grouped_axs = self.figure._align_title_groups.get_siblings(self) - ymax = None + # Align bboxes of grouped axes to highest in group + bb_ymax = None ax_max = None for ax in grouped_axs: - if ymax is None or ax.bbox.ymax > ymax: - ymax = ax.bbox.ymax + if bb_ymax is None or ax.bbox.ymax > bb_ymax: + bb_ymax = ax.bbox.ymax ax_max = ax - # print("SELF FIRST: ", self.bbox.xmax) - # print("OTHER: ", ax_max.bbox.xmax) self.bbox = ax_max.bbox - # print("SELF AFTER: ", self.bbox.xmax) # Drawing @martist.allow_rasterization diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 9f68f63e5236..8cd7d7b7d5b6 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -100,6 +100,39 @@ def test_align_labels_stray_axes(): np.testing.assert_allclose(yn[::2], yn[1::2]) +# TODO add image comparison +@image_comparison(['figure_align_titles'], extensions=['png', 'svg'], + tol=0 if platform.machine() == 'x86_64' else 0.01) +def test_align_titles(): + fig, axs = plt.subplots(2, 2, + subplot_kw={"xlabel": "x", "ylabel": "", + "title": "Title"}) + axs[0][0].imshow(plt.np.zeros((5, 3))) + axs[0][1].imshow(plt.np.zeros((3, 5))) + axs[1][0].imshow(plt.np.zeros((2, 1))) + axs[1][1].imshow(plt.np.zeros((1, 2))) + + axs[0][1].set_title('Title2', loc="left") + axs[0][1].set_title() + + fig.align_titles() + + +## TODO add image comparison +@image_comparison(['figure_align_titles_some'], extensions=['png', 'svg'], + tol=0 if platform.machine() == 'x86_64' else 0.01) +def test_align_titles_param(): + fig, axs = plt.subplots(2, 2, + subplot_kw={"xlabel": "x", "ylabel": "", + "title": "t"}) + axs[0][0].imshow(plt.np.zeros((3, 5))) + axs[0][1].imshow(plt.np.zeros((5, 3))) + axs[1][0].imshow(plt.np.zeros((2, 1))) + axs[1][1].imshow(plt.np.zeros((1, 2))) + + fig.align_titles([axs[0][0], axs[0][1]]) + + def test_figure_label(): # pyplot figure creation, selection, and closing with label/number/instance plt.close('all') From a3d8ccd29523251ec0226865f7406393cb20ec0d Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 6 Apr 2022 15:33:00 -0400 Subject: [PATCH 08/12] add demo --- .../align_titles_demo.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 examples/subplots_axes_and_figures/align_titles_demo.py diff --git a/examples/subplots_axes_and_figures/align_titles_demo.py b/examples/subplots_axes_and_figures/align_titles_demo.py new file mode 100644 index 000000000000..42c8fe819fbe --- /dev/null +++ b/examples/subplots_axes_and_figures/align_titles_demo.py @@ -0,0 +1,26 @@ +""" +=============== +Aligning Labels +=============== + +Aligning titles using `.Figure.align_titles`. + +Note that the title "Title 1" would normally be much closer to the +figure's axis. +""" +import matplotlib.pyplot as plt +fig, axs = plt.subplots(2, 2, + subplot_kw={"xlabel": "x", "ylabel": "", "title": "t"}) +print(axs.shape) +axs[0][0].imshow(plt.np.zeros((3, 5))) +axs[0][1].imshow(plt.np.zeros((5, 3))) +axs[1][0].imshow(plt.np.zeros((1, 2))) +axs[1][1].imshow(plt.np.zeros((2, 1))) +axs[0][0].set_title('t2') +rowspan1 = axs[0][0].get_subplotspec().rowspan +print(rowspan1, rowspan1.start, rowspan1.stop) +rowspan2 = axs[1][1].get_subplotspec().rowspan +print(rowspan2, rowspan2.start, rowspan2.stop) + +fig.align_titles() +plt.show() From 681abfe7322139756a10d00c549e7b9cfe1cb078 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 11 Apr 2022 15:30:38 -0400 Subject: [PATCH 09/12] remove readme2 --- README2 | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 README2 diff --git a/README2 b/README2 deleted file mode 100644 index dd4136e69f94..000000000000 --- a/README2 +++ /dev/null @@ -1,2 +0,0 @@ -ana and caroline -test git push From f23d9e01bdc091b4270ff9fcb79e977f54a470c2 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 11 Apr 2022 15:45:49 -0400 Subject: [PATCH 10/12] pr changes addressed --- lib/matplotlib/axes/_base.py | 5 ++--- lib/matplotlib/figure.py | 7 ++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index b484f990b82c..dddd0cf1930d 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2950,8 +2950,6 @@ def _update_title_position(self, renderer): _log.debug('title position was updated manually, not adjusting') return - titles = [] - titles = [self.title, self._left_title, self._right_title] for title in titles: @@ -3004,7 +3002,6 @@ def _update_title_position(self, renderer): (0., 2 * top - title.get_window_extent(renderer).ymin)) title.set_position((x, y)) - grouped_axs = self.figure._align_title_groups.get_siblings(self) ymax = max(title.get_position()[1] for title in titles) for title in titles: # now line up all the titles at the highest baseline. @@ -3012,6 +3009,8 @@ def _update_title_position(self, renderer): title.set_position((x, ymax)) # Align bboxes of grouped axes to highest in group + grouped_axs = self.figure._align_label_groups['title'] \ + .get_siblings(self) bb_ymax = None ax_max = None for ax in grouped_axs: diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 1dce61634957..025c651edd22 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -173,8 +173,8 @@ def __init__(self, **kwargs): # groupers to keep track of x and y labels we want to align. # see self.align_xlabels and self.align_ylabels and # axis._get_tick_boxes_siblings - self._align_label_groups = {"x": cbook.Grouper(), "y": cbook.Grouper()} - self._align_title_groups = cbook.Grouper() + self._align_label_groups = {"x": cbook.Grouper(), "y": cbook.Grouper(), + "title": cbook.Grouper()} self.figure = self # list of child gridspecs for this figure @@ -1294,7 +1294,7 @@ def align_titles(self, axs=None): rowspanc = axc.get_subplotspec().rowspan if rowspan.start == rowspanc.start or \ rowspan.stop == rowspanc.stop: - self._align_title_groups.join(ax, axc) + self._align_label_groups['title'].join(ax, axc) def align_ylabels(self, axs=None): """ @@ -2903,6 +2903,7 @@ def draw(self, renderer): artists = self._get_draw_artists(renderer) try: + renderer.open_group('figure', gid=self.get_gid()) if self.axes and self.get_layout_engine() is not None: try: From 83e89a16dc02be93f441e8ccfc36dca1a51c0331 Mon Sep 17 00:00:00 2001 From: unknown Date: Sun, 20 Mar 2022 16:21:42 -0400 Subject: [PATCH 11/12] add code and tests update test first copy a bbox function to axesBase from figure add broken code? commented out align titles works finish code and add tests add demo remove readme2 --- .../align_titles_demo.py | 26 +++++++++ lib/matplotlib/axes/_base.py | 14 ++++- lib/matplotlib/figure.py | 55 +++++++++++++++++++ lib/matplotlib/tests/test_figure.py | 33 +++++++++++ test_align_titles.py | 18 ++++++ 5 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 examples/subplots_axes_and_figures/align_titles_demo.py create mode 100644 test_align_titles.py diff --git a/examples/subplots_axes_and_figures/align_titles_demo.py b/examples/subplots_axes_and_figures/align_titles_demo.py new file mode 100644 index 000000000000..42c8fe819fbe --- /dev/null +++ b/examples/subplots_axes_and_figures/align_titles_demo.py @@ -0,0 +1,26 @@ +""" +=============== +Aligning Labels +=============== + +Aligning titles using `.Figure.align_titles`. + +Note that the title "Title 1" would normally be much closer to the +figure's axis. +""" +import matplotlib.pyplot as plt +fig, axs = plt.subplots(2, 2, + subplot_kw={"xlabel": "x", "ylabel": "", "title": "t"}) +print(axs.shape) +axs[0][0].imshow(plt.np.zeros((3, 5))) +axs[0][1].imshow(plt.np.zeros((5, 3))) +axs[1][0].imshow(plt.np.zeros((1, 2))) +axs[1][1].imshow(plt.np.zeros((2, 1))) +axs[0][0].set_title('t2') +rowspan1 = axs[0][0].get_subplotspec().rowspan +print(rowspan1, rowspan1.start, rowspan1.stop) +rowspan2 = axs[1][1].get_subplotspec().rowspan +print(rowspan2, rowspan2.start, rowspan2.stop) + +fig.align_titles() +plt.show() diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 3be469189a3b..b484f990b82c 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2950,7 +2950,9 @@ def _update_title_position(self, renderer): _log.debug('title position was updated manually, not adjusting') return - titles = (self.title, self._left_title, self._right_title) + titles = [] + + titles = [self.title, self._left_title, self._right_title] for title in titles: x, _ = title.get_position() @@ -3002,12 +3004,22 @@ def _update_title_position(self, renderer): (0., 2 * top - title.get_window_extent(renderer).ymin)) title.set_position((x, y)) + grouped_axs = self.figure._align_title_groups.get_siblings(self) ymax = max(title.get_position()[1] for title in titles) for title in titles: # now line up all the titles at the highest baseline. x, _ = title.get_position() title.set_position((x, ymax)) + # Align bboxes of grouped axes to highest in group + bb_ymax = None + ax_max = None + for ax in grouped_axs: + if bb_ymax is None or ax.bbox.ymax > bb_ymax: + bb_ymax = ax.bbox.ymax + ax_max = ax + self.bbox = ax_max.bbox + # Drawing @martist.allow_rasterization def draw(self, renderer): diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 974e46d6e4b3..1dce61634957 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -174,6 +174,7 @@ def __init__(self, **kwargs): # see self.align_xlabels and self.align_ylabels and # axis._get_tick_boxes_siblings self._align_label_groups = {"x": cbook.Grouper(), "y": cbook.Grouper()} + self._align_title_groups = cbook.Grouper() self.figure = self # list of child gridspecs for this figure @@ -1241,6 +1242,60 @@ def align_xlabels(self, axs=None): # grouper for groups of xlabels to align self._align_label_groups['x'].join(ax, axc) + def align_titles(self, axs=None): + """ + Align the titles of subplots in the same subplot column if title + alignment is being done automatically (i.e. the title position is + not manually set). + + Alignment persists for draw events after this is called. + + Parameters + ---------- + axs : list of `~matplotlib.axes.Axes` + Optional list of (or ndarray) `~matplotlib.axes.Axes` + to align the titles. + Default is to align all Axes on the figure. + + See Also + -------- + matplotlib.figure.Figure.align_xlabels + matplotlib.figure.Figure.align_ylabels + matplotlib.figure.Figure.align_labels + + Notes + ----- + This assumes that ``axs`` are from the same `.GridSpec`, so that + their `.SubplotSpec` positions correspond to figure positions. + + Examples + -------- + Example with titles:: + + fig, axs = subplots(1, 2, + subplot_kw={"xlabel": "x", "ylabel": "y", "title": "t"}) + axs[0].imshow(zeros((3, 5))) + axs[1].imshow(zeros((5, 3))) + fig.align_labels() + fig.align_titles() + """ + if axs is None: + axs = self.axes + axs = np.ravel(axs) + axs = [ax for ax in axs if hasattr(ax, 'get_subplotspec')] + locs = ['left', 'center', 'right'] + for ax in axs: + for loc in locs: + if ax.get_title(loc=loc): + _log.debug(' Working on: %s', ax.get_title(loc=loc)) + rowspan = ax.get_subplotspec().rowspan + for axc in axs: + for loc in locs: + rowspanc = axc.get_subplotspec().rowspan + if rowspan.start == rowspanc.start or \ + rowspan.stop == rowspanc.stop: + self._align_title_groups.join(ax, axc) + def align_ylabels(self, axs=None): """ Align the ylabels of subplots in the same subplot column if label diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 9f68f63e5236..8cd7d7b7d5b6 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -100,6 +100,39 @@ def test_align_labels_stray_axes(): np.testing.assert_allclose(yn[::2], yn[1::2]) +# TODO add image comparison +@image_comparison(['figure_align_titles'], extensions=['png', 'svg'], + tol=0 if platform.machine() == 'x86_64' else 0.01) +def test_align_titles(): + fig, axs = plt.subplots(2, 2, + subplot_kw={"xlabel": "x", "ylabel": "", + "title": "Title"}) + axs[0][0].imshow(plt.np.zeros((5, 3))) + axs[0][1].imshow(plt.np.zeros((3, 5))) + axs[1][0].imshow(plt.np.zeros((2, 1))) + axs[1][1].imshow(plt.np.zeros((1, 2))) + + axs[0][1].set_title('Title2', loc="left") + axs[0][1].set_title() + + fig.align_titles() + + +## TODO add image comparison +@image_comparison(['figure_align_titles_some'], extensions=['png', 'svg'], + tol=0 if platform.machine() == 'x86_64' else 0.01) +def test_align_titles_param(): + fig, axs = plt.subplots(2, 2, + subplot_kw={"xlabel": "x", "ylabel": "", + "title": "t"}) + axs[0][0].imshow(plt.np.zeros((3, 5))) + axs[0][1].imshow(plt.np.zeros((5, 3))) + axs[1][0].imshow(plt.np.zeros((2, 1))) + axs[1][1].imshow(plt.np.zeros((1, 2))) + + fig.align_titles([axs[0][0], axs[0][1]]) + + def test_figure_label(): # pyplot figure creation, selection, and closing with label/number/instance plt.close('all') diff --git a/test_align_titles.py b/test_align_titles.py new file mode 100644 index 000000000000..ec2957af27ea --- /dev/null +++ b/test_align_titles.py @@ -0,0 +1,18 @@ +import matplotlib as plt +fig, axs = plt.subplots(2, 2, + subplot_kw={"xlabel": "x", "ylabel": "", "title": "t"}) +print(axs.shape) +axs[0][0].imshow(plt.zeros((3, 5))) +axs[0][1].imshow(plt.zeros((5, 3))) +axs[1][0].imshow(plt.zeros((1, 2))) +axs[1][1].imshow(plt.zeros((2, 1))) +axs[0][0].set_title('t2') +rowspan1 = axs[0][0].get_subplotspec().rowspan +print(rowspan1, rowspan1.start, rowspan1.stop) +rowspan2 = axs[1][1].get_subplotspec().rowspan +print(rowspan2, rowspan2.start, rowspan2.stop) + +fig.align_labels() +fig.align_titles() +plt.show() +print("DONE") From e3c50f895a4e6ea74072b5cbe6691527c7ffe617 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 11 Apr 2022 15:45:49 -0400 Subject: [PATCH 12/12] pr changes addressed test cases --- lib/matplotlib/axes/_base.py | 5 ++--- lib/matplotlib/figure.py | 7 ++++--- lib/matplotlib/tests/test_figure.py | 3 +-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index b484f990b82c..dddd0cf1930d 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -2950,8 +2950,6 @@ def _update_title_position(self, renderer): _log.debug('title position was updated manually, not adjusting') return - titles = [] - titles = [self.title, self._left_title, self._right_title] for title in titles: @@ -3004,7 +3002,6 @@ def _update_title_position(self, renderer): (0., 2 * top - title.get_window_extent(renderer).ymin)) title.set_position((x, y)) - grouped_axs = self.figure._align_title_groups.get_siblings(self) ymax = max(title.get_position()[1] for title in titles) for title in titles: # now line up all the titles at the highest baseline. @@ -3012,6 +3009,8 @@ def _update_title_position(self, renderer): title.set_position((x, ymax)) # Align bboxes of grouped axes to highest in group + grouped_axs = self.figure._align_label_groups['title'] \ + .get_siblings(self) bb_ymax = None ax_max = None for ax in grouped_axs: diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 1dce61634957..025c651edd22 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -173,8 +173,8 @@ def __init__(self, **kwargs): # groupers to keep track of x and y labels we want to align. # see self.align_xlabels and self.align_ylabels and # axis._get_tick_boxes_siblings - self._align_label_groups = {"x": cbook.Grouper(), "y": cbook.Grouper()} - self._align_title_groups = cbook.Grouper() + self._align_label_groups = {"x": cbook.Grouper(), "y": cbook.Grouper(), + "title": cbook.Grouper()} self.figure = self # list of child gridspecs for this figure @@ -1294,7 +1294,7 @@ def align_titles(self, axs=None): rowspanc = axc.get_subplotspec().rowspan if rowspan.start == rowspanc.start or \ rowspan.stop == rowspanc.stop: - self._align_title_groups.join(ax, axc) + self._align_label_groups['title'].join(ax, axc) def align_ylabels(self, axs=None): """ @@ -2903,6 +2903,7 @@ def draw(self, renderer): artists = self._get_draw_artists(renderer) try: + renderer.open_group('figure', gid=self.get_gid()) if self.axes and self.get_layout_engine() is not None: try: diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 8cd7d7b7d5b6..fb0fed6b311f 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -113,13 +113,12 @@ def test_align_titles(): axs[1][1].imshow(plt.np.zeros((1, 2))) axs[0][1].set_title('Title2', loc="left") - axs[0][1].set_title() fig.align_titles() ## TODO add image comparison -@image_comparison(['figure_align_titles_some'], extensions=['png', 'svg'], +@image_comparison(['figure_align_titles_param'], extensions=['png', 'svg'], tol=0 if platform.machine() == 'x86_64' else 0.01) def test_align_titles_param(): fig, axs = plt.subplots(2, 2,