diff --git a/doc/api/next_api_changes/deprecations.rst b/doc/api/next_api_changes/deprecations.rst index d33d6d643908..796dfadfce42 100644 --- a/doc/api/next_api_changes/deprecations.rst +++ b/doc/api/next_api_changes/deprecations.rst @@ -247,3 +247,15 @@ mathtext glues ~~~~~~~~~~~~~~ The *copy* parameter of ``mathtext.Glue`` is deprecated (the underlying glue spec is now immutable). ``mathtext.GlueSpec`` is deprecated. + +Signatures of `.Artist.draw` and `.Axes.draw` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The *inframe* parameter to `.Axes.draw` is deprecated. Use +`.Axes.redraw_in_frame` instead. + +Not passing the *renderer* parameter to `.Axes.draw` is deprecated. Use +``axes.draw_artist(axes)`` instead. + +These changes make the signature of the ``draw`` (``artist.draw(renderer)``) +method consistent across all artists; thus, additional parameters to +`.Artist.draw` are deprecated. diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 222da2b5b4bb..6dbb5606ed38 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -26,7 +26,10 @@ def allow_rasterization(draw): renderer. """ - # the axes class has a second argument inframe for its draw method. + # Axes has a second (deprecated) argument inframe for its draw method. + # args and kwargs are deprecated, but we don't wrap this in + # cbook._delete_parameter for performance; the relevant deprecation + # warning will be emitted by the inner draw() call. @wraps(draw) def draw_wrapper(artist, renderer, *args, **kwargs): try: @@ -895,6 +898,8 @@ def set_agg_filter(self, filter_func): self._agg_filter = filter_func self.stale = True + @cbook._delete_parameter("3.3", "args") + @cbook._delete_parameter("3.3", "kwargs") def draw(self, renderer, *args, **kwargs): """ Draw the Artist (and its children) using the given renderer. diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index db47c347b9bf..d8912836c004 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1,4 +1,5 @@ from collections import OrderedDict +from contextlib import ExitStack import itertools import logging import math @@ -2618,9 +2619,16 @@ def _update_title_position(self, renderer): # Drawing @martist.allow_rasterization + @cbook._delete_parameter( + "3.3", "inframe", alternative="Axes.redraw_in_frame()") def draw(self, renderer=None, inframe=False): # docstring inherited if renderer is None: + cbook.warn_deprecated( + "3.3", message="Support for not passing the 'renderer' " + "parameter to Axes.draw() is deprecated since %(since)s and " + "will be removed %(removal)s. Use axes.draw_artist(axes) " + "instead.") renderer = self.figure._cachedRenderer if renderer is None: raise RuntimeError('No renderer defined') @@ -2701,7 +2709,7 @@ def draw_artist(self, a): """ This method can only be used after an initial draw which caches the renderer. It is used to efficiently update Axes - data (axis ticks, labels, etc are not updated) + data (axis ticks, labels, etc are not updated). """ if self.figure._cachedRenderer is None: raise AttributeError("draw_artist can only be used after an " @@ -2712,12 +2720,17 @@ def redraw_in_frame(self): """ This method can only be used after an initial draw which caches the renderer. It is used to efficiently update Axes - data (axis ticks, labels, etc are not updated) + data (axis ticks, labels, etc are not updated). """ if self.figure._cachedRenderer is None: raise AttributeError("redraw_in_frame can only be used after an " "initial draw which caches the renderer") - self.draw(self.figure._cachedRenderer, inframe=True) + with ExitStack() as stack: + for artist in [*self._get_axis_list(), + self.title, self._left_title, self._right_title]: + stack.push(artist.set_visible, artist.get_visible()) + artist.set_visible(False) + self.draw(self.figure._cachedRenderer) def get_renderer_cache(self): return self.figure._cachedRenderer diff --git a/lib/matplotlib/axes/_secondary_axes.py b/lib/matplotlib/axes/_secondary_axes.py index deb5934d08ef..3ba7c1451164 100644 --- a/lib/matplotlib/axes/_secondary_axes.py +++ b/lib/matplotlib/axes/_secondary_axes.py @@ -203,7 +203,9 @@ def set_functions(self, functions): 'and the second being the inverse') self._set_scale() - def draw(self, renderer=None, inframe=False): + # Should be changed to draw(self, renderer) once the deprecation of + # renderer=None and of inframe expires. + def draw(self, *args, **kwargs): """ Draw the secondary axes. @@ -215,7 +217,7 @@ def draw(self, renderer=None, inframe=False): self._set_lims() # this sets the scale in case the parent has set its scale. self._set_scale() - super().draw(renderer=renderer, inframe=inframe) + super().draw(*args, **kwargs) def _set_scale(self): """ diff --git a/lib/matplotlib/cbook/deprecation.py b/lib/matplotlib/cbook/deprecation.py index 09a25bc1c648..06faec380eac 100644 --- a/lib/matplotlib/cbook/deprecation.py +++ b/lib/matplotlib/cbook/deprecation.py @@ -307,7 +307,7 @@ def __repr__(self): _deprecated_parameter = _deprecated_parameter_class() -def _delete_parameter(since, name, func=None): +def _delete_parameter(since, name, func=None, **kwargs): """ Decorator indicating that parameter *name* of *func* is being deprecated. @@ -320,6 +320,8 @@ def _delete_parameter(since, name, func=None): such after the deprecation period has passed and the deprecated parameter is removed. + Additional keyword arguments are passed to `.warn_deprecated`. + Examples -------- :: @@ -329,29 +331,45 @@ def func(used_arg, other_arg, unused, more_args): ... """ if func is None: - return functools.partial(_delete_parameter, since, name) + return functools.partial(_delete_parameter, since, name, **kwargs) signature = inspect.signature(func) assert name in signature.parameters, ( f"Matplotlib internal error: {name!r} must be a parameter for " f"{func.__name__}()") - func.__signature__ = signature.replace(parameters=[ - param.replace(default=_deprecated_parameter) if param.name == name - else param - for param in signature.parameters.values()]) + kind = signature.parameters[name].kind + is_varargs = kind is inspect.Parameter.VAR_POSITIONAL + is_varkwargs = kind is inspect.Parameter.VAR_KEYWORD + if not is_varargs and not is_varkwargs: + func.__signature__ = signature = signature.replace(parameters=[ + param.replace(default=_deprecated_parameter) if param.name == name + else param + for param in signature.parameters.values()]) @functools.wraps(func) - def wrapper(*args, **kwargs): - arguments = func.__signature__.bind(*args, **kwargs).arguments + def wrapper(*inner_args, **inner_kwargs): + arguments = signature.bind(*inner_args, **inner_kwargs).arguments + if is_varargs and arguments.get(name): + warn_deprecated( + since, message=f"Additional positional arguments to " + f"{func.__name__}() are deprecated since %(since)s and " + f"support for them will be removed %(removal)s.") + elif is_varkwargs and arguments.get(name): + warn_deprecated( + since, message=f"Additional keyword arguments to " + f"{func.__name__}() are deprecated since %(since)s and " + f"support for them will be removed %(removal)s.") # We cannot just check `name not in arguments` because the pyplot # wrappers always pass all arguments explicitly. - if name in arguments and arguments[name] != _deprecated_parameter: + elif name in arguments and arguments[name] != _deprecated_parameter: warn_deprecated( - since, message=f"The {name!r} parameter of {func.__name__}() " - f"is deprecated since Matplotlib {since} and will be removed " - f"%(removal)s. If any parameter follows {name!r}, they " - f"should be pass as keyword, not positionally.") - return func(*args, **kwargs) + since, + name=repr(name), + obj_type=f"parameter of {func.__name__}()", + addendum=(f"If any parameter follows {name!r}, they should be " + f"passed as keyword, not positionally."), + **kwargs) + return func(*inner_args, **inner_kwargs) return wrapper diff --git a/lib/matplotlib/projections/polar.py b/lib/matplotlib/projections/polar.py index d8d8313dbfc7..a2ba4a0b5f5c 100644 --- a/lib/matplotlib/projections/polar.py +++ b/lib/matplotlib/projections/polar.py @@ -895,7 +895,9 @@ def get_yaxis_text2_transform(self, pad): pad_shift = _ThetaShift(self, pad, 'min') return self._yaxis_text_transform + pad_shift, 'center', halign - def draw(self, *args, **kwargs): + @cbook._delete_parameter("3.3", "args") + @cbook._delete_parameter("3.3", "kwargs") + def draw(self, renderer, *args, **kwargs): thetamin, thetamax = np.rad2deg(self._realViewLim.intervalx) if thetamin > thetamax: thetamin, thetamax = thetamax, thetamin @@ -938,7 +940,7 @@ def draw(self, *args, **kwargs): self.yaxis.reset_ticks() self.yaxis.set_clip_path(self.patch) - Axes.draw(self, *args, **kwargs) + Axes.draw(self, renderer, *args, **kwargs) def _gen_axes_patch(self): return mpatches.Wedge((0.5, 0.5), 0.5, 0.0, 360.0)