Skip to content

Commit cc8eb22

Browse files
committed
Improve handling of subplots spanning multiple gridspec cells.
See changelog.
1 parent 9a8df49 commit cc8eb22

File tree

4 files changed

+80
-12
lines changed

4 files changed

+80
-12
lines changed
Lines changed: 26 additions & 0 deletions
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

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -121,26 +121,33 @@ def get_gridspec(self):
121121

122122
def update_params(self):
123123
"""update the subplot position from fig.subplotpars"""
124-
125-
self.figbox, self.rowNum, self.colNum, self.numRows, self.numCols = \
124+
self.figbox, _, _, self.numRows, self.numCols = \
126125
self.get_subplotspec().get_position(self.figure,
127126
return_all=True)
128127

129-
def is_first_col(self):
130-
return self.colNum == 0
128+
@cbook.deprecated("3.2", alternative="ax.get_subplotspec().rowspan.start")
129+
def rowNum(self):
130+
return self.get_subplotspec().rowspan.start
131+
132+
@cbook.deprecated("3.2", alternative="ax.get_subplotspec().colspan.start")
133+
def colNum(self):
134+
return self.get_subplotspec().colspan.start
131135

132136
def is_first_row(self):
133-
return self.rowNum == 0
137+
return self.get_subplotspec().rowspan.start == 0
134138

135139
def is_last_row(self):
136-
return self.rowNum == self.numRows - 1
140+
return self.get_subplotspec().rowspan.stop == self.get_gridspec().nrows
141+
142+
def is_first_col(self):
143+
return self.get_subplotspec().colspan.start == 0
137144

138145
def is_last_col(self):
139-
return self.colNum == self.numCols - 1
146+
return self.get_subplotspec().colspan.stop == self.get_gridspec().ncols
140147

141-
# COVERAGE NOTE: Never used internally.
142148
def label_outer(self):
143-
"""Only show "outer" labels and tick labels.
149+
"""
150+
Only show "outer" labels and tick labels.
144151
145152
x-labels are only kept for subplots on the last row; y-labels only for
146153
subplots on the first column.

lib/matplotlib/gridspec.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ def __repr__(self):
5454
optionals=height_arg + width_arg,
5555
)
5656

57+
nrows = property(lambda self: self._nrows,
58+
doc="The number of rows in the grid.")
59+
ncols = property(lambda self: self._ncols,
60+
doc="The number of columns in the grid.")
61+
5762
def get_geometry(self):
5863
'get the geometry of the grid, e.g., 2,3'
5964
return self._nrows, self._ncols
@@ -478,6 +483,16 @@ def get_rows_columns(self):
478483
row_stop, col_stop = divmod(self.num2, ncols)
479484
return nrows, ncols, row_start, row_stop, col_start, col_stop
480485

486+
@property
487+
def rowspan(self):
488+
"""The rows spanned by this subplot, as a `slice` object."""
489+
return range(self.num1 // self.ncols, self.num2 // self.ncols + 1)
490+
491+
@property
492+
def colspan(self):
493+
"""The columns spanned by this subplot, as a `slice` object."""
494+
return range(self.num1 % self.ncols, self.num2 % self.ncols + 1)
495+
481496
def get_position(self, figure, return_all=False):
482497
"""Update the subplot position from ``figure.subplotpars``.
483498
"""

lib/matplotlib/tests/test_subplots.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ def check_visible(axs, x_visible, y_visible):
3030
def tostr(v):
3131
return "invisible" if v else "visible"
3232

33-
for ax, vx, vy in zip(axs, x_visible, y_visible):
33+
for i, (ax, vx, vy) in enumerate(zip(axs, x_visible, y_visible)):
3434
for l in ax.get_xticklabels() + [ax.get_xaxis().offsetText]:
3535
assert l.get_visible() == vx, \
36-
"X axis was incorrectly %s" % (tostr(vx))
36+
f"X axis #{i} is incorrectly {tostr(vx)}"
3737
for l in ax.get_yticklabels() + [ax.get_yaxis().offsetText]:
3838
assert l.get_visible() == vy, \
39-
"Y axis was incorrectly %s" % (tostr(vy))
39+
f"Y axis #{i} is incorrectly {tostr(vy)}"
4040

4141

4242
def test_shared():
@@ -100,6 +100,26 @@ def test_shared():
100100
check_visible(axs, [False, False, True, True], [True, False, True, False])
101101

102102

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

0 commit comments

Comments
 (0)