From 68498c634da803c447dbf0125fa7261ae81d61b4 Mon Sep 17 00:00:00 2001 From: Leo Singer Date: Fri, 18 Dec 2020 22:49:24 -0500 Subject: [PATCH] MNT: Remove deprecated axes kwargs collision detection In Matplotlib 2.1, the behavior of reusing existing axes when created with the same arguments was deprecated (see #9037). This behavior is now removed. 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. 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. Fixes #18832. --- .../deprecations/19153-LPS.rst | 5 + .../next_whats_new/axes_kwargs_collision.rst | 21 ++ lib/matplotlib/axes/_subplots.py | 12 +- lib/matplotlib/cbook/__init__.py | 3 + lib/matplotlib/figure.py | 274 ++---------------- lib/matplotlib/pyplot.py | 30 +- lib/matplotlib/tests/test_axes.py | 30 +- lib/matplotlib/tests/test_figure.py | 155 +++++++++- lib/mpl_toolkits/tests/test_mplot3d.py | 2 +- 9 files changed, 246 insertions(+), 286 deletions(-) create mode 100644 doc/api/next_api_changes/deprecations/19153-LPS.rst create mode 100644 doc/users/next_whats_new/axes_kwargs_collision.rst diff --git a/doc/api/next_api_changes/deprecations/19153-LPS.rst b/doc/api/next_api_changes/deprecations/19153-LPS.rst new file mode 100644 index 000000000000..b3094e3163e9 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/19153-LPS.rst @@ -0,0 +1,5 @@ +``pyplot.gca()``, ``Figure.gca`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Passing keyword arguments to `.pyplot.gca` or `.figure.Figure.gca` will not be +supported in a future release. diff --git a/doc/users/next_whats_new/axes_kwargs_collision.rst b/doc/users/next_whats_new/axes_kwargs_collision.rst new file mode 100644 index 000000000000..35d425a87dcb --- /dev/null +++ b/doc/users/next_whats_new/axes_kwargs_collision.rst @@ -0,0 +1,21 @@ +Changes to behavior of Axes creation methods (``gca()``, ``add_axes()``, ``add_subplot()``) +------------------------------------------------------------------------------------------- + +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. + +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. diff --git a/lib/matplotlib/axes/_subplots.py b/lib/matplotlib/axes/_subplots.py index 96315bb4ae6c..5042e3bbd860 100644 --- a/lib/matplotlib/axes/_subplots.py +++ b/lib/matplotlib/axes/_subplots.py @@ -1,5 +1,4 @@ import functools -import uuid from matplotlib import _api, docstring import matplotlib.artist as martist @@ -142,16 +141,7 @@ def _make_twin_axes(self, *args, **kwargs): # which currently uses this internal API. if kwargs["sharex"] is not self and kwargs["sharey"] is not self: raise ValueError("Twinned Axes may share only one axis") - # The dance here with label is to force add_subplot() to create a new - # Axes (by passing in a label never seen before). Note that this does - # not affect plot reactivation by subplot() as twin axes can never be - # reactivated by subplot(). - sentinel = str(uuid.uuid4()) - real_label = kwargs.pop("label", sentinel) - twin = self.figure.add_subplot( - self.get_subplotspec(), *args, label=sentinel, **kwargs) - if real_label is not sentinel: - twin.set_label(real_label) + twin = self.figure.add_subplot(self.get_subplotspec(), *args, **kwargs) self.set_adjustable('datalim') twin.set_adjustable('datalim') self._twinned_axes.join(self, twin) diff --git a/lib/matplotlib/cbook/__init__.py b/lib/matplotlib/cbook/__init__.py index acc1320c3c9c..2fd7c70506fd 100644 --- a/lib/matplotlib/cbook/__init__.py +++ b/lib/matplotlib/cbook/__init__.py @@ -606,6 +606,9 @@ def __len__(self): def __getitem__(self, ind): return self._elements[ind] + def as_list(self): + return list(self._elements) + def forward(self): """Move the position forward and return the current element.""" self._pos = min(self._pos + 1, len(self._elements) - 1) diff --git a/lib/matplotlib/figure.py b/lib/matplotlib/figure.py index 71be82de0374..6e8a7957e2d8 100644 --- a/lib/matplotlib/figure.py +++ b/lib/matplotlib/figure.py @@ -52,113 +52,6 @@ def _stale_figure_callback(self, val): self.figure.stale = val -class _AxesStack(cbook.Stack): - """ - Specialization of `.Stack`, to handle all tracking of `~.axes.Axes` in a - `.Figure`. - - This stack stores ``key, (ind, axes)`` pairs, where: - - * **key** is a hash of the args and kwargs used in generating the Axes. - * **ind** is a serial index tracking the order in which Axes were added. - - AxesStack is a callable; calling it returns the current Axes. - The `current_key_axes` method returns the current key and associated Axes. - """ - - def __init__(self): - super().__init__() - self._ind = 0 - - def as_list(self): - """ - Return a list of the Axes instances that have been added to the figure. - """ - ia_list = [a for k, a in self._elements] - ia_list.sort() - return [a for i, a in ia_list] - - def get(self, key): - """ - Return the Axes instance that was added with *key*. - If it is not present, return *None*. - """ - item = dict(self._elements).get(key) - if item is None: - return None - _api.warn_deprecated( - "2.1", - message="Adding an axes using the same arguments as a previous " - "axes currently reuses the earlier instance. In a future " - "version, a new instance will always be created and returned. " - "Meanwhile, this warning can be suppressed, and the future " - "behavior ensured, by passing a unique label to each axes " - "instance.") - return item[1] - - def _entry_from_axes(self, e): - ind, k = {a: (ind, k) for k, (ind, a) in self._elements}[e] - return (k, (ind, e)) - - def remove(self, a): - """Remove the Axes from the stack.""" - super().remove(self._entry_from_axes(a)) - - def bubble(self, a): - """ - Move the given Axes, which must already exist in the - stack, to the top. - """ - return super().bubble(self._entry_from_axes(a)) - - def add(self, key, a): - """ - Add Axes *a*, with key *key*, to the stack, and return the stack. - - If *key* is unhashable, replace it by a unique, arbitrary object. - - If *a* is already on the stack, don't add it again, but - return *None*. - """ - # All the error checking may be unnecessary; but this method - # is called so seldom that the overhead is negligible. - _api.check_isinstance(Axes, a=a) - try: - hash(key) - except TypeError: - key = object() - - a_existing = self.get(key) - if a_existing is not None: - super().remove((key, a_existing)) - _api.warn_external( - "key {!r} already existed; Axes is being replaced".format(key)) - # I don't think the above should ever happen. - - if a in self: - return None - self._ind += 1 - return super().push((key, (self._ind, a))) - - def current_key_axes(self): - """ - Return a tuple of ``(key, axes)`` for the active Axes. - - If no Axes exists on the stack, then returns ``(None, None)``. - """ - if not len(self._elements): - return self._default, self._default - else: - key, (index, axes) = self._elements[self._pos] - return key, axes - - def __call__(self): - return self.current_key_axes()[1] - - def __contains__(self, a): - return a in self.as_list() - - class SubplotParams: """ A class to hold the parameters for a subplot. @@ -249,7 +142,7 @@ def __init__(self): self.figure = self # list of child gridspecs for this figure self._gridspecs = [] - self._localaxes = _AxesStack() # keep track of axes at this level + self._localaxes = cbook.Stack() # keep track of axes at this level self.artists = [] self.lines = [] self.patches = [] @@ -634,15 +527,6 @@ def add_axes(self, *args, **kwargs): Notes ----- - If the figure already has an Axes with key (*args*, - *kwargs*) then it will simply make that Axes 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 Axes), you must use a unique set of args and kwargs. The Axes - *label* attribute has been exposed for this purpose: if you want - two Axes that are otherwise identical to be added to the figure, - make sure you give them unique labels. - In rare circumstances, `.add_axes` may be called with a single argument, an Axes instance already created in the present figure but not in the figure's list of Axes. @@ -661,8 +545,7 @@ def add_axes(self, *args, **kwargs): rect = l, b, w, h fig = plt.figure() - fig.add_axes(rect, label=label1) - fig.add_axes(rect, label=label2) + fig.add_axes(rect) fig.add_axes(rect, frameon=False, facecolor='g') fig.add_axes(rect, polar=True) ax = fig.add_axes(rect, projection='polar') @@ -683,14 +566,6 @@ def add_axes(self, *args, **kwargs): "add_axes() got multiple values for argument 'rect'") args = (kwargs.pop('rect'), ) - # shortcut the projection "key" modifications later on, if an axes - # with the exact args/kwargs exists, return it immediately. - key = self._make_key(*args, **kwargs) - ax = self._axstack.get(key) - if ax is not None: - self.sca(ax) - return ax - if isinstance(args[0], Axes): a = args[0] if a.get_figure() is not self: @@ -701,19 +576,12 @@ 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, key = \ - self._process_projection_requirements(*args, **kwargs) - - # check that an axes of this type doesn't already exist, if it - # does, set it as active and return it - ax = self._axstack.get(key) - if isinstance(ax, projection_class): - self.sca(ax) - return ax + projection_class, kwargs = 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(key, a) + return self._add_axes_internal(a) @docstring.dedent_interpd def add_subplot(self, *args, **kwargs): @@ -793,17 +661,6 @@ def add_subplot(self, *args, **kwargs): %(Axes_kwdoc)s - Notes - ----- - 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. - See Also -------- .Figure.add_axes @@ -840,10 +697,6 @@ def add_subplot(self, *args, **kwargs): if ax.get_figure() is not self: raise ValueError("The Subplot must have been created in " "the present figure") - # make a key for the subplot (which includes the axes object id - # in the hash) - key = self._make_key(*args, **kwargs) - else: if not args: args = (1, 1, 1) @@ -853,28 +706,15 @@ 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, key = \ - self._process_projection_requirements(*args, **kwargs) - ax = self._axstack.get(key) # search axes with this key in stack - if ax is not None: - if isinstance(ax, projection_class): - # the axes already existed, so set it as active & return - self.sca(ax) - return ax - else: - # Undocumented convenience behavior: - # subplot(111); subplot(111, projection='polar') - # will replace the first with the second. - # Without this, add_subplot would be simpler and - # more similar to add_axes. - self._axstack.remove(ax) + projection_class, kwargs = self._process_projection_requirements( + *args, **kwargs) ax = subplot_class_factory(projection_class)(self, *args, **kwargs) - return self._add_axes_internal(key, ax) + return self._add_axes_internal(ax) - def _add_axes_internal(self, key, ax): + def _add_axes_internal(self, ax): """Private helper for `add_axes` and `add_subplot`.""" - self._axstack.add(key, ax) - self._localaxes.add(key, ax) + self._axstack.push(ax) + self._localaxes.push(ax) self.sca(ax) ax._remove_method = self.delaxes self.stale = True @@ -1595,39 +1435,20 @@ def gca(self, **kwargs): %(Axes_kwdoc)s """ - ckey, cax = self._axstack.current_key_axes() - # if there exists an axes on the stack see if it matches - # the desired axes configuration - if cax is not None: - - # if no kwargs are given just return the current axes - # this is a convenience for gca() on axes such as polar etc. - if not kwargs: - return cax - - # if the user has specified particular projection detail - # then build up a key which can represent this - else: - projection_class, _, key = \ - self._process_projection_requirements(**kwargs) - - # let the returned axes have any gridspec by removing it from - # the key - ckey = ckey[1:] - key = key[1:] - - # if the cax matches this key then return the axes, otherwise - # continue and a new axes will be created - if key == ckey and isinstance(cax, projection_class): - return cax - else: - _api.warn_external('Requested projection is different ' - 'from current axis projection, ' - 'creating new axis with requested ' - 'projection.') - - # no axes found, so create one which spans the figure - return self.add_subplot(1, 1, 1, **kwargs) + if kwargs: + _api.warn_deprecated( + "3.4", + message="Calling gca() with keyword arguments was deprecated " + "in Matplotlib %(since)s. Starting %(removal)s, gca() will " + "take no keyword arguments. The gca() function should only be " + "used to get the current axes, or if no axes exist, create " + "new axes with default keyword arguments. To create a new " + "axes with non-default arguments, use plt.axes() or " + "plt.subplot().") + if self._axstack.empty(): + return self.add_subplot(1, 1, 1, **kwargs) + else: + return self._axstack() def _gci(self): # Helper for `~matplotlib.pyplot.gci`. Do not use elsewhere. @@ -1647,10 +1468,9 @@ def _gci(self): ``gci`` (get current image). """ # Look first for an image in the current Axes: - cax = self._axstack.current_key_axes()[1] - if cax is None: + if self._axstack.empty(): return None - im = cax._gci() + im = self._axstack()._gci() if im is not None: return im @@ -1669,7 +1489,7 @@ def _process_projection_requirements( """ Handle the args/kwargs to add_axes/add_subplot/gca, returning:: - (axes_proj_class, proj_class_kwargs, proj_stack_key) + (axes_proj_class, proj_class_kwargs) which can be used for new Axes initialization/identification. """ @@ -1698,41 +1518,7 @@ def _process_projection_requirements( f"projection must be a string, None or implement a " f"_as_mpl_axes method, not {projection!r}") - # Make the key without projection kwargs, this is used as a unique - # lookup for axes instances - key = self._make_key(*args, **kwargs) - - return projection_class, kwargs, key - - def _make_key(self, *args, **kwargs): - """Make a hashable key out of args and kwargs.""" - - def fixitems(items): - # items may have arrays and lists in them, so convert them - # to tuples for the key - ret = [] - for k, v in items: - # some objects can define __getitem__ without being - # iterable and in those cases the conversion to tuples - # will fail. So instead of using the np.iterable(v) function - # we simply try and convert to a tuple, and proceed if not. - try: - v = tuple(v) - except Exception: - pass - ret.append((k, v)) - return tuple(ret) - - def fixlist(args): - ret = [] - for a in args: - if np.iterable(a): - a = tuple(a) - ret.append(a) - return tuple(ret) - - key = fixlist(args), fixitems(kwargs.items()) - return key + return projection_class, kwargs def get_default_bbox_extra_artists(self): bbox_artists = [artist for artist in self.get_children() @@ -2366,7 +2152,7 @@ def __init__(self, self.set_tight_layout(tight_layout) - self._axstack = _AxesStack() # track all figure axes and current axes + self._axstack = cbook.Stack() # track all figure axes and current axes self.clf() self._cachedRenderer = None diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index d512d1876ab0..1791c2507649 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -42,7 +42,7 @@ from matplotlib import docstring from matplotlib.backend_bases import FigureCanvasBase, MouseButton from matplotlib.figure import Figure, figaspect -from matplotlib.gridspec import GridSpec +from matplotlib.gridspec import GridSpec, SubplotSpec from matplotlib import rcParams, rcParamsDefault, get_backend, rcParamsOrig from matplotlib.rcsetup import interactive_bk as _interactive_bk from matplotlib.artist import Artist @@ -1037,11 +1037,11 @@ def axes(arg=None, **kwargs): # Creating a new axes with specified dimensions and some kwargs plt.axes((left, bottom, width, height), facecolor='w') """ - + fig = gcf() if arg is None: - return subplot(**kwargs) + return fig.add_subplot(**kwargs) else: - return gcf().add_axes(arg, **kwargs) + return fig.add_axes(arg, **kwargs) def delaxes(ax=None): @@ -1221,7 +1221,18 @@ def subplot(*args, **kwargs): "and/or 'nrows'. Did you intend to call subplots()?") fig = gcf() - ax = fig.add_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: + ax = fig.add_subplot(*args, **kwargs) + bbox = ax.bbox axes_to_delete = [] for other_ax in fig.axes: @@ -2409,10 +2420,13 @@ def polar(*args, **kwargs): """ # If an axis already exists, check if it has a polar projection if gcf().get_axes(): - if not isinstance(gca(), PolarAxes): - _api.warn_external('Trying to create polar plot on an axis ' + ax = gca() + if isinstance(ax, PolarAxes): + return ax + else: + _api.warn_external('Trying to create polar plot on an Axes ' 'that does not have a polar projection.') - ax = gca(polar=True) + ax = axes(polar=True) ret = ax.plot(*args, **kwargs) return ret diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 2d448dd86d1a..948c3449e83f 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -2398,28 +2398,36 @@ def _as_mpl_axes(self): # testing axes creation with plt.axes ax = plt.axes([0, 0, 1, 1], projection=prj) assert type(ax) == PolarAxes - ax_via_gca = plt.gca(projection=prj) + with pytest.warns( + MatplotlibDeprecationWarning, + match=r'Calling gca\(\) with keyword arguments was deprecated'): + ax_via_gca = plt.gca(projection=prj) assert ax_via_gca is ax plt.close() # testing axes creation with gca - ax = plt.gca(projection=prj) + with pytest.warns( + MatplotlibDeprecationWarning, + match=r'Calling gca\(\) with keyword arguments was deprecated'): + ax = plt.gca(projection=prj) assert type(ax) == mpl.axes._subplots.subplot_class_factory(PolarAxes) - ax_via_gca = plt.gca(projection=prj) + with pytest.warns( + MatplotlibDeprecationWarning, + match=r'Calling gca\(\) with keyword arguments was deprecated'): + ax_via_gca = plt.gca(projection=prj) assert ax_via_gca is ax # try getting the axes given a different polar projection - with pytest.warns(UserWarning) as rec: + with pytest.warns( + MatplotlibDeprecationWarning, + match=r'Calling gca\(\) with keyword arguments was deprecated'): ax_via_gca = plt.gca(projection=prj2) - assert len(rec) == 1 - assert 'Requested projection is different' in str(rec[0].message) - assert ax_via_gca is not ax + assert ax_via_gca is ax assert ax.get_theta_offset() == 0 - assert ax_via_gca.get_theta_offset() == np.pi # try getting the axes given an == (not is) polar projection - with pytest.warns(UserWarning): + with pytest.warns( + MatplotlibDeprecationWarning, + match=r'Calling gca\(\) with keyword arguments was deprecated'): ax_via_gca = plt.gca(projection=prj3) - assert len(rec) == 1 - assert 'Requested projection is different' in str(rec[0].message) assert ax_via_gca is ax plt.close() diff --git a/lib/matplotlib/tests/test_figure.py b/lib/matplotlib/tests/test_figure.py index c5ab3cf6d232..b5748491bdcf 100644 --- a/lib/matplotlib/tests/test_figure.py +++ b/lib/matplotlib/tests/test_figure.py @@ -15,6 +15,7 @@ import matplotlib.pyplot as plt import matplotlib.dates as mdates import matplotlib.gridspec as gridspec +from matplotlib.cbook import MatplotlibDeprecationWarning import numpy as np import pytest @@ -154,30 +155,44 @@ def test_gca(): assert fig.add_axes() is None ax0 = fig.add_axes([0, 0, 1, 1]) - assert fig.gca(projection='rectilinear') is ax0 + with pytest.warns( + MatplotlibDeprecationWarning, + match=r'Calling gca\(\) with keyword arguments was deprecated'): + assert fig.gca(projection='rectilinear') is ax0 assert fig.gca() is ax0 ax1 = fig.add_axes(rect=[0.1, 0.1, 0.8, 0.8]) - assert fig.gca(projection='rectilinear') is ax1 + with pytest.warns( + MatplotlibDeprecationWarning, + match=r'Calling gca\(\) with keyword arguments was deprecated'): + assert fig.gca(projection='rectilinear') is ax1 assert fig.gca() is ax1 ax2 = fig.add_subplot(121, projection='polar') assert fig.gca() is ax2 - assert fig.gca(polar=True) is ax2 + with pytest.warns( + MatplotlibDeprecationWarning, + match=r'Calling gca\(\) with keyword arguments was deprecated'): + assert fig.gca(polar=True) is ax2 ax3 = fig.add_subplot(122) assert fig.gca() is ax3 - # the final request for a polar axes will end up creating one - # with a spec of 111. - with pytest.warns(UserWarning): - # Changing the projection will throw a warning - assert fig.gca(polar=True) is not ax3 - assert fig.gca(polar=True) is not ax2 - assert fig.gca().get_subplotspec().get_geometry() == (1, 1, 0, 0) + with pytest.warns( + MatplotlibDeprecationWarning, + match=r'Calling gca\(\) with keyword arguments was deprecated'): + assert fig.gca(polar=True) is ax3 + with pytest.warns( + MatplotlibDeprecationWarning, + match=r'Calling gca\(\) with keyword arguments was deprecated'): + assert fig.gca(polar=True) is not ax2 + assert fig.gca().get_subplotspec().get_geometry() == (1, 2, 1, 1) fig.sca(ax1) - assert fig.gca(projection='rectilinear') is ax1 + with pytest.warns( + MatplotlibDeprecationWarning, + match=r'Calling gca\(\) with keyword arguments was deprecated'): + assert fig.gca(projection='rectilinear') is ax1 assert fig.gca() is ax1 @@ -922,3 +937,121 @@ def test_subfigure_double(): subfigsnest[1].supylabel('supylabel') 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() + 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() + + # fig.add_axes() always creates new axes, even if axes kwargs differ. + fig = plt.figure() + ax = fig.add_axes([0, 0, 1, 1]) + ax1 = fig.add_axes([0, 0, 1, 1]) + assert ax is not None + assert ax1 is not ax + plt.close() + + fig = plt.figure() + ax = fig.add_axes([0, 0, 1, 1], projection='polar') + ax1 = fig.add_axes([0, 0, 1, 1], projection='polar') + assert ax is not None + assert ax1 is not ax + plt.close() + + fig = plt.figure() + ax = fig.add_axes([0, 0, 1, 1], projection='polar') + ax1 = fig.add_axes([0, 0, 1, 1]) + assert ax is not None + 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/mpl_toolkits/tests/test_mplot3d.py b/lib/mpl_toolkits/tests/test_mplot3d.py index 20316c82a8c1..89c3ba63acd0 100644 --- a/lib/mpl_toolkits/tests/test_mplot3d.py +++ b/lib/mpl_toolkits/tests/test_mplot3d.py @@ -899,7 +899,7 @@ def test_autoscale(): @pytest.mark.parametrize('auto', (True, False, None)) def test_unautoscale(axis, auto): fig = plt.figure() - ax = fig.gca(projection='3d') + ax = fig.add_subplot(projection='3d') x = np.arange(100) y = np.linspace(-0.1, 0.1, 100)