Skip to content

Commit 328e336

Browse files
committed
Validate positional parameters of add_subplot()
1 parent 3a8e75e commit 328e336

File tree

4 files changed

+68
-31
lines changed

4 files changed

+68
-31
lines changed

doc/api/next_api_changes/behaviour.rst

+6
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,9 @@ Previously, `.SymLogNorm` had no *base* kwarg, and defaulted to ``base=np.e``
9393
whereas the documentation said it was ``base=10``. In preparation to make
9494
the default 10, calling `.SymLogNorm` without the new *base* kwarg emits a
9595
deprecation warning.
96+
97+
``add_subplot()`` validates its inputs
98+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
99+
In particular, for ``add_subplot(rows, cols, index)``, all parameters must
100+
no be Integral. Previously strings and floats were accepted and converted to
101+
int. This will now raise an error.

lib/matplotlib/figure.py

+41-26
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030
from matplotlib.axes import Axes, SubplotBase, subplot_class_factory
3131
from matplotlib.blocking_input import BlockingMouseInput, BlockingKeyMouseInput
32-
from matplotlib.gridspec import GridSpec
32+
from matplotlib.gridspec import GridSpec, SubplotSpec
3333
import matplotlib.legend as mlegend
3434
from matplotlib.patches import Rectangle
3535
from matplotlib.text import Text
@@ -1259,19 +1259,19 @@ def add_subplot(self, *args, **kwargs):
12591259
12601260
Parameters
12611261
----------
1262-
*args, default: (1, 1, 1)
1263-
Either a 3-digit integer or three separate integers
1264-
describing the position of the subplot. If the three
1265-
integers are *nrows*, *ncols*, and *index* in order, the
1266-
subplot will take the *index* position on a grid with *nrows*
1267-
rows and *ncols* columns. *index* starts at 1 in the upper left
1268-
corner and increases to the right.
1269-
1270-
*pos* is a three digit integer, where the first digit is the
1271-
number of rows, the second the number of columns, and the third
1272-
the index of the subplot. i.e. fig.add_subplot(235) is the same as
1273-
fig.add_subplot(2, 3, 5). Note that all integers must be less than
1274-
10 for this form to work.
1262+
*args, int or (int, int, int) or `SubplotSpec`, default: (1, 1, 1)
1263+
The position of the subplot described by one of
1264+
1265+
- Three integers (*nrows*, *ncols*, *index*). The subplot will
1266+
take the *index* position on a grid with *nrows* rows and
1267+
*ncols* columns. *index* starts at 1 in the upper left corner
1268+
and increases to the right.
1269+
- A 3-digit integer. The digits are interpreted as if given
1270+
separately as three single-digit integers, i.e.
1271+
``fig.add_subplot(235)`` is the same as
1272+
``fig.add_subplot(2, 3, 5)``. Note that this can only be used
1273+
if there are no more than 9 subplots.
1274+
- A `.SubplotSpec`.
12751275
12761276
In rare circumstances, `.add_subplot` may be called with a single
12771277
argument, a subplot axes instance already created in the
@@ -1351,27 +1351,42 @@ def add_subplot(self, *args, **kwargs):
13511351
ax1.remove() # delete ax1 from the figure
13521352
fig.add_subplot(ax1) # add ax1 back to the figure
13531353
"""
1354-
if not len(args):
1355-
args = (1, 1, 1)
1356-
1357-
if len(args) == 1 and isinstance(args[0], Integral):
1358-
if not 100 <= args[0] <= 999:
1359-
raise ValueError("Integer subplot specification must be a "
1360-
"three-digit number, not {}".format(args[0]))
1361-
args = tuple(map(int, str(args[0])))
1362-
13631354
if 'figure' in kwargs:
13641355
# Axes itself allows for a 'figure' kwarg, but since we want to
13651356
# bind the created Axes to self, it is not allowed here.
13661357
raise TypeError(
13671358
"add_subplot() got an unexpected keyword argument 'figure'")
13681359

1369-
if isinstance(args[0], SubplotBase):
1360+
nargs = len(args)
1361+
if nargs == 0:
1362+
args = (1, 1, 1)
1363+
elif nargs == 1:
1364+
print(type(args[0]))
1365+
if isinstance(args[0], Integral):
1366+
if not 100 <= args[0] <= 999:
1367+
raise ValueError(f"Integer subplot specification must be "
1368+
f"a three-digit number, not {args[0]}")
1369+
args = tuple(map(int, str(args[0])))
1370+
elif isinstance(args[0], (SubplotBase, SubplotSpec)):
1371+
pass # no further validation or normalization needed
1372+
else:
1373+
raise TypeError('Positional arguments are not a valid '
1374+
'position specification.')
1375+
elif nargs == 3:
1376+
for arg in args:
1377+
if not isinstance(arg, Integral):
1378+
raise ValueError("Three-element position specification "
1379+
"must be all integers.")
1380+
args = tuple(map(int, args))
1381+
else:
1382+
raise TypeError('Positional arguments are not a valid position '
1383+
'specification.')
13701384

1385+
if isinstance(args[0], SubplotBase):
13711386
ax = args[0]
13721387
if ax.get_figure() is not self:
1373-
raise ValueError(
1374-
"The Subplot must have been created in the present figure")
1388+
raise ValueError("The Subplot must have been created in "
1389+
"the present figure")
13751390
# make a key for the subplot (which includes the axes object id
13761391
# in the hash)
13771392
key = self._make_key(*args, **kwargs)

lib/matplotlib/tests/test_axes.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4037,7 +4037,7 @@ def test_mixed_collection():
40374037

40384038

40394039
def test_subplot_key_hash():
4040-
ax = plt.subplot(np.float64(5.5), np.int64(1), np.float64(1.2))
4040+
ax = plt.subplot(np.int32(5), np.int64(1), 1)
40414041
ax.twinx()
40424042
assert ax.get_subplotspec().get_geometry() == (5, 1, 0, 0)
40434043

lib/matplotlib/tests/test_figure.py

+20-4
Original file line numberDiff line numberDiff line change
@@ -172,15 +172,31 @@ def test_gca():
172172

173173
def test_add_subplot_invalid():
174174
fig = plt.figure()
175-
with pytest.raises(ValueError):
175+
with pytest.raises(ValueError, match='Number of columns must be > 0'):
176176
fig.add_subplot(2, 0, 1)
177-
with pytest.raises(ValueError):
177+
with pytest.raises(ValueError, match='Number of rows must be > 0'):
178178
fig.add_subplot(0, 2, 1)
179-
with pytest.raises(ValueError):
179+
with pytest.raises(ValueError, match='num must be 1 <= num <= 4'):
180180
fig.add_subplot(2, 2, 0)
181-
with pytest.raises(ValueError):
181+
with pytest.raises(ValueError, match='num must be 1 <= num <= 4'):
182182
fig.add_subplot(2, 2, 5)
183183

184+
with pytest.raises(ValueError, match='must be a three-digit number'):
185+
fig.add_subplot(42)
186+
with pytest.raises(ValueError, match='must be a three-digit number'):
187+
fig.add_subplot(1000)
188+
189+
with pytest.raises(TypeError, match='not a valid position specification'):
190+
fig.add_subplot(2, 2)
191+
with pytest.raises(TypeError, match='not a valid position specification'):
192+
fig.add_subplot(1, 2, 3, 4)
193+
with pytest.raises(ValueError, match='Three-element position '
194+
'specification must be all integers'):
195+
fig.add_subplot('2', 2, 1)
196+
with pytest.raises(ValueError, match='Three-element position '
197+
'specification must be all integers'):
198+
fig.add_subplot(2.0, 2, 1)
199+
184200

185201
@image_comparison(['figure_suptitle'])
186202
def test_suptitle():

0 commit comments

Comments
 (0)