Skip to content

Commit dfae504

Browse files
committed
Improve handling of subplots spanning multiple gridspec cells.
See changelog.
1 parent 74e9dc7 commit dfae504

File tree

4 files changed

+83
-16
lines changed

4 files changed

+83
-16
lines changed
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
``Axes.label_outer``, ``Axes.is_last_row``, and ``Axes.is_last_col`` now work correctly for axes spanning multiple rows or columns
2+
``````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````
3+
4+
``Axes.label_outer`` now correctly keep the x labels and tick labels visible
5+
for Axes spanning multiple rows, as long as they cover the last row of the Axes
6+
grid. (This is consistent with keeping the y labels and tick labels visible
7+
for Axes spanning multiple columns as long as they cover the first column of
8+
the Axes grid.)
9+
10+
The ``Axes.is_last_row`` and ``Axes.is_last_col`` methods now correctly return
11+
True for Axes spanning multiple rows, as long as they cover the last row or
12+
column respectively. Again this is consistent with the behavior for axes
13+
covering the first row or column.
14+
15+
The ``Axes.rowNum`` and ``Axes.colNum`` attributes are deprecated, as they only
16+
refer to the first grid cell covered by the Axes. Instead, use the new
17+
``ax.get_subplotspec().rowspan`` and ``ax.get_subplotspec().colspan``
18+
properties, which are `range` objects indicating the whole span of rows and
19+
columns covered by the subplot.
20+
21+
(Note that all methods and attributes mentioned here actually only exist on
22+
the `Subplot` subclass of `Axes`, which is used for grid-positioned Axes but
23+
not for Axes positioned directly in absolute coordinates.)
24+
25+
The `GridSpec` class gained the ``nrows`` and ``ncols`` properties as more
26+
explicit synonyms for the parameters returned by `GridSpec.get_geometry`.

lib/matplotlib/axes/_subplots.py

+17-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import functools
22
import uuid
33

4-
from matplotlib import docstring
4+
from matplotlib import cbook, docstring
55
import matplotlib.artist as martist
66
from matplotlib.axes._axes import Axes
77
from matplotlib.gridspec import GridSpec, SubplotSpec
@@ -125,26 +125,33 @@ def get_gridspec(self):
125125

126126
def update_params(self):
127127
"""update the subplot position from fig.subplotpars"""
128-
129-
self.figbox, self.rowNum, self.colNum, self.numRows, self.numCols = \
128+
self.figbox, _, _, self.numRows, self.numCols = \
130129
self.get_subplotspec().get_position(self.figure,
131130
return_all=True)
132131

133-
def is_first_col(self):
134-
return self.colNum == 0
132+
@cbook.deprecated("3.2", alternative="ax.get_subplotspec().rowspan.start")
133+
def rowNum(self):
134+
return self.get_subplotspec().rowspan.start
135+
136+
@cbook.deprecated("3.2", alternative="ax.get_subplotspec().colspan.start")
137+
def colNum(self):
138+
return self.get_subplotspec().colspan.start
135139

136140
def is_first_row(self):
137-
return self.rowNum == 0
141+
return self.get_subplotspec().rowspan.start == 0
138142

139143
def is_last_row(self):
140-
return self.rowNum == self.numRows - 1
144+
return self.get_subplotspec().rowspan.stop == self.get_gridspec().nrows
145+
146+
def is_first_col(self):
147+
return self.get_subplotspec().colspan.start == 0
141148

142149
def is_last_col(self):
143-
return self.colNum == self.numCols - 1
150+
return self.get_subplotspec().colspan.stop == self.get_gridspec().ncols
144151

145-
# COVERAGE NOTE: Never used internally.
146152
def label_outer(self):
147-
"""Only show "outer" labels and tick labels.
153+
"""
154+
Only show "outer" labels and tick labels.
148155
149156
x-labels are only kept for subplots on the last row; y-labels only for
150157
subplots on the first column.

lib/matplotlib/gridspec.py

+17
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ def __repr__(self):
5959
optionals=height_arg + width_arg,
6060
)
6161

62+
nrows = property(lambda self: self._nrows,
63+
doc="The number of rows in the grid.")
64+
ncols = property(lambda self: self._ncols,
65+
doc="The number of columns in the grid.")
66+
6267
def get_geometry(self):
6368
"""
6469
Return a tuple containing the number of rows and columns in the grid.
@@ -566,6 +571,18 @@ def get_rows_columns(self):
566571
row_stop, col_stop = divmod(self.num2, ncols)
567572
return nrows, ncols, row_start, row_stop, col_start, col_stop
568573

574+
@property
575+
def rowspan(self):
576+
"""The rows spanned by this subplot, as a `range` object."""
577+
ncols = self.get_gridspec().ncols
578+
return range(self.num1 // ncols, self.num2 // ncols + 1)
579+
580+
@property
581+
def colspan(self):
582+
"""The columns spanned by this subplot, as a `range` object."""
583+
ncols = self.get_gridspec().ncols
584+
return range(self.num1 % ncols, self.num2 % ncols + 1)
585+
569586
def get_position(self, figure, return_all=False):
570587
"""
571588
Update the subplot position from ``figure.subplotpars``.

lib/matplotlib/tests/test_subplots.py

+23-6
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,13 @@ def check_shared(axs, x_shared, y_shared):
2626

2727

2828
def check_visible(axs, x_visible, y_visible):
29-
def tostr(v):
30-
return "invisible" if v else "visible"
31-
32-
for ax, vx, vy in zip(axs, x_visible, y_visible):
29+
for i, (ax, vx, vy) in enumerate(zip(axs, x_visible, y_visible)):
3330
for l in ax.get_xticklabels() + [ax.get_xaxis().offsetText]:
3431
assert l.get_visible() == vx, \
35-
"X axis was incorrectly %s" % (tostr(vx))
32+
f"Visibility of x axis #{i} is incorrectly {vx}"
3633
for l in ax.get_yticklabels() + [ax.get_yaxis().offsetText]:
3734
assert l.get_visible() == vy, \
38-
"Y axis was incorrectly %s" % (tostr(vy))
35+
f"Visibility of y axis #{i} is incorrectly {vy}"
3936

4037

4138
def test_shared():
@@ -99,6 +96,26 @@ def test_shared():
9996
check_visible(axs, [False, False, True, True], [True, False, True, False])
10097

10198

99+
def test_label_outer_span():
100+
fig = plt.figure()
101+
gs = fig.add_gridspec(3, 3)
102+
# +---+---+---+
103+
# | 1 | |
104+
# +---+---+---+
105+
# | | | 3 |
106+
# + 2 +---+---+
107+
# | | 4 | |
108+
# +---+---+---+
109+
a1 = fig.add_subplot(gs[0, 0:2])
110+
a2 = fig.add_subplot(gs[1:3, 0])
111+
a3 = fig.add_subplot(gs[1, 2])
112+
a4 = fig.add_subplot(gs[2, 1])
113+
for ax in fig.axes:
114+
ax.label_outer()
115+
check_visible(
116+
fig.axes, [False, True, False, True], [True, True, False, False])
117+
118+
102119
def test_shared_and_moved():
103120
# test if sharey is on, but then tick_left is called that labels don't
104121
# re-appear. Seaborn does this just to be sure yaxis is on left...

0 commit comments

Comments
 (0)