From e47efd18710bd34fe762812061b360d9098a684b Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 2 Feb 2021 20:09:29 -0500 Subject: [PATCH 1/3] FIX: set sca when re-using as Axes with pyplot.subplot --- lib/matplotlib/pyplot.py | 1 + lib/matplotlib/tests/test_pyplot.py | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 534d0f1998bd..954952c6ede5 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1232,6 +1232,7 @@ def subplot(*args, **kwargs): # If no existing axes match, then create a new one. if ax is None: ax = fig.add_subplot(*args, **kwargs) + fig.sca(ax) bbox = ax.bbox axes_to_delete = [] diff --git a/lib/matplotlib/tests/test_pyplot.py b/lib/matplotlib/tests/test_pyplot.py index b60b86ec6575..9426a9234670 100644 --- a/lib/matplotlib/tests/test_pyplot.py +++ b/lib/matplotlib/tests/test_pyplot.py @@ -161,3 +161,12 @@ def test_close(): except TypeError as e: assert str(e) == "close() argument must be a Figure, an int, " \ "a string, or None, not " + + +def test_subplot_reuse(): + ax1 = plt.subplot(121) + assert ax1 is plt.gca() + ax2 = plt.subplot(122) + assert ax2 is plt.gca() + ax1 = plt.subplot(121) + assert ax1 is plt.gca() From d854e5af38b7002045449ca1b48dd3d0698a73f3 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Tue, 2 Feb 2021 13:01:25 -0500 Subject: [PATCH 2/3] FIX: restore creating new axes via plt.subplot with different kwargs This adds a small amount of additional state to the Axes created via Figure.add_axes and Figure.add_subplot (which the other Axes creation methods eventually funnel through) to track the projection class and (processed) kwargs. We then use that state in `pyplot.subplot` to determine if we should re-use an Axes found at a given position or create a new one (and implicitly destroy the existing one). This also changes the behavior of `plt.subplot` when no kwargs are passed to return the Axes at the location without doing any matching of the kwargs. As part of this work we also fixed two additional bugs: - you can now force Axes "back to" rectilinear Axes by passing projection='rectilinear' - Axes3D instances can now be re-selected at all closes #19432 closes #10700 Co-authored-by: Elliott Sales de Andrade --- .../next_api_changes/behavior/19438-TAC.rst | 65 ++++++++ .../next_whats_new/axes_kwargs_collision.rst | 28 ++-- lib/matplotlib/axes/_base.py | 1 + lib/matplotlib/figure.py | 20 ++- lib/matplotlib/pyplot.py | 85 +++++++---- lib/matplotlib/tests/test_figure.py | 94 ++---------- lib/matplotlib/tests/test_pyplot.py | 142 +++++++++++++++++- 7 files changed, 302 insertions(+), 133 deletions(-) create mode 100644 doc/api/next_api_changes/behavior/19438-TAC.rst diff --git a/doc/api/next_api_changes/behavior/19438-TAC.rst b/doc/api/next_api_changes/behavior/19438-TAC.rst new file mode 100644 index 000000000000..c491c3a30fbd --- /dev/null +++ b/doc/api/next_api_changes/behavior/19438-TAC.rst @@ -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 diff --git a/doc/users/next_whats_new/axes_kwargs_collision.rst b/doc/users/next_whats_new/axes_kwargs_collision.rst index 35d425a87dcb..350e75f800b5 100644 --- a/doc/users/next_whats_new/axes_kwargs_collision.rst +++ b/doc/users/next_whats_new/axes_kwargs_collision.rst @@ -3,19 +3,19 @@ Changes to behavior of Axes creation methods (``gca()``, ``add_axes()``, ``add_s The behavior of the functions to create new axes (`.pyplot.axes`, `.pyplot.subplot`, `.figure.Figure.add_axes`, -`.figure.Figure.add_subplot`) has changed. In the past, these functions would -detect if you were attempting to create Axes with the same keyword arguments as -already-existing axes in the current figure, and if so, they would return the -existing Axes. Now, these functions will always create new Axes. A special -exception is `.pyplot.subplot`, which will reuse any existing subplot with a -matching subplot spec. However, if there is a subplot with a matching subplot -spec, then that subplot will be returned, even if the keyword arguments with -which it was created differ. +`.figure.Figure.add_subplot`) has changed. In the past, these +functions would detect if you were attempting to create Axes with the +same keyword arguments as already-existing axes in the current figure, +and if so, they would return the existing Axes. Now, `.pyplot.axes`, +`.figure.Figure.add_axes`, and `.figure.Figure.add_subplot` will +always create new Axes. `.pyplot.subplot` will continue to reuse an +existing Axes with a matching subplot spec and equal *kwargs*. Correspondingly, the behavior of the functions to get the current Axes -(`.pyplot.gca`, `.figure.Figure.gca`) has changed. In the past, these functions -accepted keyword arguments. If the keyword arguments matched an -already-existing Axes, then that Axes would be returned, otherwise new Axes -would be created with those keyword arguments. Now, the keyword arguments are -only considered if there are no axes at all in the current figure. In a future -release, these functions will not accept keyword arguments at all. +(`.pyplot.gca`, `.figure.Figure.gca`) has changed. In the past, these +functions accepted keyword arguments. If the keyword arguments +matched an already-existing Axes, then that Axes would be returned, +otherwise new Axes would be created with those keyword arguments. +Now, the keyword arguments are only considered if there are no axes at +all in the current figure. In a future release, these functions will +not accept keyword arguments at all. diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 84647e459896..728d22cd7d08 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1237,6 +1237,7 @@ def cla(self): self._mouseover_set = _OrderedSet() self.child_axes = [] self._current_image = None # strictly for pyplot via _sci, _gci + self._projection_init = None # strictly for pyplot.subplot self.legend_ = None self.collections = [] # collection.Collection instances self.containers = [] diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 620d936e8e51..4c826a30df7e 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -567,6 +567,7 @@ def add_axes(self, *args, **kwargs): if isinstance(args[0], Axes): a = args[0] + key = a._projection_init if a.get_figure() is not self: raise ValueError( "The Axes must have been created in the present figure") @@ -575,12 +576,13 @@ def add_axes(self, *args, **kwargs): if not np.isfinite(rect).all(): raise ValueError('all entries in rect must be finite ' 'not {}'.format(rect)) - projection_class, kwargs = self._process_projection_requirements( + projection_class, pkw = self._process_projection_requirements( *args, **kwargs) # create the new axes using the axes class given - a = projection_class(self, rect, **kwargs) - return self._add_axes_internal(a) + a = projection_class(self, rect, **pkw) + key = (projection_class, pkw) + return self._add_axes_internal(a, key) @docstring.dedent_interpd def add_subplot(self, *args, **kwargs): @@ -693,6 +695,7 @@ def add_subplot(self, *args, **kwargs): if len(args) == 1 and isinstance(args[0], SubplotBase): ax = args[0] + key = ax._projection_init if ax.get_figure() is not self: raise ValueError("The Subplot must have been created in " "the present figure") @@ -705,17 +708,20 @@ def add_subplot(self, *args, **kwargs): if (len(args) == 1 and isinstance(args[0], Integral) and 100 <= args[0] <= 999): args = tuple(map(int, str(args[0]))) - projection_class, kwargs = self._process_projection_requirements( + projection_class, pkw = self._process_projection_requirements( *args, **kwargs) - ax = subplot_class_factory(projection_class)(self, *args, **kwargs) - return self._add_axes_internal(ax) + ax = subplot_class_factory(projection_class)(self, *args, **pkw) + key = (projection_class, pkw) + return self._add_axes_internal(ax, key) - def _add_axes_internal(self, ax): + def _add_axes_internal(self, ax, key): """Private helper for `add_axes` and `add_subplot`.""" self._axstack.push(ax) self._localaxes.push(ax) self.sca(ax) ax._remove_method = self.delaxes + # this is to support plt.subplot's re-selection logic + ax._projection_init = key self.stale = True ax.stale_callback = _stale_figure_callback return ax diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 954952c6ede5..f033619182e6 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -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 + 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,14 +1241,22 @@ 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 == {}: + 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) bbox = ax.bbox diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index b5748491bdcf..b3835ad79759 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -939,30 +939,32 @@ def test_subfigure_double(): axsRight = subfigs[1].subplots(2, 2) -def test_axes_kwargs(): - # plt.axes() always creates new axes, even if axes kwargs differ. - plt.figure() - ax = plt.axes() - ax1 = plt.axes() +def test_add_subplot_kwargs(): + # fig.add_subplot() always creates new axes, even if axes kwargs differ. + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1) + ax1 = fig.add_subplot(1, 1, 1) assert ax is not None assert ax1 is not ax plt.close() - plt.figure() - ax = plt.axes(projection='polar') - ax1 = plt.axes(projection='polar') + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1, projection='polar') + ax1 = fig.add_subplot(1, 1, 1, projection='polar') assert ax is not None assert ax1 is not ax plt.close() - plt.figure() - ax = plt.axes(projection='polar') - ax1 = plt.axes() + fig = plt.figure() + ax = fig.add_subplot(1, 1, 1, projection='polar') + ax1 = fig.add_subplot(1, 1, 1) assert ax is not None assert ax1.name == 'rectilinear' assert ax1 is not ax plt.close() + +def test_add_axes_kwargs(): # fig.add_axes() always creates new axes, even if axes kwargs differ. fig = plt.figure() ax = fig.add_axes([0, 0, 1, 1]) @@ -985,73 +987,3 @@ def test_axes_kwargs(): assert ax1.name == 'rectilinear' assert ax1 is not ax plt.close() - - # fig.add_subplot() always creates new axes, even if axes kwargs differ. - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1) - ax1 = fig.add_subplot(1, 1, 1) - assert ax is not None - assert ax1 is not ax - plt.close() - - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1, projection='polar') - ax1 = fig.add_subplot(1, 1, 1, projection='polar') - assert ax is not None - assert ax1 is not ax - plt.close() - - fig = plt.figure() - ax = fig.add_subplot(1, 1, 1, projection='polar') - ax1 = fig.add_subplot(1, 1, 1) - assert ax is not None - assert ax1.name == 'rectilinear' - assert ax1 is not ax - plt.close() - - # plt.subplot() searches for axes with the same subplot spec, and if one - # exists, returns it, regardless of whether the axes kwargs were the same. - fig = plt.figure() - ax = plt.subplot(1, 2, 1) - ax1 = plt.subplot(1, 2, 1) - ax2 = plt.subplot(1, 2, 2) - ax3 = plt.subplot(1, 2, 1, projection='polar') - assert ax is not None - assert ax1 is ax - assert ax2 is not ax - assert ax3 is ax - assert ax.name == 'rectilinear' - assert ax3.name == 'rectilinear' - plt.close() - - # plt.gca() returns an existing axes, unless there were no axes. - plt.figure() - ax = plt.gca() - ax1 = plt.gca() - assert ax is not None - assert ax1 is ax - plt.close() - - # plt.gca() raises a DeprecationWarning if called with kwargs. - plt.figure() - with pytest.warns( - MatplotlibDeprecationWarning, - match=r'Calling gca\(\) with keyword arguments was deprecated'): - ax = plt.gca(projection='polar') - ax1 = plt.gca() - assert ax is not None - assert ax1 is ax - assert ax1.name == 'polar' - plt.close() - - # plt.gca() ignores keyword arguments if an axes already exists. - plt.figure() - ax = plt.gca() - with pytest.warns( - MatplotlibDeprecationWarning, - match=r'Calling gca\(\) with keyword arguments was deprecated'): - ax1 = plt.gca(projection='polar') - assert ax is not None - assert ax1 is ax - assert ax1.name == 'rectilinear' - plt.close() diff --git a/lib/matplotlib/tests/test_pyplot.py b/lib/matplotlib/tests/test_pyplot.py index 9426a9234670..c2c71d586715 100644 --- a/lib/matplotlib/tests/test_pyplot.py +++ b/lib/matplotlib/tests/test_pyplot.py @@ -168,5 +168,145 @@ def test_subplot_reuse(): assert ax1 is plt.gca() ax2 = plt.subplot(122) assert ax2 is plt.gca() - ax1 = plt.subplot(121) + ax3 = plt.subplot(121) + assert ax1 is plt.gca() + assert ax1 is ax3 + + +def test_axes_kwargs(): + # plt.axes() always creates new axes, even if axes kwargs differ. + plt.figure() + ax = plt.axes() + ax1 = plt.axes() + assert ax is not None + assert ax1 is not ax + plt.close() + + plt.figure() + ax = plt.axes(projection='polar') + ax1 = plt.axes(projection='polar') + assert ax is not None + assert ax1 is not ax + plt.close() + + plt.figure() + ax = plt.axes(projection='polar') + ax1 = plt.axes() + assert ax is not None + assert ax1.name == 'rectilinear' + assert ax1 is not ax + plt.close() + + +def test_subplot_replace_projection(): + # plt.subplot() searches for axes with the same subplot spec, and if one + # exists, and the kwargs match returns it, create a new one if they do not + fig = plt.figure() + ax = plt.subplot(1, 2, 1) + ax1 = plt.subplot(1, 2, 1) + ax2 = plt.subplot(1, 2, 2) + # This will delete ax / ax1 as they fully overlap + ax3 = plt.subplot(1, 2, 1, projection='polar') + ax4 = plt.subplot(1, 2, 1, projection='polar') + assert ax is not None + assert ax1 is ax + assert ax2 is not ax + assert ax3 is not ax + assert ax3 is ax4 + + assert ax not in fig.axes + assert ax2 in fig.axes + assert ax3 in fig.axes + + assert ax.name == 'rectilinear' + assert ax2.name == 'rectilinear' + assert ax3.name == 'polar' + + +def test_subplot_kwarg_collision(): + ax1 = plt.subplot(projection='polar', theta_offset=0) + ax2 = plt.subplot(projection='polar', theta_offset=0) + assert ax1 is ax2 + ax3 = plt.subplot(projection='polar', theta_offset=1) + assert ax1 is not ax3 + assert ax1 not in plt.gcf().axes + + +def test_gca_kwargs(): + # plt.gca() returns an existing axes, unless there were no axes. + plt.figure() + ax = plt.gca() + ax1 = plt.gca() + assert ax is not None + assert ax1 is ax + plt.close() + + # plt.gca() raises a DeprecationWarning if called with kwargs. + plt.figure() + with pytest.warns( + MatplotlibDeprecationWarning, + match=r'Calling gca\(\) with keyword arguments was deprecated'): + ax = plt.gca(projection='polar') + ax1 = plt.gca() + assert ax is not None + assert ax1 is ax + assert ax1.name == 'polar' + plt.close() + + # plt.gca() ignores keyword arguments if an axes already exists. + plt.figure() + ax = plt.gca() + with pytest.warns( + MatplotlibDeprecationWarning, + match=r'Calling gca\(\) with keyword arguments was deprecated'): + ax1 = plt.gca(projection='polar') + assert ax is not None + assert ax1 is ax + assert ax1.name == 'rectilinear' + plt.close() + + +def test_subplot_projection_reuse(): + # create an axes + ax1 = plt.subplot(111) + # check that it is current assert ax1 is plt.gca() + # make sure we get it back if we ask again + assert ax1 is plt.subplot(111) + # create a polar plot + ax2 = plt.subplot(111, projection='polar') + assert ax2 is plt.gca() + # this should have deleted the first axes + assert ax1 not in plt.gcf().axes + # assert we get it back if no extra parameters passed + assert ax2 is plt.subplot(111) + # now check explicitly setting the projection to rectilinear + # makes a new axes + ax3 = plt.subplot(111, projection='rectilinear') + assert ax3 is plt.gca() + assert ax3 is not ax2 + assert ax2 not in plt.gcf().axes + + +def test_subplot_polar_normalization(): + ax1 = plt.subplot(111, projection='polar') + ax2 = plt.subplot(111, polar=True) + ax3 = plt.subplot(111, polar=True, projection='polar') + assert ax1 is ax2 + assert ax1 is ax3 + + with pytest.raises(ValueError, + match="polar=True, yet projection='3d'"): + ax2 = plt.subplot(111, polar=True, projection='3d') + + +def test_subplot_change_projection(): + ax = plt.subplot() + projections = ('aitoff', 'hammer', 'lambert', 'mollweide', + 'polar', 'rectilinear', '3d') + for proj in projections: + ax_next = plt.subplot(projection=proj) + assert ax_next is plt.subplot() + assert ax_next.name == proj + assert ax is not ax_next + ax = ax_next From c18967729a217e2f40b3a3f0d7dda057508d4dc6 Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Thu, 11 Feb 2021 20:56:33 -0500 Subject: [PATCH 3/3] MNT: use fstring in error message --- lib/matplotlib/figure.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 4c826a30df7e..fe98dd3f59a8 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -1508,9 +1508,9 @@ def _process_projection_requirements( if polar: if projection is not None and projection != 'polar': raise ValueError( - "polar=True, yet projection=%r. " - "Only one of these arguments should be supplied." % - projection) + f"polar={polar}, yet projection={projection!r}. " + "Only one of these arguments should be supplied." + ) projection = 'polar' if isinstance(projection, str) or projection is None: pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy