-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
FIX: restore creating new axes via plt.subplot with different kwargs #19438
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
``plt.subplot`` re-selection without keyword arguments | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
The purpose of `.pyplot.subplot` is to facilitate creating and re-selecting | ||
Axes in a Figure when working strictly in the implicit pyplot API. When | ||
creating new Axes it is possible to select the projection (e.g. polar, 3D, or | ||
various cartographic projections) as well as to pass additional keyword | ||
arguments through to the Axes-subclass that is created. | ||
|
||
The first time `.pyplot.subplot` is called for a given position in the Axes | ||
grid it always creates and return a new Axes with the passed arguments and | ||
projection (defaulting to a rectilinear). On subsequent calls to | ||
`.pyplot.subplot` we have to determine if an existing Axes has equivalent | ||
parameters, in which case in should be selected as the current Axes and | ||
returned, or different parameters, in which case a new Axes is created and the | ||
existing Axes is removed. This leaves the question of what is "equivalent | ||
parameters". | ||
|
||
Previously it was the case that an existing Axes subclass, except for Axes3D, | ||
would be considered equivalent to a 2D rectilinear Axes, despite having | ||
different projections, if the kwargs (other than *projection*) matched. Thus | ||
:: | ||
|
||
ax1 = plt.subplot(1, 1, 1, projection='polar') | ||
ax2 = plt.subplots(1, 1, 1) | ||
ax1 is ax2 | ||
|
||
We are embracing this long standing behavior to ensure that in the case when no | ||
keyword arguments (of any sort) are passed to `.pyplot.subplot` any existing | ||
Axes is returned, without consideration for keywords or projection used to | ||
initially create it. This will cause a change in behavior when additional | ||
keywords were passed to the original axes :: | ||
|
||
ax1 = plt.subplot(111, projection='polar', theta_offset=.75) | ||
ax2 = plt.subplots(1, 1, 1) | ||
ax1 is ax2 # new behavior | ||
# ax1 is not ax2 # old behavior, made a new axes | ||
|
||
ax1 = plt.subplot(111, label='test') | ||
ax2 = plt.subplots(1, 1, 1) | ||
ax1 is ax2 # new behavior | ||
# ax1 is not ax2 # old behavior, made a new axes | ||
|
||
|
||
For the same reason, if there was an existing Axes that was not rectilinear, | ||
passing ``projection='rectilinear'`` would reuse the existing Axes :: | ||
|
||
ax1 = plt.subplot(projection='polar') | ||
ax2 = plt.subplot(projection='rectilinear') | ||
ax1 is not ax2 # new behavior, makes new axes | ||
# ax1 is ax2 # old behavior | ||
|
||
|
||
contrary to the users request. | ||
|
||
Previously Axes3D could not be re-selected with `.pyplot.subplot` due to an | ||
unrelated bug (also fixed in mpl3.4). While Axes3D are now consistent with all | ||
other projections there is a change in behavior for :: | ||
|
||
plt.subplot(projection='3d') # create a 3D Axes | ||
|
||
plt.subplot() # now returns existing 3D Axes, but | ||
# previously created new 2D Axes | ||
|
||
plt.subplot(projection='rectilinear') # to get a new 2D Axes |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -1072,10 +1072,10 @@ def cla(): | |||||
@docstring.dedent_interpd | ||||||
def subplot(*args, **kwargs): | ||||||
""" | ||||||
Add a subplot to the current figure. | ||||||
Add an Axes to the current figure or retrieve an existing Axes. | ||||||
|
||||||
Wrapper of `.Figure.add_subplot` with a difference in | ||||||
behavior explained in the notes section. | ||||||
This is a wrapper of `.Figure.add_subplot` which provides additional | ||||||
behavior when working with the implicit API (see the notes section). | ||||||
|
||||||
Call signatures:: | ||||||
|
||||||
|
@@ -1142,8 +1142,8 @@ def subplot(*args, **kwargs): | |||||
|
||||||
Notes | ||||||
----- | ||||||
Creating a subplot will delete any pre-existing subplot that overlaps | ||||||
with it beyond sharing a boundary:: | ||||||
Creating a new Axes will delete any pre-existing Axes that | ||||||
overlaps with it beyond sharing a boundary:: | ||||||
|
||||||
import matplotlib.pyplot as plt | ||||||
# plot a line, implicitly creating a subplot(111) | ||||||
|
@@ -1156,18 +1156,19 @@ def subplot(*args, **kwargs): | |||||
If you do not want this behavior, use the `.Figure.add_subplot` method | ||||||
or the `.pyplot.axes` function instead. | ||||||
|
||||||
If the figure already has a subplot with key (*args*, | ||||||
*kwargs*) then it will simply make that subplot current and | ||||||
return it. This behavior is deprecated. Meanwhile, if you do | ||||||
not want this behavior (i.e., you want to force the creation of a | ||||||
new subplot), you must use a unique set of args and kwargs. The axes | ||||||
*label* attribute has been exposed for this purpose: if you want | ||||||
two subplots that are otherwise identical to be added to the figure, | ||||||
make sure you give them unique labels. | ||||||
If no *kwargs* are passed and there exists an Axes in the location | ||||||
specified by *args* then that Axes will be returned rather than a new | ||||||
Axes being created. | ||||||
|
||||||
In rare circumstances, `.Figure.add_subplot` may be called with a single | ||||||
argument, a subplot axes instance already created in the | ||||||
present figure but not in the figure's list of axes. | ||||||
If *kwargs* are passed and there exists an Axes in the location | ||||||
specified by *args*, the projection type is the same, and the | ||||||
*kwargs* match with the existing Axes, then the existing Axes is | ||||||
returned. Otherwise a new Axes is created with the specified | ||||||
parameters. We save a reference to the *kwargs* which we us | ||||||
QuLogic marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
for this comparison. If any of the values in *kwargs* are | ||||||
mutable we will not detect the case where they are mutated. | ||||||
In these cases we suggest using `.Figure.add_subplot` and the | ||||||
explicit Axes API rather than the implicit pyplot API. | ||||||
|
||||||
See Also | ||||||
-------- | ||||||
|
@@ -1183,10 +1184,10 @@ def subplot(*args, **kwargs): | |||||
plt.subplot(221) | ||||||
|
||||||
# equivalent but more general | ||||||
ax1=plt.subplot(2, 2, 1) | ||||||
ax1 = plt.subplot(2, 2, 1) | ||||||
|
||||||
# add a subplot with no frame | ||||||
ax2=plt.subplot(222, frameon=False) | ||||||
ax2 = plt.subplot(222, frameon=False) | ||||||
|
||||||
# add a polar subplot | ||||||
plt.subplot(223, projection='polar') | ||||||
|
@@ -1199,18 +1200,34 @@ def subplot(*args, **kwargs): | |||||
|
||||||
# add ax2 to the figure again | ||||||
plt.subplot(ax2) | ||||||
|
||||||
# make the first axes "current" again | ||||||
plt.subplot(221) | ||||||
|
||||||
""" | ||||||
# Here we will only normalize `polar=True` vs `projection='polar'` and let | ||||||
# downstream code deal with the rest. | ||||||
unset = object() | ||||||
projection = kwargs.get('projection', unset) | ||||||
polar = kwargs.pop('polar', unset) | ||||||
if polar is not unset and polar: | ||||||
# if we got mixed messages from the user, raise | ||||||
if projection is not unset and projection != 'polar': | ||||||
raise ValueError( | ||||||
f"polar={polar}, yet projection={projection!r}. " | ||||||
"Only one of these arguments should be supplied." | ||||||
) | ||||||
kwargs['projection'] = projection = 'polar' | ||||||
|
||||||
# if subplot called without arguments, create subplot(1, 1, 1) | ||||||
if len(args) == 0: | ||||||
args = (1, 1, 1) | ||||||
|
||||||
# This check was added because it is very easy to type | ||||||
# subplot(1, 2, False) when subplots(1, 2, False) was intended | ||||||
# (sharex=False, that is). In most cases, no error will | ||||||
# ever occur, but mysterious behavior can result because what was | ||||||
# intended to be the sharex argument is instead treated as a | ||||||
# subplot index for subplot() | ||||||
# This check was added because it is very easy to type subplot(1, 2, False) | ||||||
# when subplots(1, 2, False) was intended (sharex=False, that is). In most | ||||||
# cases, no error will ever occur, but mysterious behavior can result | ||||||
# because what was intended to be the sharex argument is instead treated as | ||||||
# a subplot index for subplot() | ||||||
if len(args) >= 3 and isinstance(args[2], bool): | ||||||
_api.warn_external("The subplot index argument to subplot() appears " | ||||||
"to be a boolean. Did you intend to use " | ||||||
|
@@ -1224,15 +1241,24 @@ def subplot(*args, **kwargs): | |||||
|
||||||
# First, search for an existing subplot with a matching spec. | ||||||
key = SubplotSpec._from_subplot_args(fig, args) | ||||||
ax = next( | ||||||
(ax for ax in fig.axes | ||||||
if hasattr(ax, 'get_subplotspec') and ax.get_subplotspec() == key), | ||||||
None) | ||||||
|
||||||
# If no existing axes match, then create a new one. | ||||||
if ax is None: | ||||||
for ax in fig.axes: | ||||||
# if we found an axes at the position sort out if we can re-use it | ||||||
if hasattr(ax, 'get_subplotspec') and ax.get_subplotspec() == key: | ||||||
# if the user passed no kwargs, re-use | ||||||
if kwargs == {}: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
but I know it's just a matter of style. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can see arguments either way here. I want to say "If kwargs is equal to the empty dictionary" which is equivalent to "kwargs is falsy", but I think the |
||||||
break | ||||||
# if the axes class and kwargs are identical, reuse | ||||||
elif ax._projection_init == fig._process_projection_requirements( | ||||||
*args, **kwargs | ||||||
): | ||||||
break | ||||||
else: | ||||||
# we have exhausted the known Axes and none match, make a new one! | ||||||
ax = fig.add_subplot(*args, **kwargs) | ||||||
|
||||||
fig.sca(ax) | ||||||
tacaswell marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
bbox = ax.bbox | ||||||
axes_to_delete = [] | ||||||
for other_ax in fig.axes: | ||||||
|
Uh oh!
There was an error while loading. Please reload this page.