diff --git a/doc/api/next_api_changes/2019-02-28-AL.rst b/doc/api/next_api_changes/2019-02-28-AL.rst new file mode 100644 index 000000000000..1e7a99627f46 --- /dev/null +++ b/doc/api/next_api_changes/2019-02-28-AL.rst @@ -0,0 +1,26 @@ +``Axes.label_outer``, ``Axes.is_last_row``, and ``Axes.is_last_col`` now work correctly for axes spanning multiple rows or columns +`````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````` + +``Axes.label_outer`` now correctly keep the x labels and tick labels visible +for Axes spanning multiple rows, as long as they cover the last row of the Axes +grid. (This is consistent with keeping the y labels and tick labels visible +for Axes spanning multiple columns as long as they cover the first column of +the Axes grid.) + +The ``Axes.is_last_row`` and ``Axes.is_last_col`` methods now correctly return +True for Axes spanning multiple rows, as long as they cover the last row or +column respectively. Again this is consistent with the behavior for axes +covering the first row or column. + +The ``Axes.rowNum`` and ``Axes.colNum`` attributes are deprecated, as they only +refer to the first grid cell covered by the Axes. Instead, use the new +``ax.get_subplotspec().rowspan`` and ``ax.get_subplotspec().colspan`` +properties, which are `range` objects indicating the whole span of rows and +columns covered by the subplot. + +(Note that all methods and attributes mentioned here actually only exist on +the ``Subplot`` subclass of `Axes`, which is used for grid-positioned Axes but +not for Axes positioned directly in absolute coordinates.) + +The `.GridSpec` class gained the ``nrows`` and ``ncols`` properties as more +explicit synonyms for the parameters returned by ``GridSpec.get_geometry``. diff --git a/lib/matplotlib/axes/_subplots.py b/lib/matplotlib/axes/_subplots.py index 6ba9439f6b86..e216de13426c 100644 --- a/lib/matplotlib/axes/_subplots.py +++ b/lib/matplotlib/axes/_subplots.py @@ -1,7 +1,7 @@ import functools import uuid -from matplotlib import docstring +from matplotlib import cbook, docstring import matplotlib.artist as martist from matplotlib.axes._axes import Axes from matplotlib.gridspec import GridSpec, SubplotSpec @@ -125,26 +125,33 @@ def get_gridspec(self): def update_params(self): """update the subplot position from fig.subplotpars""" - - self.figbox, self.rowNum, self.colNum, self.numRows, self.numCols = \ + self.figbox, _, _, self.numRows, self.numCols = \ self.get_subplotspec().get_position(self.figure, return_all=True) - def is_first_col(self): - return self.colNum == 0 + @cbook.deprecated("3.2", alternative="ax.get_subplotspec().rowspan.start") + def rowNum(self): + return self.get_subplotspec().rowspan.start + + @cbook.deprecated("3.2", alternative="ax.get_subplotspec().colspan.start") + def colNum(self): + return self.get_subplotspec().colspan.start def is_first_row(self): - return self.rowNum == 0 + return self.get_subplotspec().rowspan.start == 0 def is_last_row(self): - return self.rowNum == self.numRows - 1 + return self.get_subplotspec().rowspan.stop == self.get_gridspec().nrows + + def is_first_col(self): + return self.get_subplotspec().colspan.start == 0 def is_last_col(self): - return self.colNum == self.numCols - 1 + return self.get_subplotspec().colspan.stop == self.get_gridspec().ncols - # COVERAGE NOTE: Never used internally. def label_outer(self): - """Only show "outer" labels and tick labels. + """ + Only show "outer" labels and tick labels. x-labels are only kept for subplots on the last row; y-labels only for subplots on the first column. diff --git a/lib/matplotlib/gridspec.py b/lib/matplotlib/gridspec.py index 14f21930219e..18cd55920ba8 100644 --- a/lib/matplotlib/gridspec.py +++ b/lib/matplotlib/gridspec.py @@ -59,6 +59,11 @@ def __repr__(self): optionals=height_arg + width_arg, ) + nrows = property(lambda self: self._nrows, + doc="The number of rows in the grid.") + ncols = property(lambda self: self._ncols, + doc="The number of columns in the grid.") + def get_geometry(self): """ Return a tuple containing the number of rows and columns in the grid. @@ -566,6 +571,18 @@ def get_rows_columns(self): row_stop, col_stop = divmod(self.num2, ncols) return nrows, ncols, row_start, row_stop, col_start, col_stop + @property + def rowspan(self): + """The rows spanned by this subplot, as a `range` object.""" + ncols = self.get_gridspec().ncols + return range(self.num1 // ncols, self.num2 // ncols + 1) + + @property + def colspan(self): + """The columns spanned by this subplot, as a `range` object.""" + ncols = self.get_gridspec().ncols + return range(self.num1 % ncols, self.num2 % ncols + 1) + def get_position(self, figure, return_all=False): """ Update the subplot position from ``figure.subplotpars``. diff --git a/lib/matplotlib/tests/test_subplots.py b/lib/matplotlib/tests/test_subplots.py index c581ec81086c..782d56d736ae 100644 --- a/lib/matplotlib/tests/test_subplots.py +++ b/lib/matplotlib/tests/test_subplots.py @@ -26,16 +26,13 @@ def check_shared(axs, x_shared, y_shared): def check_visible(axs, x_visible, y_visible): - def tostr(v): - return "invisible" if v else "visible" - - for ax, vx, vy in zip(axs, x_visible, y_visible): + for i, (ax, vx, vy) in enumerate(zip(axs, x_visible, y_visible)): for l in ax.get_xticklabels() + [ax.get_xaxis().offsetText]: assert l.get_visible() == vx, \ - "X axis was incorrectly %s" % (tostr(vx)) + f"Visibility of x axis #{i} is incorrectly {vx}" for l in ax.get_yticklabels() + [ax.get_yaxis().offsetText]: assert l.get_visible() == vy, \ - "Y axis was incorrectly %s" % (tostr(vy)) + f"Visibility of y axis #{i} is incorrectly {vy}" def test_shared(): @@ -99,6 +96,26 @@ def test_shared(): check_visible(axs, [False, False, True, True], [True, False, True, False]) +def test_label_outer_span(): + fig = plt.figure() + gs = fig.add_gridspec(3, 3) + # +---+---+---+ + # | 1 | | + # +---+---+---+ + # | | | 3 | + # + 2 +---+---+ + # | | 4 | | + # +---+---+---+ + a1 = fig.add_subplot(gs[0, 0:2]) + a2 = fig.add_subplot(gs[1:3, 0]) + a3 = fig.add_subplot(gs[1, 2]) + a4 = fig.add_subplot(gs[2, 1]) + for ax in fig.axes: + ax.label_outer() + check_visible( + fig.axes, [False, True, False, True], [True, True, False, False]) + + def test_shared_and_moved(): # test if sharey is on, but then tick_left is called that labels don't # re-appear. Seaborn does this just to be sure yaxis is on left...