Skip to content

Commit 3ae847d

Browse files
committed
Move integerness checks to SubplotSpec._from_subplot_args.
This avoids having to later do them same for other places (axes_grid) which rely on the same machinery to convert three-digit specifications. I'll assume that the already existing deprecation note for add_subplot is effectively sufficient.
1 parent 9e20541 commit 3ae847d

File tree

3 files changed

+54
-52
lines changed

3 files changed

+54
-52
lines changed

lib/matplotlib/figure.py

+10-35
Original file line numberDiff line numberDiff line change
@@ -1264,13 +1264,16 @@ def add_subplot(self, *args, **kwargs):
12641264
12651265
Parameters
12661266
----------
1267-
*args, int or (int, int, int) or `SubplotSpec`, default: (1, 1, 1)
1267+
*args, int, (int, int, *index*), or `SubplotSpec`, default: (1, 1, 1)
12681268
The position of the subplot described by one of
12691269
12701270
- Three integers (*nrows*, *ncols*, *index*). The subplot will
12711271
take the *index* position on a grid with *nrows* rows and
12721272
*ncols* columns. *index* starts at 1 in the upper left corner
1273-
and increases to the right.
1273+
and increases to the right. *index* can also be a two-tuple
1274+
specifying the (*first*, *last*) indices (1-based, and including
1275+
*last*) of the subplot, e.g., ``fig.add_subplot(3, 1, (1, 2))``
1276+
makes a subplot that spans the upper 2/3 of the figure.
12741277
- A 3-digit integer. The digits are interpreted as if given
12751278
separately as three single-digit integers, i.e.
12761279
``fig.add_subplot(235)`` is the same as
@@ -1362,48 +1365,21 @@ def add_subplot(self, *args, **kwargs):
13621365
raise TypeError(
13631366
"add_subplot() got an unexpected keyword argument 'figure'")
13641367

1365-
nargs = len(args)
1366-
if nargs == 0:
1367-
args = (1, 1, 1)
1368-
elif nargs == 1:
1369-
if isinstance(args[0], Integral):
1370-
if not 100 <= args[0] <= 999:
1371-
raise ValueError(f"Integer subplot specification must be "
1372-
f"a three-digit number, not {args[0]}")
1373-
args = tuple(map(int, str(args[0])))
1374-
elif isinstance(args[0], (SubplotBase, SubplotSpec)):
1375-
pass # no further validation or normalization needed
1376-
else:
1377-
raise TypeError('Positional arguments are not a valid '
1378-
'position specification.')
1379-
elif nargs == 3:
1380-
for arg in args:
1381-
if not isinstance(arg, Integral):
1382-
cbook.warn_deprecated(
1383-
"3.3",
1384-
message="Passing non-integers as three-element "
1385-
"position specification is deprecated since "
1386-
"%(since)s and will be removed %(removal)s.")
1387-
args = tuple(map(int, args))
1388-
else:
1389-
raise TypeError(f'add_subplot() takes 1 or 3 positional arguments '
1390-
f'but {nargs} were given')
1391-
1392-
if isinstance(args[0], SubplotBase):
1368+
if len(args) == 1 and isinstance(args[0], SubplotBase):
13931369
ax = args[0]
13941370
if ax.get_figure() is not self:
13951371
raise ValueError("The Subplot must have been created in "
13961372
"the present figure")
13971373
# make a key for the subplot (which includes the axes object id
13981374
# in the hash)
13991375
key = self._make_key(*args, **kwargs)
1376+
14001377
else:
1378+
if not args:
1379+
args = (1, 1, 1)
14011380
projection_class, kwargs, key = \
14021381
self._process_projection_requirements(*args, **kwargs)
1403-
1404-
# try to find the axes with this key in the stack
1405-
ax = self._axstack.get(key)
1406-
1382+
ax = self._axstack.get(key) # search axes with this key in stack
14071383
if ax is not None:
14081384
if isinstance(ax, projection_class):
14091385
# the axes already existed, so set it as active & return
@@ -1416,7 +1392,6 @@ def add_subplot(self, *args, **kwargs):
14161392
# Without this, add_subplot would be simpler and
14171393
# more similar to add_axes.
14181394
self._axstack.remove(ax)
1419-
14201395
ax = subplot_class_factory(projection_class)(self, *args, **kwargs)
14211396

14221397
return self._add_axes_internal(key, ax)

lib/matplotlib/gridspec.py

