Skip to content

FIX: allow start-stop subplot #17344

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 6 additions & 28 deletions lib/matplotlib/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -1264,13 +1264,16 @@ def add_subplot(self, *args, **kwargs):

Parameters
----------
*args, int or (int, int, int) or `SubplotSpec`, default: (1, 1, 1)
*args, int, (int, int, *index*), or `SubplotSpec`, default: (1, 1, 1)
The position of the subplot described by one of

- Three integers (*nrows*, *ncols*, *index*). The subplot will
take the *index* position on a grid with *nrows* rows and
*ncols* columns. *index* starts at 1 in the upper left corner
and increases to the right.
and increases to the right. *index* can also be a two-tuple
specifying the (*start*, *stop*) indices of the subplot, e.g.,
``fig.add_subplot(3, 1, (1, 2))`` makes a subplot that spans the
upper 2/3 of the figure.
- A 3-digit integer. The digits are interpreted as if given
separately as three single-digit integers, i.e.
``fig.add_subplot(235)`` is the same as
Expand Down Expand Up @@ -1362,32 +1365,7 @@ def add_subplot(self, *args, **kwargs):
raise TypeError(
"add_subplot() got an unexpected keyword argument 'figure'")

nargs = len(args)
if nargs == 0:
args = (1, 1, 1)
elif nargs == 1:
if isinstance(args[0], Integral):
if not 100 <= args[0] <= 999:
raise ValueError(f"Integer subplot specification must be "
f"a three-digit number, not {args[0]}")
args = tuple(map(int, str(args[0])))
elif isinstance(args[0], (SubplotBase, SubplotSpec)):
pass # no further validation or normalization needed
else:
raise TypeError('Positional arguments are not a valid '
'position specification.')
elif nargs == 3:
for arg in args:
if not isinstance(arg, Integral):
cbook.warn_deprecated(
"3.3",
message="Passing non-integers as three-element "
"position specification is deprecated since "
"%(since)s and will be removed %(removal)s.")
args = tuple(map(int, args))
else:
raise TypeError(f'add_subplot() takes 1 or 3 positional arguments '
f'but {nargs} were given')
args = SubplotSpec._check_subplots_args(args)

if isinstance(args[0], SubplotBase):
ax = args[0]
Expand Down
90 changes: 63 additions & 27 deletions lib/matplotlib/gridspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import copy
import logging
from numbers import Integral

import numpy as np

Expand Down Expand Up @@ -516,6 +517,60 @@ def __repr__(self):
f"{self.rowspan.start}:{self.rowspan.stop}, "
f"{self.colspan.start}:{self.colspan.stop}]")

@staticmethod
def _check_subplots_args(args):
print('args', args, type(args))
nargs = len(args)
if nargs == 0:
args = (1, 1, 1)
elif nargs == 1:
if isinstance(args[0], Integral):
if not 100 <= args[0] <= 999:
raise ValueError(f"Integer subplot specification must be "
f"a three-digit number, not {args[0]}")
args = tuple(map(int, str(args[0])))
elif isinstance(args[0], (mpl.axes.SubplotBase, SubplotSpec)):
return args
else:
raise ValueError("Single argument to subplot must be a "
"3-digit integer or a SubplotSpec")
elif nargs == 3:
message = ("Passing non-integers as three-element "
"position specification is deprecated since "
"%(since)s and will be removed %(removal)s.")
rows, cols, nums = args
if not isinstance(rows, Integral):
cbook.warn_deprecated("3.3", message=message)
rows = int(rows)
if rows <= 0:
raise ValueError(f"Number of rows must be > 0, not {rows}")

if not isinstance(cols, Integral):
cbook.warn_deprecated("3.3", message=message)
cols = int(cols)
if cols <= 0:
raise ValueError(f"Number of columns must be > 0, not {cols}")

if isinstance(nums, tuple) and len(nums) == 2:
# start/stop two-tuple is allowed...
for arg in nums:
if not isinstance(arg, Integral):
cbook.warn_deprecated("3.3", message=message)
nums = (int(nums[0]), int(nums[1]))
else:
if not isinstance(nums, Integral):
cbook.warn_deprecated("3.3", message=message)
nums = int(nums)
if nums < 1 or nums > rows*cols:
raise ValueError(
f"num must be 1 <= num <= {rows*cols}, not {nums}")
args = (rows, cols, nums)
else:
raise TypeError(f'subplot() takes 1 or 3 positional arguments '
f'but {nargs} were given')

return args

@staticmethod
def _from_subplot_args(figure, args):
"""
Expand All @@ -524,39 +579,20 @@ def _from_subplot_args(figure, args):
- a `.SubplotSpec` -- returned as is;
- one or three numbers -- a MATLAB-style subplot specifier.
"""
args = SubplotSpec._check_subplots_args(args)
if len(args) == 1:
arg, = args
if isinstance(arg, SubplotSpec):
return arg
else:
try:
s = str(int(arg))
rows, cols, num = map(int, s)
except ValueError as err:
raise ValueError("Single argument to subplot must be a "
"3-digit integer") from err
# num - 1 for converting from MATLAB to python indexing
return GridSpec(rows, cols, figure=figure)[num - 1]
# args is a subplotspec
return args[0]
elif len(args) == 3:
rows, cols, num = args
rows = int(rows)
cols = int(cols)
if rows <= 0:
raise ValueError(f"Number of rows must be > 0, not {rows}")
if cols <= 0:
raise ValueError(f"Number of columns must be > 0, not {cols}")
if isinstance(num, tuple) and len(num) == 2:
i, j = map(int, num)
return GridSpec(rows, cols, figure=figure)[i-1:j]
start, stop = num
else:
if num < 1 or num > rows*cols:
raise ValueError(
f"num must be 1 <= num <= {rows*cols}, not {num}")
# num - 1 for converting from MATLAB to python indexing
return GridSpec(rows, cols, figure=figure)[int(num) - 1]
else:
raise TypeError(f"subplot() takes 1 or 3 positional arguments but "
f"{len(args)} were given")
start = num
stop = num
# start - 1 for converting from MATLAB to python indexing
return GridSpec(rows, cols, figure=figure)[start - 1:stop]

# num2 is a property only to handle the case where it is None and someone
# mutates num1.
Expand Down
13 changes: 13 additions & 0 deletions lib/matplotlib/tests/test_figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,3 +533,16 @@ def test_removed_axis():
fig, axs = plt.subplots(2, sharex=True)
axs[0].remove()
fig.canvas.draw()


def test_add_subplot_twotuple():
fig = plt.figure()
ax2 = fig.add_subplot(3, 2, (3, 5))
assert ax2.get_subplotspec().rowspan == range(1, 3)
assert ax2.get_subplotspec().colspan == range(0, 1)
ax3 = fig.add_subplot(3, 2, (4, 6))
assert ax3.get_subplotspec().rowspan == range(1, 3)
assert ax3.get_subplotspec().colspan == range(1, 2)
ax4 = fig.add_subplot(3, 2, (3, 6))
assert ax4.get_subplotspec().rowspan == range(1, 3)
assert ax4.get_subplotspec().colspan == range(0, 2)