From ce91095fb477d0e87145f9353c31e1cb673abb6a Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Tue, 31 Oct 2017 14:17:54 -0700 Subject: [PATCH 1/4] ENH: aligning x and y labels across axes --- .../2017-11-1_figure_align_labels.rst | 35 + .../align_labels_demo.py | 38 + lib/matplotlib/axis.py | 45 +- lib/matplotlib/figure.py | 210 ++ lib/matplotlib/gridspec.py | 21 +- .../test_figure/figure_align_labels.pdf | Bin 0 -> 11021 bytes .../test_figure/figure_align_labels.png | Bin 0 -> 65569 bytes .../test_figure/figure_align_labels.svg | 2724 +++++++++++++++++ lib/matplotlib/tests/test_figure.py | 42 + 9 files changed, 3105 insertions(+), 10 deletions(-) create mode 100644 doc/users/next_whats_new/2017-11-1_figure_align_labels.rst create mode 100644 examples/subplots_axes_and_figures/align_labels_demo.py create mode 100644 lib/matplotlib/tests/baseline_images/test_figure/figure_align_labels.pdf create mode 100644 lib/matplotlib/tests/baseline_images/test_figure/figure_align_labels.png create mode 100644 lib/matplotlib/tests/baseline_images/test_figure/figure_align_labels.svg diff --git a/doc/users/next_whats_new/2017-11-1_figure_align_labels.rst b/doc/users/next_whats_new/2017-11-1_figure_align_labels.rst new file mode 100644 index 000000000000..946cf11067b6 --- /dev/null +++ b/doc/users/next_whats_new/2017-11-1_figure_align_labels.rst @@ -0,0 +1,35 @@ +xlabels and ylabels can now be automatically aligned +---------------------------------------------------- + +Subplot axes ``ylabels`` can be misaligned horizontally if the tick labels +are very different widths. The same can happen to ``xlabels`` if the +ticklabels are rotated on one subplot (for instance). The new methods +on the `Figure` class: `Figure.align_xlabels` and `Figure.align_ylabels` +will now align these labels horizontally or vertically. If the user only +wants to align some axes, a list of axes can be passed. If no list is +passed, the algorithm looks at all the labels on the figure. + +Only labels that have the same subplot locations are aligned. i.e. the +ylabels are aligned only if the subplots are in the same column of the +subplot layout. + +A convenience wrapper `Figure.align_labels` calls both functions at once. + +.. plot:: + + import matplotlib.gridspec as gridspec + + fig = plt.figure(figsize=(5, 3), tight_layout=True) + gs = gridspec.GridSpec(2, 2) + + ax = fig.add_subplot(gs[0,:]) + ax.plot(np.arange(0, 1e6, 1000)) + ax.set_ylabel('Test') + for i in range(2): + ax = fig.add_subplot(gs[1, i]) + ax.set_ylabel('Booooo') + ax.set_xlabel('Hello') + if i == 0: + for tick in ax.get_xticklabels(): + tick.set_rotation(45) + fig.align_labels() diff --git a/examples/subplots_axes_and_figures/align_labels_demo.py b/examples/subplots_axes_and_figures/align_labels_demo.py new file mode 100644 index 000000000000..b08bd48cc98e --- /dev/null +++ b/examples/subplots_axes_and_figures/align_labels_demo.py @@ -0,0 +1,38 @@ +""" +=============== +Aligning Labels +=============== + +Aligning xlabel and ylabel using +`Figure.align_xlabels` and +`Figure.align_ylabels` + +`Figure.align_labels` wraps these two functions. + +Note that +the xlabel "XLabel1 1" would normally be much closer to the x-axis, and +"YLabel1 0" would be much closer to the y-axis of their respective axes. +""" +import matplotlib.pyplot as plt +import numpy as np +import matplotlib.gridspec as gridspec + +fig = plt.figure(tight_layout=True) +gs = gridspec.GridSpec(2, 2) + +ax = fig.add_subplot(gs[0, :]) +ax.plot(np.arange(0, 1e6, 1000)) +ax.set_ylabel('YLabel0') +ax.set_xlabel('XLabel0') + +for i in range(2): + ax = fig.add_subplot(gs[1, i]) + ax.plot(np.arange(1., 0., -0.1) * 2000., np.arange(1., 0., -0.1)) + ax.set_ylabel('YLabel1 %d' % i) + ax.set_xlabel('XLabel1 %d' % i) + if i == 0: + for tick in ax.get_xticklabels(): + tick.set_rotation(55) +fig.align_labels() # same as fig.align_xlabels() and fig.align_ylabels() + +plt.show() diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index ac6bf3425c41..2424e9630bb1 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -692,6 +692,7 @@ def __init__(self, axes, pickradius=15): self._autolabelpos = True self._smart_bounds = False + self._align_label_siblings = [self] self.label = self._get_label() self.labelpad = rcParams['axes.labelpad'] @@ -1113,10 +1114,12 @@ def get_tightbbox(self, renderer): return ticks_to_draw = self._update_ticks(renderer) - ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(ticks_to_draw, - renderer) - self._update_label_position(ticklabelBoxes, ticklabelBoxes2) + self._update_label_position(renderer) + + # go back to just this axis's tick labels + ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes( + ticks_to_draw, renderer) self._update_offset_text_position(ticklabelBoxes, ticklabelBoxes2) self.offsetText.set_text(self.major.formatter.get_offset()) @@ -1167,7 +1170,7 @@ def draw(self, renderer, *args, **kwargs): # *copy* of the axis label box because we don't wan't to scale # the actual bbox - self._update_label_position(ticklabelBoxes, ticklabelBoxes2) + self._update_label_position(renderer) self.label.draw(renderer) @@ -1670,7 +1673,24 @@ def set_ticks(self, ticks, minor=False): self.set_major_locator(mticker.FixedLocator(ticks)) return self.get_major_ticks(len(ticks)) - def _update_label_position(self, bboxes, bboxes2): + def _get_tick_boxes_siblings(self, renderer): + """ + Get the bounding boxes for this axis and its sibblings + as set by `Figure.align_xlabels` or ``Figure.align_ylables`. + + By default it just gets bboxes for self. + """ + bboxes = [] + bboxes2 = [] + # if we want to align labels from other axes: + for axx in self._align_label_siblings: + ticks_to_draw = axx._update_ticks(renderer) + tlb, tlb2 = axx._get_tick_bboxes(ticks_to_draw, renderer) + bboxes.extend(tlb) + bboxes2.extend(tlb2) + return bboxes, bboxes2 + + def _update_label_position(self, renderer): """ Update the label position based on the bounding box enclosing all the ticklabels and axis spine @@ -1846,13 +1866,18 @@ def set_label_position(self, position): self.label_position = position self.stale = True - def _update_label_position(self, bboxes, bboxes2): + def _update_label_position(self, renderer): """ Update the label position based on the bounding box enclosing all the ticklabels and axis spine """ if not self._autolabelpos: return + + # get bounding boxes for this axis and any siblings + # that have been set by `fig.align_xlabels()` + bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer) + x, y = self.label.get_position() if self.label_position == 'bottom': try: @@ -2191,13 +2216,18 @@ def set_label_position(self, position): self.label_position = position self.stale = True - def _update_label_position(self, bboxes, bboxes2): + def _update_label_position(self, renderer): """ Update the label position based on the bounding box enclosing all the ticklabels and axis spine """ if not self._autolabelpos: return + + # get bounding boxes for this axis and any siblings + # that have been set by `fig.align_ylabels()` + bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer) + x, y = self.label.get_position() if self.label_position == 'left': try: @@ -2209,7 +2239,6 @@ def _update_label_position(self, bboxes, bboxes2): spinebbox = self.axes.bbox bbox = mtransforms.Bbox.union(bboxes + [spinebbox]) left = bbox.x0 - self.label.set_position( (left - self.labelpad * self.figure.dpi / 72.0, y) ) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index b56c67b3c054..c2636e0e7368 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2084,6 +2084,216 @@ def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None, pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect) self.subplots_adjust(**kwargs) + def align_xlabels(self, axs=None, renderer=None): + """ + Align the xlabels of subplots in this figure. + + If a label is on the bottom, it is aligned with labels on axes that + also have their label on the bottom and that have the same + bottom-most subplot row. If the label is on the top, + it is aligned with labels on axes with the same top-most row. + + Parameters + ---------- + axs : list of `~matplotlib.axes.Axes` (None) + Optional list of `~matplotlib.axes.Axes` to align + the xlabels. + + renderer : (None) + Optional renderer to do the adjustment on. + + See Also + -------- + matplotlib.figure.Figure.align_ylabels + + matplotlib.figure.Figure.align_labels + + Example + ------- + Example with rotated xtick labels:: + + fig, axs = plt.subplots(1, 2) + for tick in axs[0].get_xticklabels(): + tick.set_rotation(55) + axs[0].set_xlabel('XLabel 0') + axs[1].set_xlabel('XLabel 1') + fig.align_xlabels() + + """ + + from .tight_layout import get_renderer + + if renderer is None: + renderer = get_renderer(self) + + if axs is None: + axs = self.axes + + axs = np.asarray(np.array(axs)).flatten().tolist() + + for ax in axs: + _log.debug(' Working on: %s', ax.get_xlabel()) + ss = ax.get_subplotspec() + nrows, ncols, row0, row1, col0, col1 = ss.get_rows_columns() + same = [ax] + labpo = ax.xaxis.get_label_position() + for axc in axs: + if axc.xaxis.get_label_position() == labpo: + ss = axc.get_subplotspec() + nrows, ncols, rowc0, rowc1, colc, col1 = \ + ss.get_rows_columns() + if (labpo == 'bottom') and (rowc1 == row1): + same += [axc] + elif (labpo == 'top') and (rowc0 == row0): + same += [axc] + + for axx in same: + _log.debug(' Same: %s', axx.xaxis.label) + axx.xaxis._align_label_siblings += [ax.xaxis] + + def align_ylabels(self, axs=None, renderer=None): + """ + Align the ylabels of subplots in this figure. + + If a label is on the left, it is aligned with labels on axes that + also have their label on the left and that have the same + left-most subplot column. If the label is on the right, + it is aligned with labels on axes with the same right-most column. + + Parameters + ---------- + axs : list of `~matplotlib.axes.Axes` (None) + Optional list of `~matplotlib.axes.Axes` to align + the ylabels. + + renderer : (None) + Optional renderer to do the adjustment on. + + See Also + -------- + matplotlib.figure.Figure.align_xlabels + + matplotlib.figure.Figure.align_labels + + Example + ------- + Example with large yticks labels:: + + fig, axs = plt.subplots(2, 1) + axs[0].plot(np.arange(0, 1000, 50)) + axs[0].set_ylabel('YLabel 0') + axs[1].set_ylabel('YLabel 1') + fig.align_ylabels() + + """ + + from .tight_layout import get_renderer + + if renderer is None: + renderer = get_renderer(self) + + if axs is None: + axs = self.axes + + axs = np.asarray(np.array(axs)).flatten().tolist() + for ax in axs: + _log.debug(' Working on: %s', ax.get_ylabel()) + ss = ax.get_subplotspec() + nrows, ncols, row0, row1, col0, col1 = ss.get_rows_columns() + same = [ax] + labpo = ax.yaxis.get_label_position() + for axc in axs: + if axc != ax: + if axc.yaxis.get_label_position() == labpo: + ss = axc.get_subplotspec() + nrows, ncols, row0, row1, colc0, colc1 = \ + ss.get_rows_columns() + if (labpo == 'left') and (colc0 == col0): + same += [axc] + elif (labpo == 'right') and (colc1 == col1): + same += [axc] + for axx in same: + _log.debug(' Same: %s', axx.yaxis.label) + axx.yaxis._align_label_siblings += [ax.yaxis] + + # place holder until #9498 is merged... + def align_titles(self, axs=None, renderer=None): + """ + Align the titles of subplots in this figure. + + Parameters + ---------- + axs : list of `~matplotlib.axes.Axes` (None) + Optional list of axes to align the xlabels. + + renderer : (None) + Optional renderer to do the adjustment on. + + See Also + -------- + matplotlib.figure.Figure.align_xlabels + + matplotlib.figure.Figure.align_ylabels + """ + + from .tight_layout import get_renderer + + if renderer is None: + renderer = get_renderer(self) + + if axs is None: + axs = self.axes + + while len(axs): + ax = axs.pop() + ax._update_title_position(renderer) + same = [ax] + if ax._autolabelpos: + ss = ax.get_subplotspec() + nrows, ncols, row0, row1, col0, col1 = ss.get_rows_columns() + labpo = ax.xaxis.get_label_position() + for axc in axs: + axc._update_title_position(renderer) + if axc._autolabelpos: + ss = axc.get_subplotspec() + nrows, ncols, rowc0, rowc1, colc, col1 = \ + ss.get_rows_columns() + if (rowc0 == row0): + same += [axc] + + x0, y0 = ax.title.get_position() + for axx in same: + x, y = axx.title.get_position() + if y > y0: + ax.title.set_position(x0, y) + y0 = y + elif y0 > y: + axx.title.set_positions(x, y0) + + def align_labels(self, axs=None, renderer=None): + """ + Align the xlabels and ylabels of subplots with the same subplots + row or column (respectively). + + Parameters + ---------- + axs : list of `~matplotlib.axes.Axes` (None) + Optional list (or ndarray) of `~matplotlib.axes.Axes` to + align the labels. + + renderer : (None) + Optional renderer to do the adjustment on. + + See Also + -------- + matplotlib.figure.Figure.align_xlabels + + matplotlib.figure.Figure.align_ylabels + """ + self.align_xlabels(axs=axs, renderer=renderer) + self.align_ylabels(axs=axs, renderer=renderer) + # self.align_titles(axs=axs, renderer=renderer) + def figaspect(arg): """ diff --git a/lib/matplotlib/gridspec.py b/lib/matplotlib/gridspec.py index 035f554959af..8bd46c1a7d5f 100644 --- a/lib/matplotlib/gridspec.py +++ b/lib/matplotlib/gridspec.py @@ -340,13 +340,30 @@ def get_gridspec(self): return self._gridspec def get_geometry(self): - """Get the subplot geometry (``n_rows, n_cols, row, col``). + """ + Get the subplot geometry (``n_rows, n_cols, start, stop``). - Unlike SuplorParams, indexes are 0-based. + start and stop are the index of the start and stop of the + subplot. """ rows, cols = self.get_gridspec().get_geometry() return rows, cols, self.num1, self.num2 + def get_rows_columns(self): + """ + Get the subplot row and column numbers: + (``n_rows, n_cols, row_start, row_stop, col_start, col_stop``) + """ + gridspec = self.get_gridspec() + nrows, ncols = gridspec.get_geometry() + row_start, col_start = divmod(self.num1, ncols) + if self.num2 is not None: + row_stop, col_stop = divmod(self.num2, ncols) + else: + row_stop = row_start + col_stop = col_start + return nrows, ncols, row_start, row_stop, col_start, col_stop + def get_position(self, fig, return_all=False): """Update the subplot position from ``fig.subplotpars``. """ diff --git a/lib/matplotlib/tests/baseline_images/test_figure/figure_align_labels.pdf b/lib/matplotlib/tests/baseline_images/test_figure/figure_align_labels.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c1763f68be19210a13a4e5a4fb92ce81e7fede67 GIT binary patch literal 11021 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 NfrP+dN;=Ar{{vBv{BHby7w19Na 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index 4ad48d5d6966..3ec1c59e0dab 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -6,11 +6,53 @@ from matplotlib.ticker import AutoMinorLocator, FixedFormatter import matplotlib.pyplot as plt import matplotlib.dates as mdates +import matplotlib.gridspec as gridspec import numpy as np import warnings import pytest +@image_comparison(baseline_images=['figure_align_labels']) +def test_align_labels(): + # Check the figure.align_labels() command + fig = plt.figure(tight_layout=True) + gs = gridspec.GridSpec(3, 3) + + ax = fig.add_subplot(gs[0, :2]) + ax.plot(np.arange(0, 1e6, 1000)) + ax.set_ylabel('Ylabel0 0') + ax = fig.add_subplot(gs[0, -1]) + ax.plot(np.arange(0, 1e4, 100)) + + for i in range(3): + ax = fig.add_subplot(gs[1, i]) + ax.set_ylabel('YLabel1 %d' % i) + ax.set_xlabel('XLabel1 %d' % i) + if i in [0, 2]: + ax.xaxis.set_label_position("top") + ax.xaxis.tick_top() + if i == 0: + for tick in ax.get_xticklabels(): + tick.set_rotation(90) + if i == 2: + ax.yaxis.set_label_position("right") + ax.yaxis.tick_right() + + for i in range(3): + ax = fig.add_subplot(gs[2, i]) + ax.set_xlabel('XLabel2 %d' % (i)) + ax.set_ylabel('YLabel2 %d' % (i)) + + if i == 2: + ax.plot(np.arange(0, 1e4, 10)) + ax.yaxis.set_label_position("right") + ax.yaxis.tick_right() + for tick in ax.get_xticklabels(): + tick.set_rotation(90) + + fig.align_labels() + + def test_figure_label(): # pyplot figure creation, selection and closing with figure label and # number From a8f72f3e8f515b61ec31a86e0268ce4b410ff961 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Fri, 12 Jan 2018 14:22:59 -0800 Subject: [PATCH 2/4] Refinements --- .../2017-11-1_figure_align_labels.rst | 2 + .../align_labels_demo.py | 11 +- lib/matplotlib/axis.py | 4 +- lib/matplotlib/figure.py | 150 ++++++------------ 4 files changed, 54 insertions(+), 113 deletions(-) diff --git a/doc/users/next_whats_new/2017-11-1_figure_align_labels.rst b/doc/users/next_whats_new/2017-11-1_figure_align_labels.rst index 946cf11067b6..8128512c1576 100644 --- a/doc/users/next_whats_new/2017-11-1_figure_align_labels.rst +++ b/doc/users/next_whats_new/2017-11-1_figure_align_labels.rst @@ -13,6 +13,8 @@ Only labels that have the same subplot locations are aligned. i.e. the ylabels are aligned only if the subplots are in the same column of the subplot layout. +Alignemnt is persistent and automatic after these are called. + A convenience wrapper `Figure.align_labels` calls both functions at once. .. plot:: diff --git a/examples/subplots_axes_and_figures/align_labels_demo.py b/examples/subplots_axes_and_figures/align_labels_demo.py index b08bd48cc98e..bd574156a506 100644 --- a/examples/subplots_axes_and_figures/align_labels_demo.py +++ b/examples/subplots_axes_and_figures/align_labels_demo.py @@ -3,15 +3,14 @@ Aligning Labels =============== -Aligning xlabel and ylabel using -`Figure.align_xlabels` and +Aligning xlabel and ylabel using `Figure.align_xlabels` and `Figure.align_ylabels` `Figure.align_labels` wraps these two functions. -Note that -the xlabel "XLabel1 1" would normally be much closer to the x-axis, and -"YLabel1 0" would be much closer to the y-axis of their respective axes. +Note that the xlabel "XLabel1 1" would normally be much closer to the +x-axis, and "YLabel1 0" would be much closer to the y-axis of their +respective axes. """ import matplotlib.pyplot as plt import numpy as np @@ -33,6 +32,6 @@ if i == 0: for tick in ax.get_xticklabels(): tick.set_rotation(55) -fig.align_labels() # same as fig.align_xlabels() and fig.align_ylabels() +fig.align_labels() # same as fig.align_xlabels(); fig.align_ylabels() plt.show() diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 2424e9630bb1..1fc8b62f0950 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -1675,8 +1675,8 @@ def set_ticks(self, ticks, minor=False): def _get_tick_boxes_siblings(self, renderer): """ - Get the bounding boxes for this axis and its sibblings - as set by `Figure.align_xlabels` or ``Figure.align_ylables`. + Get the bounding boxes for this `.axis` and its siblings + as set by `.Figure.align_xlabels` or `.Figure.align_ylablels`. By default it just gets bboxes for self. """ diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index c2636e0e7368..99888255b7ac 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -2084,9 +2084,13 @@ def tight_layout(self, renderer=None, pad=1.08, h_pad=None, w_pad=None, pad=pad, h_pad=h_pad, w_pad=w_pad, rect=rect) self.subplots_adjust(**kwargs) - def align_xlabels(self, axs=None, renderer=None): + def align_xlabels(self, axs=None): """ - Align the xlabels of subplots in this figure. + Align the ylabels of subplots in the same subplot column if label + alignment is being done automatically (i.e. the label position is + not manually set). + + Alignment persists for draw events after this is called. If a label is on the bottom, it is aligned with labels on axes that also have their label on the bottom and that have the same @@ -2096,11 +2100,8 @@ def align_xlabels(self, axs=None, renderer=None): Parameters ---------- axs : list of `~matplotlib.axes.Axes` (None) - Optional list of `~matplotlib.axes.Axes` to align - the xlabels. - - renderer : (None) - Optional renderer to do the adjustment on. + Optional list of (or ndarray) `~matplotlib.axes.Axes` to align + the xlabels. Default is to align all axes on the figure. See Also -------- @@ -2121,11 +2122,6 @@ def align_xlabels(self, axs=None, renderer=None): """ - from .tight_layout import get_renderer - - if renderer is None: - renderer = get_renderer(self) - if axs is None: axs = self.axes @@ -2135,25 +2131,30 @@ def align_xlabels(self, axs=None, renderer=None): _log.debug(' Working on: %s', ax.get_xlabel()) ss = ax.get_subplotspec() nrows, ncols, row0, row1, col0, col1 = ss.get_rows_columns() - same = [ax] - labpo = ax.xaxis.get_label_position() + labpo = ax.xaxis.get_label_position() # top or bottom + + # loop through other axes, and search for label positions + # that are same as this one, and that share the appropriate + # row number. + # Add to a list associated with each axes of sibblings. + # This list is inspected in `axis.draw` by + # `axis._update_label_position`. for axc in axs: if axc.xaxis.get_label_position() == labpo: ss = axc.get_subplotspec() nrows, ncols, rowc0, rowc1, colc, col1 = \ ss.get_rows_columns() - if (labpo == 'bottom') and (rowc1 == row1): - same += [axc] - elif (labpo == 'top') and (rowc0 == row0): - same += [axc] - - for axx in same: - _log.debug(' Same: %s', axx.xaxis.label) - axx.xaxis._align_label_siblings += [ax.xaxis] + if (labpo == 'bottom' and rowc1 == row1 or + labpo == 'top' and rowc0 == row0): + axc.xaxis._align_label_siblings += [ax.xaxis] - def align_ylabels(self, axs=None, renderer=None): + def align_ylabels(self, axs=None): """ - Align the ylabels of subplots in this figure. + Align the ylabels of subplots in the same subplot column if label + alignment is being done automatically (i.e. the label position is + not manually set). + + Alignment persists for draw events after this is called. If a label is on the left, it is aligned with labels on axes that also have their label on the left and that have the same @@ -2163,11 +2164,8 @@ def align_ylabels(self, axs=None, renderer=None): Parameters ---------- axs : list of `~matplotlib.axes.Axes` (None) - Optional list of `~matplotlib.axes.Axes` to align - the ylabels. - - renderer : (None) - Optional renderer to do the adjustment on. + Optional list (or ndarray) of `~matplotlib.axes.Axes` to align + the ylabels. Default is to align all axes on the figure. See Also -------- @@ -2187,11 +2185,6 @@ def align_ylabels(self, axs=None, renderer=None): """ - from .tight_layout import get_renderer - - if renderer is None: - renderer = get_renderer(self) - if axs is None: axs = self.axes @@ -2201,88 +2194,36 @@ def align_ylabels(self, axs=None, renderer=None): ss = ax.get_subplotspec() nrows, ncols, row0, row1, col0, col1 = ss.get_rows_columns() same = [ax] - labpo = ax.yaxis.get_label_position() + labpo = ax.yaxis.get_label_position() # left or right + # loop through other axes, and search for label positions + # that are same as this one, and that share the appropriate + # column number. + # Add to a list associated with each axes of sibblings. + # This list is inspected in `axis.draw` by + # `axis._update_label_position`. for axc in axs: if axc != ax: if axc.yaxis.get_label_position() == labpo: ss = axc.get_subplotspec() nrows, ncols, row0, row1, colc0, colc1 = \ ss.get_rows_columns() - if (labpo == 'left') and (colc0 == col0): - same += [axc] - elif (labpo == 'right') and (colc1 == col1): - same += [axc] - for axx in same: - _log.debug(' Same: %s', axx.yaxis.label) - axx.yaxis._align_label_siblings += [ax.yaxis] - - # place holder until #9498 is merged... - def align_titles(self, axs=None, renderer=None): - """ - Align the titles of subplots in this figure. - - Parameters - ---------- - axs : list of `~matplotlib.axes.Axes` (None) - Optional list of axes to align the xlabels. - - renderer : (None) - Optional renderer to do the adjustment on. - - See Also - -------- - matplotlib.figure.Figure.align_xlabels - - matplotlib.figure.Figure.align_ylabels - """ - - from .tight_layout import get_renderer - - if renderer is None: - renderer = get_renderer(self) - - if axs is None: - axs = self.axes - - while len(axs): - ax = axs.pop() - ax._update_title_position(renderer) - same = [ax] - if ax._autolabelpos: - ss = ax.get_subplotspec() - nrows, ncols, row0, row1, col0, col1 = ss.get_rows_columns() - labpo = ax.xaxis.get_label_position() - for axc in axs: - axc._update_title_position(renderer) - if axc._autolabelpos: - ss = axc.get_subplotspec() - nrows, ncols, rowc0, rowc1, colc, col1 = \ - ss.get_rows_columns() - if (rowc0 == row0): - same += [axc] - - x0, y0 = ax.title.get_position() - for axx in same: - x, y = axx.title.get_position() - if y > y0: - ax.title.set_position(x0, y) - y0 = y - elif y0 > y: - axx.title.set_positions(x, y0) + if (labpo == 'left' and colc0 == col0 or + labpo == 'right' and colc1 == col1): + axc.yaxis._align_label_siblings += [ax.yaxis] - def align_labels(self, axs=None, renderer=None): + def align_labels(self, axs=None): """ Align the xlabels and ylabels of subplots with the same subplots - row or column (respectively). + row or column (respectively) if label alignment is being + done automatically (i.e. the label position is not manually set). + + Alignment persists for draw events after this is called. Parameters ---------- axs : list of `~matplotlib.axes.Axes` (None) Optional list (or ndarray) of `~matplotlib.axes.Axes` to - align the labels. - - renderer : (None) - Optional renderer to do the adjustment on. + align the labels. Default is to align all axes on the figure. See Also -------- @@ -2290,9 +2231,8 @@ def align_labels(self, axs=None, renderer=None): matplotlib.figure.Figure.align_ylabels """ - self.align_xlabels(axs=axs, renderer=renderer) - self.align_ylabels(axs=axs, renderer=renderer) - # self.align_titles(axs=axs, renderer=renderer) + self.align_xlabels(axs=axs) + self.align_ylabels(axs=axs) def figaspect(arg): From 17d1025ba9bab4e37390aa8ab8633a2f30aee776 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Fri, 12 Jan 2018 21:39:16 -0800 Subject: [PATCH 3/4] Changed from lists to Grouper --- lib/matplotlib/axis.py | 53 ++++++++++++++++++++++++++++++---------- lib/matplotlib/figure.py | 24 ++++++++++++++---- 2 files changed, 59 insertions(+), 18 deletions(-) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 1fc8b62f0950..ec11aeb8f598 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -692,7 +692,6 @@ def __init__(self, axes, pickradius=15): self._autolabelpos = True self._smart_bounds = False - self._align_label_siblings = [self] self.label = self._get_label() self.labelpad = rcParams['axes.labelpad'] @@ -1673,22 +1672,14 @@ def set_ticks(self, ticks, minor=False): self.set_major_locator(mticker.FixedLocator(ticks)) return self.get_major_ticks(len(ticks)) - def _get_tick_boxes_siblings(self, renderer): + def _get_tick_boxes_siblings(self, xdir, renderer): """ Get the bounding boxes for this `.axis` and its siblings as set by `.Figure.align_xlabels` or `.Figure.align_ylablels`. By default it just gets bboxes for self. """ - bboxes = [] - bboxes2 = [] - # if we want to align labels from other axes: - for axx in self._align_label_siblings: - ticks_to_draw = axx._update_ticks(renderer) - tlb, tlb2 = axx._get_tick_bboxes(ticks_to_draw, renderer) - bboxes.extend(tlb) - bboxes2.extend(tlb2) - return bboxes, bboxes2 + raise NotImplementedError('Derived must override') def _update_label_position(self, renderer): """ @@ -1866,6 +1857,24 @@ def set_label_position(self, position): self.label_position = position self.stale = True + def _get_tick_boxes_siblings(self, renderer): + """ + Get the bounding boxes for this `.axis` and its siblings + as set by `.Figure.align_xlabels` or `.Figure.align_ylablels`. + + By default it just gets bboxes for self. + """ + bboxes = [] + bboxes2 = [] + grp = self.figure._align_xlabel_grp + # if we want to align labels from other axes: + for axx in grp.get_siblings(self.axes): + ticks_to_draw = axx.xaxis._update_ticks(renderer) + tlb, tlb2 = axx.xaxis._get_tick_bboxes(ticks_to_draw, renderer) + bboxes.extend(tlb) + bboxes2.extend(tlb2) + return bboxes, bboxes2 + def _update_label_position(self, renderer): """ Update the label position based on the bounding box enclosing @@ -1876,7 +1885,7 @@ def _update_label_position(self, renderer): # get bounding boxes for this axis and any siblings # that have been set by `fig.align_xlabels()` - bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer) + bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer) x, y = self.label.get_position() if self.label_position == 'bottom': @@ -2216,6 +2225,24 @@ def set_label_position(self, position): self.label_position = position self.stale = True + def _get_tick_boxes_siblings(self, renderer): + """ + Get the bounding boxes for this `.axis` and its siblings + as set by `.Figure.align_xlabels` or `.Figure.align_ylablels`. + + By default it just gets bboxes for self. + """ + bboxes = [] + bboxes2 = [] + grp = self.figure._align_ylabel_grp + # if we want to align labels from other axes: + for axx in grp.get_siblings(self.axes): + ticks_to_draw = axx.yaxis._update_ticks(renderer) + tlb, tlb2 = axx.yaxis._get_tick_bboxes(ticks_to_draw, renderer) + bboxes.extend(tlb) + bboxes2.extend(tlb2) + return bboxes, bboxes2 + def _update_label_position(self, renderer): """ Update the label position based on the bounding box enclosing @@ -2226,7 +2253,7 @@ def _update_label_position(self, renderer): # get bounding boxes for this axis and any siblings # that have been set by `fig.align_ylabels()` - bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer) + bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer) x, y = self.label.get_position() if self.label_position == 'left': diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 99888255b7ac..fe489a12e70c 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -380,6 +380,10 @@ def __init__(self, self.clf() self._cachedRenderer = None + # groupers to keep track of x and y labels we want to align. + self._align_xlabel_grp = cbook.Grouper() + self._align_ylabel_grp = cbook.Grouper() + @property @cbook.deprecated("2.1", alternative="Figure.patch") def figurePatch(self): @@ -2103,6 +2107,11 @@ def align_xlabels(self, axs=None): Optional list of (or ndarray) `~matplotlib.axes.Axes` to align the xlabels. Default is to align all axes on the figure. + Note + ---- + This assumes that ``axs`` are from the same `~.GridSpec`, so that + their `~.SubplotSpec` positions correspond to figure positions. + See Also -------- matplotlib.figure.Figure.align_ylabels @@ -2124,7 +2133,6 @@ def align_xlabels(self, axs=None): if axs is None: axs = self.axes - axs = np.asarray(np.array(axs)).flatten().tolist() for ax in axs: @@ -2136,7 +2144,7 @@ def align_xlabels(self, axs=None): # loop through other axes, and search for label positions # that are same as this one, and that share the appropriate # row number. - # Add to a list associated with each axes of sibblings. + # Add to a grouper associated with each axes of sibblings. # This list is inspected in `axis.draw` by # `axis._update_label_position`. for axc in axs: @@ -2146,7 +2154,8 @@ def align_xlabels(self, axs=None): ss.get_rows_columns() if (labpo == 'bottom' and rowc1 == row1 or labpo == 'top' and rowc0 == row0): - axc.xaxis._align_label_siblings += [ax.xaxis] + # grouper for groups of xlabels to align + self._align_xlabel_grp.join(ax, axc) def align_ylabels(self, axs=None): """ @@ -2167,6 +2176,11 @@ def align_ylabels(self, axs=None): Optional list (or ndarray) of `~matplotlib.axes.Axes` to align the ylabels. Default is to align all axes on the figure. + Note + ---- + This assumes that ``axs`` are from the same `~.GridSpec`, so that + their `~.SubplotSpec` positions correspond to figure positions. + See Also -------- matplotlib.figure.Figure.align_xlabels @@ -2187,7 +2201,6 @@ def align_ylabels(self, axs=None): if axs is None: axs = self.axes - axs = np.asarray(np.array(axs)).flatten().tolist() for ax in axs: _log.debug(' Working on: %s', ax.get_ylabel()) @@ -2209,7 +2222,8 @@ def align_ylabels(self, axs=None): ss.get_rows_columns() if (labpo == 'left' and colc0 == col0 or labpo == 'right' and colc1 == col1): - axc.yaxis._align_label_siblings += [ax.yaxis] + # grouper for groups of ylabels to align + self._align_ylabel_grp.join(ax, axc) def align_labels(self, axs=None): """ From 2d478111637d2ed1541d8ab9e99e66c9d9872e10 Mon Sep 17 00:00:00 2001 From: Jody Klymak Date: Fri, 12 Jan 2018 21:49:26 -0800 Subject: [PATCH 4/4] DOC: some more comments --- lib/matplotlib/axis.py | 4 +++- lib/matplotlib/figure.py | 7 ++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index ec11aeb8f598..47caa380279f 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -1866,9 +1866,10 @@ def _get_tick_boxes_siblings(self, renderer): """ bboxes = [] bboxes2 = [] + # get the Grouper that keeps track of x-label groups for this figure grp = self.figure._align_xlabel_grp # if we want to align labels from other axes: - for axx in grp.get_siblings(self.axes): + for nn, axx in enumerate(grp.get_siblings(self.axes)): ticks_to_draw = axx.xaxis._update_ticks(renderer) tlb, tlb2 = axx.xaxis._get_tick_bboxes(ticks_to_draw, renderer) bboxes.extend(tlb) @@ -2234,6 +2235,7 @@ def _get_tick_boxes_siblings(self, renderer): """ bboxes = [] bboxes2 = [] + # get the Grouper that keeps track of y-label groups for this figure grp = self.figure._align_ylabel_grp # if we want to align labels from other axes: for axx in grp.get_siblings(self.axes): diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index fe489a12e70c..d9e00247574b 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -381,6 +381,8 @@ def __init__(self, self._cachedRenderer = None # groupers to keep track of x and y labels we want to align. + # see self.align_xlabels and self.align_ylabels and + # axis._get_tick_boxes_siblings self._align_xlabel_grp = cbook.Grouper() self._align_ylabel_grp = cbook.Grouper() @@ -2133,8 +2135,7 @@ def align_xlabels(self, axs=None): if axs is None: axs = self.axes - axs = np.asarray(np.array(axs)).flatten().tolist() - + axs = np.asarray(axs).ravel() for ax in axs: _log.debug(' Working on: %s', ax.get_xlabel()) ss = ax.get_subplotspec() @@ -2201,7 +2202,7 @@ def align_ylabels(self, axs=None): if axs is None: axs = self.axes - axs = np.asarray(np.array(axs)).flatten().tolist() + axs = np.asarray(axs).ravel() for ax in axs: _log.debug(' Working on: %s', ax.get_ylabel()) ss = ax.get_subplotspec()