+29-15
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ def __init__(self, nrows, ncols, height_ratios=None, width_ratios=None):
4444
relative height of ``height_ratios[i] / sum(height_ratios)``.
4545
If not given, all rows will have the same height.
4646
"""
47+
if not isinstance(nrows, Integral) or nrows <= 0:
48+
raise ValueError(f"Number of rows must be > 0, not {nrows}")
49+
if not isinstance(ncols, Integral) or ncols <= 0:
50+
raise ValueError(f"Number of columns must be > 0, not {ncols}")
4751
self._nrows, self._ncols = nrows, ncols
4852
self.set_height_ratios(height_ratios)
4953
self.set_width_ratios(width_ratios)
@@ -642,36 +646,46 @@ def _from_subplot_args(figure, args):
642646
- a `.SubplotSpec` -- returned as is;
643647
- one or three numbers -- a MATLAB-style subplot specifier.
644648
"""
649+
message = ("Passing non-integers as three-element position "
650+
"specification is deprecated since %(since)s and will be "
651+
"removed %(removal)s.")
645652
if len(args) == 1:
646653
arg, = args
647654
if isinstance(arg, SubplotSpec):
648655
return arg
649656
else:
657+
if not isinstance(arg, Integral):
658+
cbook.warn_deprecated("3.3", message=message)
659+
arg = str(arg)
650660
try:
651-
s = str(int(arg))
652-
rows, cols, num = map(int, s)
653-
except ValueError as err:
654-
raise ValueError("Single argument to subplot must be a "
655-
"3-digit integer") from err
661+
rows, cols, num = map(int, str(arg))
662+
except ValueError:
663+
raise ValueError(
664+
f"Single argument to subplot must be a three-digit "
665+
f"integer, not {arg}") from None
656666
# num - 1 for converting from MATLAB to python indexing
657667
return GridSpec(rows, cols, figure=figure)[num - 1]
658668
elif len(args) == 3:
659669
rows, cols, num = args
660-
rows = int(rows)
661-
cols = int(cols)
662-
if rows <= 0:
663-
raise ValueError(f"Number of rows must be > 0, not {rows}")
664-
if cols <= 0:
665-
raise ValueError(f"Number of columns must be > 0, not {cols}")
670+
if not (isinstance(rows, Integral) and isinstance(cols, Integral)):
671+
cbook.warn_deprecated("3.3", message=message)
672+
rows, cols = map(int, [rows, cols])
673+
gs = GridSpec(rows, cols, figure=figure)
666674
if isinstance(num, tuple) and len(num) == 2:
667-
i, j = map(int, num)
668-
return GridSpec(rows, cols, figure=figure)[i-1:j]
675+
if not all(isinstance(n, Integral) for n in num):
676+
cbook.warn_deprecated("3.3", message=message)
677+
i, j = map(int, num)
678+
else:
679+
i, j = num
680+
return gs[i-1:j]
669681
else:
682+
if not isinstance(num, Integral):
683+
cbook.warn_deprecated("3.3", message=message)
684+
num = int(num)
670685
if num < 1 or num > rows*cols:
671686
raise ValueError(
672687
f"num must be 1 <= num <= {rows*cols}, not {num}")
673-
# num - 1 for converting from MATLAB to python indexing
674-
return GridSpec(rows, cols, figure=figure)[int(num) - 1]
688+
return gs[num - 1] # -1 due to MATLAB indexing.
675689
else:
676690
raise TypeError(f"subplot() takes 1 or 3 positional arguments but "
677691
f"{len(args)} were given")

lib/matplotlib/tests/test_figure.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -189,9 +189,9 @@ def test_add_subplot_invalid():
189189
with pytest.raises(ValueError, match='num must be 1 <= num <= 4'):
190190
fig.add_subplot(2, 2, 5)
191191

192-
with pytest.raises(ValueError, match='must be a three-digit number'):
192+
with pytest.raises(ValueError, match='must be a three-digit integer'):
193193
fig.add_subplot(42)
194-
with pytest.raises(ValueError, match='must be a three-digit number'):
194+
with pytest.raises(ValueError, match='must be a three-digit integer'):
195195
fig.add_subplot(1000)
196196

197197
with pytest.raises(TypeError, match='takes 1 or 3 positional arguments '
@@ -533,3 +533,16 @@ def test_removed_axis():
533533
fig, axs = plt.subplots(2, sharex=True)
534534
axs[0].remove()
535535
fig.canvas.draw()
536+
537+
538+
def test_add_subplot_twotuple():
539+
fig = plt.figure()
540+
ax2 = fig.add_subplot(3, 2, (3, 5))
541+
assert ax2.get_subplotspec().rowspan == range(1, 3)
542+
assert ax2.get_subplotspec().colspan == range(0, 1)
543+
ax3 = fig.add_subplot(3, 2, (4, 6))
544+
assert ax3.get_subplotspec().rowspan == range(1, 3)
545+
assert ax3.get_subplotspec().colspan == range(1, 2)
546+
ax4 = fig.add_subplot(3, 2, (3, 6))
547+
assert ax4.get_subplotspec().rowspan == range(1, 3)
548+
assert ax4.get_subplotspec().colspan == range(0, 2)

0 commit comments

Comments
 (0)