From e57b32165cae851b3f9cb846e37e3e49339aed43 Mon Sep 17 00:00:00 2001 From: Corenthin ZOZOR Date: Mon, 16 Jun 2025 16:33:39 +0200 Subject: [PATCH 01/12] Add xlim / ylim autogeneration - Create get_signature method - Create AXES_GETTER_SETTER_TEMPLATE - Create call_param method on generate_function --- lib/matplotlib/pyplot.py | 66 +++++++++++++++++++++ tools/boilerplate.py | 120 +++++++++++++++++++++++++++------------ 2 files changed, 151 insertions(+), 35 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index cf5c9b4b739f..e6bde60671d5 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -4457,6 +4457,72 @@ def yscale(value: str | ScaleBase, **kwargs) -> None: gca().set_yscale(value, **kwargs) +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@overload +@_copy_docstring_and_deprecators(Axes.get_xlim) +def xlim() -> tuple[float, float]: + ... + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@overload +@_copy_docstring_and_deprecators(Axes.set_xlim) +def xlim( + left: float | tuple[float, float] | None = None, + right: float | None = None, + *, + emit: bool = True, + auto: bool | None = False, + xmin: float | None = None, + xmax: float | None = None, +) -> tuple[float, float]: + ... + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@_copy_docstring_and_deprecators(Axes.get_xlim) +def xlim(*args, **kwargs): + ax = gca() + if not args and not kwargs: + return ax.get_xlim() + + ret = ax.set_xlim(*args, **kwargs) + return ret + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@overload +@_copy_docstring_and_deprecators(Axes.get_ylim) +def ylim() -> tuple[float, float]: + ... + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@overload +@_copy_docstring_and_deprecators(Axes.set_ylim) +def ylim( + bottom: float | tuple[float, float] | None = None, + top: float | None = None, + *, + emit: bool = True, + auto: bool | None = False, + ymin: float | None = None, + ymax: float | None = None, +) -> tuple[float, float]: + ... + + +# Autogenerated by boilerplate.py. Do not edit as changes will be lost. +@_copy_docstring_and_deprecators(Axes.get_ylim) +def ylim(*args, **kwargs): + ax = gca() + if not args and not kwargs: + return ax.get_ylim() + + ret = ax.set_ylim(*args, **kwargs) + return ret + + # Autogenerated by boilerplate.py. Do not edit as changes will be lost. def autumn() -> None: """ diff --git a/tools/boilerplate.py b/tools/boilerplate.py index 11ec15ac1c44..72e4100f0f16 100644 --- a/tools/boilerplate.py +++ b/tools/boilerplate.py @@ -54,6 +54,25 @@ def {name}{signature}: {return_statement}gca().{called_name}{call} """ +AXES_GETTER_SETTER_TEMPLATE = AUTOGEN_MSG + """ +@overload +@_copy_docstring_and_deprecators(Axes.get_{called_name}) +def {name}() -> {get_return_type}: ... +""" + AUTOGEN_MSG + """ +@overload +@_copy_docstring_and_deprecators(Axes.set_{called_name}) +def {name}{signature}: ... +""" + AUTOGEN_MSG + """ +@_copy_docstring_and_deprecators(Axes.get_{called_name}) +def {name}(*args, **kwargs): + ax = gca() + if not args and not kwargs: + return ax.get_{called_name}() + + ret = ax.set_{called_name}(*args, **kwargs) + return ret +""" + FIGURE_METHOD_TEMPLATE = AUTOGEN_MSG + """ @_copy_docstring_and_deprecators(Figure.{called_name}) def {name}{signature}: @@ -102,6 +121,7 @@ class direct_repr: """ A placeholder class to destringify annotations from ast """ + def __init__(self, value): self._repr = value @@ -109,7 +129,7 @@ def __repr__(self): return self._repr -def generate_function(name, called_fullname, template, **kwargs): +def generate_function(name, called_fullname, template, gettersetter=False, **kwargs): """ Create a wrapper function *pyplot_name* calling *call_name*. @@ -127,6 +147,11 @@ def generate_function(name, called_fullname, template, **kwargs): - signature: The function signature (including parentheses). - called_name: The name of the called function. - call: Parameters passed to *called_name* (including parentheses). + gettersetter : bool + Indicate if the method to be wrapped is correponding to a getter and setter. A new placeholdr is filled : + + - get_return_type: The type returned by the getter + - set_return_type: The type returned by the setter **kwargs Additional parameters are passed to ``template.format()``. @@ -135,15 +160,14 @@ def generate_function(name, called_fullname, template, **kwargs): class_name, called_name = called_fullname.split('.') class_ = {'Axes': Axes, 'Figure': Figure}[class_name] - meth = getattr(class_, called_name) - decorator = _api.deprecation.DECORATORS.get(meth) - # Generate the wrapper with the non-kwonly signature, as it will get - # redecorated with make_keyword_only by _copy_docstring_and_deprecators. - if decorator and decorator.func is _api.make_keyword_only: - meth = meth.__wrapped__ + if not gettersetter: + signature = get_signature(class_, called_name) + else: + getter_signature = get_signature(class_, f"get_{called_name}") + kwargs.setdefault("get_return_type", str(getter_signature.return_annotation)) - annotated_trees = get_ast_mro_trees(class_) - signature = get_matching_signature(meth, annotated_trees) + signature = get_signature(class_, f"set_{called_name}") + kwargs.setdefault('return_type', str(signature.return_annotation)) # Replace self argument. params = list(signature.parameters.values())[1:] @@ -152,30 +176,30 @@ def generate_function(name, called_fullname, template, **kwargs): param.replace(default=value_formatter(param.default)) if param.default is not param.empty else param for param in params])) + # How to call the wrapped function. - call = '(' + ', '.join(( - # Pass "intended-as-positional" parameters positionally to avoid - # forcing third-party subclasses to reproduce the parameter names. - '{0}' - if param.kind in [ - Parameter.POSITIONAL_OR_KEYWORD] - and param.default is Parameter.empty else - # Only pass the data kwarg if it is actually set, to avoid forcing - # third-party subclasses to support it. - '**({{"data": data}} if data is not None else {{}})' - if param.name == "data" else - '{0}={0}' - if param.kind in [ - Parameter.POSITIONAL_OR_KEYWORD, - Parameter.KEYWORD_ONLY] else - '{0}' - if param.kind is Parameter.POSITIONAL_ONLY else - '*{0}' - if param.kind is Parameter.VAR_POSITIONAL else - '**{0}' - if param.kind is Parameter.VAR_KEYWORD else - None).format(param.name) - for param in params) + ')' + + def call_param(param: Parameter): + match param.kind: + # Pass "intended-as-positional" parameters positionally to avoid + # forcing third-party subclasses to reproduce the parameter names. + case Parameter.POSITIONAL_OR_KEYWORD if param.default is Parameter.empty: + return '{0}' + # Only pass the data kwarg if it is actually set, to avoid forcing + # third-party subclasses to support it. + case _ if param.name == "data": + return '**({{"data": data}} if data is not None else {{}})' + case Parameter.POSITIONAL_OR_KEYWORD | Parameter.KEYWORD_ONLY: + return '{0}={0}' + case Parameter.POSITIONAL_ONLY: + return '{0}' + case Parameter.VAR_POSITIONAL: + return '*{0}' + case Parameter.VAR_KEYWORD: + return '**{0}' + return None + + call = '(' + ', '.join((call_param(param)).format(param.name) for param in params) + ')' return_statement = 'return ' if has_return_value else '' # Bail out in case of name collision. for reserved in ('gca', 'gci', 'gcf', '__ret'): @@ -286,7 +310,12 @@ def boilerplate_gen(): 'xlabel:set_xlabel', 'ylabel:set_ylabel', 'xscale:set_xscale', - 'yscale:set_yscale', + 'yscale:set_yscale' + ) + + _axes_getter_setters = ( + 'xlim', + 'ylim', ) cmappable = { @@ -341,6 +370,14 @@ def boilerplate_gen(): yield generate_function(name, f'Axes.{called_name}', template, sci_command=cmappable.get(name)) + for spec in _axes_getter_setters: + if ':' in spec: + name, called_name = spec.split(':') + else: + name = called_name = spec + yield generate_function(name, f'Axes.{called_name}', + AXES_GETTER_SETTER_TEMPLATE, True) + cmaps = ( 'autumn', 'bone', @@ -405,6 +442,19 @@ def get_ast_mro_trees(cls): return [get_ast_tree(c) for c in cls.__mro__ if c.__module__ != "builtins"] +def get_signature(class_, name): + meth = getattr(class_, name) + + decorator = _api.deprecation.DECORATORS.get(meth) + # Generate the wrapper with the non-kwonly signature, as it will get + # redecorated with make_keyword_only by _copy_docstring_and_deprecators. + if decorator and decorator.func is _api.make_keyword_only: + meth = meth.__wrapped__ + + annotated_trees = get_ast_mro_trees(class_) + return get_matching_signature(meth, annotated_trees) + + def get_matching_signature(method, trees): sig = inspect.signature(method) for tree in trees: @@ -460,10 +510,10 @@ def update_sig_from_node(node, sig): if len(sys.argv) > 1: pyplot_path = Path(sys.argv[1]) else: - mpl_path = (Path(__file__).parent / ".." /"lib"/"matplotlib").resolve() + mpl_path = (Path(__file__).parent / ".." / "lib" / "matplotlib").resolve() pyplot_path = mpl_path / "pyplot.py" for cls in [Axes, Figure]: - if mpl_path not in Path(inspect.getfile(cls)).parents: + if mpl_path not in Path(inspect.getfile(cls)).parents: raise RuntimeError( f"{cls.__name__} import path is not {mpl_path}.\n" "Please make sure your Matplotlib installation " From 608b51fd6321ac07133b8d66a14e15a906e21169 Mon Sep 17 00:00:00 2001 From: Corenthin ZOZOR Date: Thu, 19 Jun 2025 16:00:21 +0200 Subject: [PATCH 02/12] Create tests --- lib/matplotlib/tests/test_pyplot.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_pyplot.py b/lib/matplotlib/tests/test_pyplot.py index ab713707bace..193635973733 100644 --- a/lib/matplotlib/tests/test_pyplot.py +++ b/lib/matplotlib/tests/test_pyplot.py @@ -1,4 +1,5 @@ import difflib +import inspect import numpy as np import sys @@ -449,7 +450,6 @@ def figure_hook_example(figure): def test_figure_hook(): - test_rc = { 'figure.hooks': ['matplotlib.tests.test_pyplot:figure_hook_example'] } @@ -484,3 +484,24 @@ def test_matshow(): # Smoke test that matshow does not ask for a new figsize on the existing figure plt.matshow(arr, fignum=fig.number) + + +def assert_signatures_identical(plt_meth, original_meth, remove_self_param=False): + plt_params = inspect.signature(plt_meth).parameters + original_params = inspect.signature(original_meth).parameters + if remove_self_param: + if next(iter(original_params)) not in ["self"]: + raise AssertionError(f"{original_params} is not an instance method") + + original_params = dict(original_params) + del original_params["self"] + + assert plt_params == original_params + + +def test_setloglevel_signature(): + assert_signatures_identical(plt.set_loglevel, mpl.set_loglevel) + + +def test_polar_signature(): + assert_signatures_identical(plt.polar, plt.Axes.plot, True) From 41b701b41858cb2868485ca9f5747db4cd1f6d4a Mon Sep 17 00:00:00 2001 From: Corenthin ZOZOR Date: Thu, 19 Jun 2025 16:09:35 +0200 Subject: [PATCH 03/12] Update polar and set_loglevel signature on pyplot.py --- lib/matplotlib/pyplot.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index e6bde60671d5..c1144210478d 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -338,8 +338,8 @@ def uninstall_repl_displayhook() -> None: # Ensure this appears in the pyplot docs. @_copy_docstring_and_deprecators(matplotlib.set_loglevel) -def set_loglevel(*args, **kwargs) -> None: - return matplotlib.set_loglevel(*args, **kwargs) +def set_loglevel(level) -> None: + return matplotlib.set_loglevel(level) @_copy_docstring_and_deprecators(Artist.findobj) @@ -2690,7 +2690,13 @@ def matshow(A: ArrayLike, fignum: None | int = None, **kwargs) -> AxesImage: return im -def polar(*args, **kwargs) -> list[Line2D]: +def polar( + *args, + scalex=True, + scaley=True, + data=None, + **kwargs +) -> list[Line2D]: """ Make a polar plot. From 1ff2bd2be6ab4610b9c59e5d44c44bdcdfda7241 Mon Sep 17 00:00:00 2001 From: Corenthin ZOZOR Date: Thu, 19 Jun 2025 16:20:24 +0200 Subject: [PATCH 04/12] Create show overloads on pyplot.py --- lib/matplotlib/pyplot.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index c1144210478d..4741b7a3bd75 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -569,6 +569,14 @@ def draw_if_interactive(*args, **kwargs): return _get_backend_mod().draw_if_interactive(*args, **kwargs) +@overload +def show() -> None: ... + + +@overload +def show(block: bool) -> None: ... + + # This function's signature is rewritten upon backend-load by switch_backend. def show(*args, **kwargs) -> None: """ From b863ba298abe37c08c92f1ac1afc41f985d0bbff Mon Sep 17 00:00:00 2001 From: Corenthin ZOZOR Date: Thu, 19 Jun 2025 16:21:59 +0200 Subject: [PATCH 05/12] Update savefig signature on pyplot.py --- lib/matplotlib/pyplot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 4741b7a3bd75..278987715e81 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -50,7 +50,7 @@ import sys import threading import time -from typing import TYPE_CHECKING, cast, overload +from typing import IO, TYPE_CHECKING, cast, overload from cycler import cycler # noqa: F401 import matplotlib @@ -1259,11 +1259,11 @@ def draw() -> None: @_copy_docstring_and_deprecators(Figure.savefig) -def savefig(*args, **kwargs) -> None: +def savefig(fname: str | os.PathLike | IO, **kwargs) -> None: fig = gcf() # savefig default implementation has no return, so mypy is unhappy # presumably this is here because subclasses can return? - res = fig.savefig(*args, **kwargs) # type: ignore[func-returns-value] + res = fig.savefig(fname, **kwargs) # type: ignore[func-returns-value] fig.canvas.draw_idle() # Need this if 'transparent=True', to reset colors. return res From f4693e3c984f4775e29c3d10e48f58ae7de84e98 Mon Sep 17 00:00:00 2001 From: Corenthin ZOZOR Date: Thu, 19 Jun 2025 16:29:26 +0200 Subject: [PATCH 06/12] Create subplot overloads on pyplot.py --- lib/matplotlib/pyplot.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 278987715e81..18e43c268e2d 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -1401,6 +1401,22 @@ def cla() -> None: ## More ways of creating Axes ## +@overload +def subplot(nrows: int, ncols: int, index: int, /, **kwargs): ... + + +@overload +def subplot(pos: int | SubplotSpec, /, **kwargs): ... + + +@overload +def subplot(ax: Axes, /): ... + + +@overload +def subplot(**kwargs): ... + + @_docstring.interpd def subplot(*args, **kwargs) -> Axes: """ From 4ea0ff8e50f3a2460d18694aa1d58e7757c8726a Mon Sep 17 00:00:00 2001 From: Corenthin ZOZOR Date: Fri, 20 Jun 2025 19:36:15 +0200 Subject: [PATCH 07/12] Update test_pyplot.py to include type on signature --- lib/matplotlib/tests/test_pyplot.py | 55 ++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/tests/test_pyplot.py b/lib/matplotlib/tests/test_pyplot.py index 193635973733..8bfc1951f4bb 100644 --- a/lib/matplotlib/tests/test_pyplot.py +++ b/lib/matplotlib/tests/test_pyplot.py @@ -1,3 +1,4 @@ +import ast import difflib import inspect @@ -487,16 +488,60 @@ def test_matshow(): def assert_signatures_identical(plt_meth, original_meth, remove_self_param=False): - plt_params = inspect.signature(plt_meth).parameters - original_params = inspect.signature(original_meth).parameters + def get_src(meth): + meth_src = Path(inspect.getfile(meth)) + meth_stub = meth_src.with_suffix(".pyi") + return meth_stub if meth_stub.exists() else meth_src + + def tree_loop(tree, name, class_): + for item in tree.body: + if class_ and isinstance(item, ast.ClassDef) and item.name == class_: + return tree_loop(item, name, None) + + if isinstance(item, ast.FunctionDef) and item.name == name: + return item + + raise ValueError(f"Cannot find {class_}.{name} in ast") + + def get_signature(meth): + qualname = meth.__qualname__ + class_ = None if "." not in qualname else qualname.split(".")[-2] + path = get_src(meth) + tree = ast.parse(path.read_text()) + node = tree_loop(tree, meth.__name__, class_) + + params = dict(inspect.signature(meth).parameters) + args = node.args + for param in (*args.posonlyargs, *args.args, args.vararg, *args.kwonlyargs, args.kwarg): + if param is None: + continue + if param.annotation is None: + continue + annotation = ast.unparse(param.annotation) + params[param.arg] = params[param.arg].replace(annotation=annotation) + + if node.returns is not None: + return inspect.Signature( + params.values(), + return_annotation=ast.unparse(node.returns) + ) + else: + return inspect.Signature(params.values()) + + plt_sig = get_signature(plt_meth) + original_sig = get_signature(original_meth) + + assert plt_sig.return_annotation == original_sig.return_annotation + + original_params = original_sig.parameters if remove_self_param: if next(iter(original_params)) not in ["self"]: - raise AssertionError(f"{original_params} is not an instance method") + raise ValueError(f"{original_sig} is not an instance method") - original_params = dict(original_params) + original_params = original_params.copy() del original_params["self"] - assert plt_params == original_params + assert plt_sig.parameters == original_params def test_setloglevel_signature(): From 92dc04501bab539586cac48a3266891c75a4cb7c Mon Sep 17 00:00:00 2001 From: Corenthin ZOZOR Date: Fri, 20 Jun 2025 19:44:25 +0200 Subject: [PATCH 08/12] Add type hint on polar and set_loglevel on pyplot.py. Correct polar content --- lib/matplotlib/pyplot.py | 335 ++++++++++++++++++++------------------- 1 file changed, 171 insertions(+), 164 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 18e43c268e2d..309f5bbdc5b4 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -138,7 +138,6 @@ _R = TypeVar('_R') _T = TypeVar('_T') - # We may not need the following imports here: from matplotlib.colors import Normalize from matplotlib.lines import Line2D, AxLine @@ -155,7 +154,6 @@ _log = logging.getLogger(__name__) - # Explicit rename instead of import-as for typing's sake. colormaps = _colormaps color_sequences = _color_sequences @@ -163,19 +161,19 @@ @overload def _copy_docstring_and_deprecators( - method: Any, - func: Literal[None] = None + method: Any, + func: Literal[None] = None ) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ... @overload def _copy_docstring_and_deprecators( - method: Any, func: Callable[_P, _R]) -> Callable[_P, _R]: ... + method: Any, func: Callable[_P, _R]) -> Callable[_P, _R]: ... def _copy_docstring_and_deprecators( - method: Any, - func: Callable[_P, _R] | None = None + method: Any, + func: Callable[_P, _R] | None = None ) -> Callable[[Callable[_P, _R]], Callable[_P, _R]] | Callable[_P, _R]: if func is None: return cast('Callable[[Callable[_P, _R]], Callable[_P, _R]]', @@ -201,8 +199,8 @@ def _copy_docstring_and_deprecators( 'FigureBase._gci', # wrapped_func is private '_AxesBase._sci', # wrapped_func is private 'Artist.findobj', # not a standard pyplot wrapper because it does not operate - # on the current Figure / Axes. Explanation of relation would - # be more complex and is not too important. + # on the current Figure / Axes. Explanation of relation would + # be more complex and is not too important. ] @@ -338,15 +336,15 @@ def uninstall_repl_displayhook() -> None: # Ensure this appears in the pyplot docs. @_copy_docstring_and_deprecators(matplotlib.set_loglevel) -def set_loglevel(level) -> None: +def set_loglevel(level: str) -> None: return matplotlib.set_loglevel(level) @_copy_docstring_and_deprecators(Artist.findobj) def findobj( - o: Artist | None = None, - match: Callable[[Artist], bool] | type[Artist] | None = None, - include_self: bool = True + o: Artist | None = None, + match: Callable[[Artist], bool] | type[Artist] | None = None, + include_self: bool = True ) -> list[Artist]: if o is None: o = gcf() @@ -454,7 +452,6 @@ class backend_mod(matplotlib.backend_bases._Backend): # update backend_mod accordingly; also, per-backend customization of # draw_if_interactive is disabled. if new_figure_manager is None: - def new_figure_manager_given_figure(num, figure): return canvas_class.new_manager(figure, num) @@ -779,8 +776,8 @@ def rc(group: str, **kwargs) -> None: @_copy_docstring_and_deprecators(matplotlib.rc_context) def rc_context( - rc: dict[str, Any] | None = None, - fname: str | pathlib.Path | os.PathLike | None = None, + rc: dict[str, Any] | None = None, + fname: str | pathlib.Path | os.PathLike | None = None, ) -> AbstractContextManager[None]: return matplotlib.rc_context(rc, fname) @@ -811,7 +808,7 @@ def setp(obj, *args, **kwargs): def xkcd( - scale: float = 1, length: float = 100, randomness: float = 2 + scale: float = 1, length: float = 100, randomness: float = 2 ) -> ExitStack: """ Turn on `xkcd `_ sketch-style drawing mode. @@ -881,23 +878,23 @@ def xkcd( ## Figures ## def figure( - # autoincrement if None, else integer from 1-N - num: int | str | Figure | SubFigure | None = None, - # defaults to rc figure.figsize - figsize: ArrayLike # a 2-element ndarray is accepted as well - | tuple[float, float, Literal["in", "cm", "px"]] - | None = None, - # defaults to rc figure.dpi - dpi: float | None = None, - *, - # defaults to rc figure.facecolor - facecolor: ColorType | None = None, - # defaults to rc figure.edgecolor - edgecolor: ColorType | None = None, - frameon: bool = True, - FigureClass: type[Figure] = Figure, - clear: bool = False, - **kwargs + # autoincrement if None, else integer from 1-N + num: int | str | Figure | SubFigure | None = None, + # defaults to rc figure.figsize + figsize: ArrayLike # a 2-element ndarray is accepted as well + | tuple[float, float, Literal["in", "cm", "px"]] + | None = None, + # defaults to rc figure.dpi + dpi: float | None = None, + *, + # defaults to rc figure.facecolor + facecolor: ColorType | None = None, + # defaults to rc figure.edgecolor + edgecolor: ColorType | None = None, + frameon: bool = True, + FigureClass: type[Figure] = Figure, + clear: bool = False, + **kwargs ) -> Figure: """ Create a new figure, or activate an existing figure. @@ -1025,7 +1022,7 @@ def figure( num = next_num else: if (any(param is not None for param in [figsize, dpi, facecolor, edgecolor]) - or not frameon or kwargs) and num in allnums: + or not frameon or kwargs) and num in allnums: _api.warn_external( "Ignoring specified arguments in this call " f"because figure with num: {num} already exists") @@ -1273,6 +1270,8 @@ def savefig(fname: str | os.PathLike | IO, **kwargs) -> None: def figlegend(*args, **kwargs) -> Legend: return gcf().legend(*args, **kwargs) + + if Figure.legend.__doc__: figlegend.__doc__ = Figure.legend.__doc__ \ .replace(" legend(", " figlegend(") \ @@ -1284,8 +1283,8 @@ def figlegend(*args, **kwargs) -> Legend: @_docstring.interpd def axes( - arg: None | tuple[float, float, float, float] = None, - **kwargs + arg: None | tuple[float, float, float, float] = None, + **kwargs ) -> matplotlib.axes.Axes: """ Add an Axes to the current figure and make it the current Axes. @@ -1586,9 +1585,9 @@ def subplot(*args, **kwargs) -> Axes: # If we found an Axes at the position, we can reuse it if the user passed no # kwargs or if the Axes class and kwargs are identical. if (ax.get_subplotspec() == key - and (kwargs == {} - or (ax._projection_init - == fig._process_projection_requirements(**kwargs)))): + and (kwargs == {} + or (ax._projection_init + == fig._process_projection_requirements(**kwargs)))): break else: # we have exhausted the known Axes and none match, make a new one! @@ -1601,65 +1600,65 @@ def subplot(*args, **kwargs) -> Axes: @overload def subplots( - nrows: Literal[1] = ..., - ncols: Literal[1] = ..., - *, - sharex: bool | Literal["none", "all", "row", "col"] = ..., - sharey: bool | Literal["none", "all", "row", "col"] = ..., - squeeze: Literal[True] = ..., - width_ratios: Sequence[float] | None = ..., - height_ratios: Sequence[float] | None = ..., - subplot_kw: dict[str, Any] | None = ..., - gridspec_kw: dict[str, Any] | None = ..., - **fig_kw + nrows: Literal[1] = ..., + ncols: Literal[1] = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: Literal[True] = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + **fig_kw ) -> tuple[Figure, Axes]: ... @overload def subplots( - nrows: int = ..., - ncols: int = ..., - *, - sharex: bool | Literal["none", "all", "row", "col"] = ..., - sharey: bool | Literal["none", "all", "row", "col"] = ..., - squeeze: Literal[False], - width_ratios: Sequence[float] | None = ..., - height_ratios: Sequence[float] | None = ..., - subplot_kw: dict[str, Any] | None = ..., - gridspec_kw: dict[str, Any] | None = ..., - **fig_kw + nrows: int = ..., + ncols: int = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: Literal[False], + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + **fig_kw ) -> tuple[Figure, np.ndarray]: # TODO numpy/numpy#24738 ... @overload def subplots( - nrows: int = ..., - ncols: int = ..., - *, - sharex: bool | Literal["none", "all", "row", "col"] = ..., - sharey: bool | Literal["none", "all", "row", "col"] = ..., - squeeze: bool = ..., - width_ratios: Sequence[float] | None = ..., - height_ratios: Sequence[float] | None = ..., - subplot_kw: dict[str, Any] | None = ..., - gridspec_kw: dict[str, Any] | None = ..., - **fig_kw + nrows: int = ..., + ncols: int = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: bool = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + **fig_kw ) -> tuple[Figure, Any]: ... def subplots( - nrows: int = 1, ncols: int = 1, *, - sharex: bool | Literal["none", "all", "row", "col"] = False, - sharey: bool | Literal["none", "all", "row", "col"] = False, - squeeze: bool = True, - width_ratios: Sequence[float] | None = None, - height_ratios: Sequence[float] | None = None, - subplot_kw: dict[str, Any] | None = None, - gridspec_kw: dict[str, Any] | None = None, - **fig_kw + nrows: int = 1, ncols: int = 1, *, + sharex: bool | Literal["none", "all", "row", "col"] = False, + sharey: bool | Literal["none", "all", "row", "col"] = False, + squeeze: bool = True, + width_ratios: Sequence[float] | None = None, + height_ratios: Sequence[float] | None = None, + subplot_kw: dict[str, Any] | None = None, + gridspec_kw: dict[str, Any] | None = None, + **fig_kw ) -> tuple[Figure, Any]: """ Create a figure and a set of subplots. @@ -1815,66 +1814,66 @@ def subplots( @overload def subplot_mosaic( - mosaic: str, - *, - sharex: bool = ..., - sharey: bool = ..., - width_ratios: ArrayLike | None = ..., - height_ratios: ArrayLike | None = ..., - empty_sentinel: str = ..., - subplot_kw: dict[str, Any] | None = ..., - gridspec_kw: dict[str, Any] | None = ..., - per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] | None = ..., - **fig_kw: Any + mosaic: str, + *, + sharex: bool = ..., + sharey: bool = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + empty_sentinel: str = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] | None = ..., + **fig_kw: Any ) -> tuple[Figure, dict[str, matplotlib.axes.Axes]]: ... @overload def subplot_mosaic( - mosaic: list[HashableList[_T]], - *, - sharex: bool = ..., - sharey: bool = ..., - width_ratios: ArrayLike | None = ..., - height_ratios: ArrayLike | None = ..., - empty_sentinel: _T = ..., - subplot_kw: dict[str, Any] | None = ..., - gridspec_kw: dict[str, Any] | None = ..., - per_subplot_kw: dict[_T | tuple[_T, ...], dict[str, Any]] | None = ..., - **fig_kw: Any + mosaic: list[HashableList[_T]], + *, + sharex: bool = ..., + sharey: bool = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + empty_sentinel: _T = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + per_subplot_kw: dict[_T | tuple[_T, ...], dict[str, Any]] | None = ..., + **fig_kw: Any ) -> tuple[Figure, dict[_T, matplotlib.axes.Axes]]: ... @overload def subplot_mosaic( - mosaic: list[HashableList[Hashable]], - *, - sharex: bool = ..., - sharey: bool = ..., - width_ratios: ArrayLike | None = ..., - height_ratios: ArrayLike | None = ..., - empty_sentinel: Any = ..., - subplot_kw: dict[str, Any] | None = ..., - gridspec_kw: dict[str, Any] | None = ..., - per_subplot_kw: dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = ..., - **fig_kw: Any + mosaic: list[HashableList[Hashable]], + *, + sharex: bool = ..., + sharey: bool = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + empty_sentinel: Any = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + per_subplot_kw: dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = ..., + **fig_kw: Any ) -> tuple[Figure, dict[Hashable, matplotlib.axes.Axes]]: ... def subplot_mosaic( - mosaic: str | list[HashableList[_T]] | list[HashableList[Hashable]], - *, - sharex: bool = False, - sharey: bool = False, - width_ratios: ArrayLike | None = None, - height_ratios: ArrayLike | None = None, - empty_sentinel: Any = '.', - subplot_kw: dict[str, Any] | None = None, - gridspec_kw: dict[str, Any] | None = None, - per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] | - dict[_T | tuple[_T, ...], dict[str, Any]] | - dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = None, - **fig_kw: Any + mosaic: str | list[HashableList[_T]] | list[HashableList[Hashable]], + *, + sharex: bool = False, + sharey: bool = False, + width_ratios: ArrayLike | None = None, + height_ratios: ArrayLike | None = None, + empty_sentinel: Any = '.', + subplot_kw: dict[str, Any] | None = None, + gridspec_kw: dict[str, Any] | None = None, + per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] | + dict[_T | tuple[_T, ...], dict[str, Any]] | + dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = None, + **fig_kw: Any ) -> tuple[Figure, dict[str, matplotlib.axes.Axes]] | \ tuple[Figure, dict[_T, matplotlib.axes.Axes]] | \ tuple[Figure, dict[Hashable, matplotlib.axes.Axes]]: @@ -1991,10 +1990,10 @@ def subplot_mosaic( def subplot2grid( - shape: tuple[int, int], loc: tuple[int, int], - rowspan: int = 1, colspan: int = 1, - fig: Figure | None = None, - **kwargs + shape: tuple[int, int], loc: tuple[int, int], + rowspan: int = 1, colspan: int = 1, + fig: Figure | None = None, + **kwargs ) -> matplotlib.axes.Axes: """ Create a subplot at a specific location inside a regular grid. @@ -2117,6 +2116,7 @@ def box(on: bool | None = None) -> None: on = not ax.get_frame_on() ax.set_frame_on(on) + ## Axis ## @@ -2195,11 +2195,11 @@ def ylim(*args, **kwargs) -> tuple[float, float]: def xticks( - ticks: ArrayLike | None = None, - labels: Sequence[str] | None = None, - *, - minor: bool = False, - **kwargs + ticks: ArrayLike | None = None, + labels: Sequence[str] | None = None, + *, + minor: bool = False, + **kwargs ) -> tuple[list[Tick] | np.ndarray, list[Text]]: """ Get or set the current tick locations and labels of the x-axis. @@ -2281,11 +2281,11 @@ def xticks( def yticks( - ticks: ArrayLike | None = None, - labels: Sequence[str] | None = None, - *, - minor: bool = False, - **kwargs + ticks: ArrayLike | None = None, + labels: Sequence[str] | None = None, + *, + minor: bool = False, + **kwargs ) -> tuple[list[Tick] | np.ndarray, list[Text]]: """ Get or set the current tick locations and labels of the y-axis. @@ -2366,11 +2366,11 @@ def yticks( def rgrids( - radii: ArrayLike | None = None, - labels: Sequence[str | Text] | None = None, - angle: float | None = None, - fmt: str | None = None, - **kwargs + radii: ArrayLike | None = None, + labels: Sequence[str | Text] | None = None, + angle: float | None = None, + fmt: str | None = None, + **kwargs ) -> tuple[list[Line2D], list[Text]]: """ Get or set the radial gridlines on the current polar plot. @@ -2445,10 +2445,10 @@ def rgrids( def thetagrids( - angles: ArrayLike | None = None, - labels: Sequence[str | Text] | None = None, - fmt: str | None = None, - **kwargs + angles: ArrayLike | None = None, + labels: Sequence[str | Text] | None = None, + fmt: str | None = None, + **kwargs ) -> tuple[list[Line2D], list[Text]]: """ Get or set the theta gridlines on the current polar plot. @@ -2541,8 +2541,8 @@ def _get_pyplot_commands() -> list[str]: return sorted( name for name, obj in globals().items() if not name.startswith('_') and name not in exclude - and inspect.isfunction(obj) - and inspect.getmodule(obj) is this_module) + and inspect.isfunction(obj) + and inspect.getmodule(obj) is this_module) ## Plotting part 1: manually generated functions and wrappers ## @@ -2550,10 +2550,10 @@ def _get_pyplot_commands() -> list[str]: @_copy_docstring_and_deprecators(Figure.colorbar) def colorbar( - mappable: ScalarMappable | ColorizingArtist | None = None, - cax: matplotlib.axes.Axes | None = None, - ax: matplotlib.axes.Axes | Iterable[matplotlib.axes.Axes] | None = None, - **kwargs + mappable: ScalarMappable | ColorizingArtist | None = None, + cax: matplotlib.axes.Axes | None = None, + ax: matplotlib.axes.Axes | Iterable[matplotlib.axes.Axes] | None = None, + **kwargs ) -> Colorbar: if mappable is None: mappable = gci() @@ -2648,7 +2648,7 @@ def imread( @_copy_docstring_and_deprecators(matplotlib.image.imsave) def imsave( - fname: str | os.PathLike | BinaryIO, arr: ArrayLike, **kwargs + fname: str | os.PathLike | BinaryIO, arr: ArrayLike, **kwargs ) -> None: matplotlib.image.imsave(fname, arr, **kwargs) @@ -2715,9 +2715,9 @@ def matshow(A: ArrayLike, fignum: None | int = None, **kwargs) -> AxesImage: def polar( - *args, - scalex=True, - scaley=True, + *args: float | ArrayLike | str, + scalex: bool = True, + scaley: bool = True, data=None, **kwargs ) -> list[Line2D]: @@ -2754,7 +2754,13 @@ def polar( ) else: ax = axes(projection="polar") - return ax.plot(*args, **kwargs) + return ax.plot( + *args, + scalex=scalex, + scaley=scaley, + data=data, + **kwargs + ) # If rcParams['backend_fallback'] is true, and an interactive backend is @@ -2765,11 +2771,12 @@ def polar( requested_backend = None if requested_backend is None else requested_backend.lower() available_backends = backend_registry.list_builtin(BackendFilter.INTERACTIVE) if ( - requested_backend in (set(available_backends) - {'webagg', 'nbagg'}) - and cbook._get_running_interactive_framework() + requested_backend in (set(available_backends) - {'webagg', 'nbagg'}) + and cbook._get_running_interactive_framework() ): rcParams._set("backend", rcsetup._auto_backend_sentinel) + # fmt: on ################# REMAINING CONTENT GENERATED BY boilerplate.py ############## From 66ee0714ff310e0693e05c4616bbb702e45a6407 Mon Sep 17 00:00:00 2001 From: Corenthin ZOZOR Date: Fri, 20 Jun 2025 20:24:18 +0200 Subject: [PATCH 09/12] Remove old xlim and ylim --- lib/matplotlib/pyplot.py | 75 ---------------------------------------- 1 file changed, 75 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 309f5bbdc5b4..6330b66ed01b 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2119,81 +2119,6 @@ def box(on: bool | None = None) -> None: ## Axis ## - -def xlim(*args, **kwargs) -> tuple[float, float]: - """ - Get or set the x limits of the current Axes. - - Call signatures:: - - left, right = xlim() # return the current xlim - xlim((left, right)) # set the xlim to left, right - xlim(left, right) # set the xlim to left, right - - If you do not specify args, you can pass *left* or *right* as kwargs, - i.e.:: - - xlim(right=3) # adjust the right leaving left unchanged - xlim(left=1) # adjust the left leaving right unchanged - - Setting limits turns autoscaling off for the x-axis. - - Returns - ------- - left, right - A tuple of the new x-axis limits. - - Notes - ----- - Calling this function with no arguments (e.g. ``xlim()``) is the pyplot - equivalent of calling `~.Axes.get_xlim` on the current Axes. - Calling this function with arguments is the pyplot equivalent of calling - `~.Axes.set_xlim` on the current Axes. All arguments are passed though. - """ - ax = gca() - if not args and not kwargs: - return ax.get_xlim() - ret = ax.set_xlim(*args, **kwargs) - return ret - - -def ylim(*args, **kwargs) -> tuple[float, float]: - """ - Get or set the y-limits of the current Axes. - - Call signatures:: - - bottom, top = ylim() # return the current ylim - ylim((bottom, top)) # set the ylim to bottom, top - ylim(bottom, top) # set the ylim to bottom, top - - If you do not specify args, you can alternatively pass *bottom* or - *top* as kwargs, i.e.:: - - ylim(top=3) # adjust the top leaving bottom unchanged - ylim(bottom=1) # adjust the bottom leaving top unchanged - - Setting limits turns autoscaling off for the y-axis. - - Returns - ------- - bottom, top - A tuple of the new y-axis limits. - - Notes - ----- - Calling this function with no arguments (e.g. ``ylim()``) is the pyplot - equivalent of calling `~.Axes.get_ylim` on the current Axes. - Calling this function with arguments is the pyplot equivalent of calling - `~.Axes.set_ylim` on the current Axes. All arguments are passed though. - """ - ax = gca() - if not args and not kwargs: - return ax.get_ylim() - ret = ax.set_ylim(*args, **kwargs) - return ret - - def xticks( ticks: ArrayLike | None = None, labels: Sequence[str] | None = None, From e46252a85b88844a85d556b949f965ed94ec65de Mon Sep 17 00:00:00 2001 From: Corenthin ZOZOR Date: Fri, 20 Jun 2025 21:06:21 +0200 Subject: [PATCH 10/12] Revert superfluous changes --- lib/matplotlib/pyplot.py | 394 +++++++++++++++++++++++---------------- 1 file changed, 234 insertions(+), 160 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index 6330b66ed01b..ddce303c1efc 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -138,6 +138,7 @@ _R = TypeVar('_R') _T = TypeVar('_T') + # We may not need the following imports here: from matplotlib.colors import Normalize from matplotlib.lines import Line2D, AxLine @@ -154,6 +155,7 @@ _log = logging.getLogger(__name__) + # Explicit rename instead of import-as for typing's sake. colormaps = _colormaps color_sequences = _color_sequences @@ -161,19 +163,19 @@ @overload def _copy_docstring_and_deprecators( - method: Any, - func: Literal[None] = None + method: Any, + func: Literal[None] = None ) -> Callable[[Callable[_P, _R]], Callable[_P, _R]]: ... @overload def _copy_docstring_and_deprecators( - method: Any, func: Callable[_P, _R]) -> Callable[_P, _R]: ... + method: Any, func: Callable[_P, _R]) -> Callable[_P, _R]: ... def _copy_docstring_and_deprecators( - method: Any, - func: Callable[_P, _R] | None = None + method: Any, + func: Callable[_P, _R] | None = None ) -> Callable[[Callable[_P, _R]], Callable[_P, _R]] | Callable[_P, _R]: if func is None: return cast('Callable[[Callable[_P, _R]], Callable[_P, _R]]', @@ -199,8 +201,8 @@ def _copy_docstring_and_deprecators( 'FigureBase._gci', # wrapped_func is private '_AxesBase._sci', # wrapped_func is private 'Artist.findobj', # not a standard pyplot wrapper because it does not operate - # on the current Figure / Axes. Explanation of relation would - # be more complex and is not too important. + # on the current Figure / Axes. Explanation of relation would + # be more complex and is not too important. ] @@ -342,9 +344,9 @@ def set_loglevel(level: str) -> None: @_copy_docstring_and_deprecators(Artist.findobj) def findobj( - o: Artist | None = None, - match: Callable[[Artist], bool] | type[Artist] | None = None, - include_self: bool = True + o: Artist | None = None, + match: Callable[[Artist], bool] | type[Artist] | None = None, + include_self: bool = True ) -> list[Artist]: if o is None: o = gcf() @@ -452,6 +454,7 @@ class backend_mod(matplotlib.backend_bases._Backend): # update backend_mod accordingly; also, per-backend customization of # draw_if_interactive is disabled. if new_figure_manager is None: + def new_figure_manager_given_figure(num, figure): return canvas_class.new_manager(figure, num) @@ -776,8 +779,8 @@ def rc(group: str, **kwargs) -> None: @_copy_docstring_and_deprecators(matplotlib.rc_context) def rc_context( - rc: dict[str, Any] | None = None, - fname: str | pathlib.Path | os.PathLike | None = None, + rc: dict[str, Any] | None = None, + fname: str | pathlib.Path | os.PathLike | None = None, ) -> AbstractContextManager[None]: return matplotlib.rc_context(rc, fname) @@ -808,7 +811,7 @@ def setp(obj, *args, **kwargs): def xkcd( - scale: float = 1, length: float = 100, randomness: float = 2 + scale: float = 1, length: float = 100, randomness: float = 2 ) -> ExitStack: """ Turn on `xkcd `_ sketch-style drawing mode. @@ -878,23 +881,23 @@ def xkcd( ## Figures ## def figure( - # autoincrement if None, else integer from 1-N - num: int | str | Figure | SubFigure | None = None, - # defaults to rc figure.figsize - figsize: ArrayLike # a 2-element ndarray is accepted as well - | tuple[float, float, Literal["in", "cm", "px"]] - | None = None, - # defaults to rc figure.dpi - dpi: float | None = None, - *, - # defaults to rc figure.facecolor - facecolor: ColorType | None = None, - # defaults to rc figure.edgecolor - edgecolor: ColorType | None = None, - frameon: bool = True, - FigureClass: type[Figure] = Figure, - clear: bool = False, - **kwargs + # autoincrement if None, else integer from 1-N + num: int | str | Figure | SubFigure | None = None, + # defaults to rc figure.figsize + figsize: ArrayLike # a 2-element ndarray is accepted as well + | tuple[float, float, Literal["in", "cm", "px"]] + | None = None, + # defaults to rc figure.dpi + dpi: float | None = None, + *, + # defaults to rc figure.facecolor + facecolor: ColorType | None = None, + # defaults to rc figure.edgecolor + edgecolor: ColorType | None = None, + frameon: bool = True, + FigureClass: type[Figure] = Figure, + clear: bool = False, + **kwargs ) -> Figure: """ Create a new figure, or activate an existing figure. @@ -1022,7 +1025,7 @@ def figure( num = next_num else: if (any(param is not None for param in [figsize, dpi, facecolor, edgecolor]) - or not frameon or kwargs) and num in allnums: + or not frameon or kwargs) and num in allnums: _api.warn_external( "Ignoring specified arguments in this call " f"because figure with num: {num} already exists") @@ -1270,8 +1273,6 @@ def savefig(fname: str | os.PathLike | IO, **kwargs) -> None: def figlegend(*args, **kwargs) -> Legend: return gcf().legend(*args, **kwargs) - - if Figure.legend.__doc__: figlegend.__doc__ = Figure.legend.__doc__ \ .replace(" legend(", " figlegend(") \ @@ -1283,8 +1284,8 @@ def figlegend(*args, **kwargs) -> Legend: @_docstring.interpd def axes( - arg: None | tuple[float, float, float, float] = None, - **kwargs + arg: None | tuple[float, float, float, float] = None, + **kwargs ) -> matplotlib.axes.Axes: """ Add an Axes to the current figure and make it the current Axes. @@ -1585,9 +1586,9 @@ def subplot(*args, **kwargs) -> Axes: # If we found an Axes at the position, we can reuse it if the user passed no # kwargs or if the Axes class and kwargs are identical. if (ax.get_subplotspec() == key - and (kwargs == {} - or (ax._projection_init - == fig._process_projection_requirements(**kwargs)))): + and (kwargs == {} + or (ax._projection_init + == fig._process_projection_requirements(**kwargs)))): break else: # we have exhausted the known Axes and none match, make a new one! @@ -1600,65 +1601,65 @@ def subplot(*args, **kwargs) -> Axes: @overload def subplots( - nrows: Literal[1] = ..., - ncols: Literal[1] = ..., - *, - sharex: bool | Literal["none", "all", "row", "col"] = ..., - sharey: bool | Literal["none", "all", "row", "col"] = ..., - squeeze: Literal[True] = ..., - width_ratios: Sequence[float] | None = ..., - height_ratios: Sequence[float] | None = ..., - subplot_kw: dict[str, Any] | None = ..., - gridspec_kw: dict[str, Any] | None = ..., - **fig_kw + nrows: Literal[1] = ..., + ncols: Literal[1] = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: Literal[True] = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + **fig_kw ) -> tuple[Figure, Axes]: ... @overload def subplots( - nrows: int = ..., - ncols: int = ..., - *, - sharex: bool | Literal["none", "all", "row", "col"] = ..., - sharey: bool | Literal["none", "all", "row", "col"] = ..., - squeeze: Literal[False], - width_ratios: Sequence[float] | None = ..., - height_ratios: Sequence[float] | None = ..., - subplot_kw: dict[str, Any] | None = ..., - gridspec_kw: dict[str, Any] | None = ..., - **fig_kw + nrows: int = ..., + ncols: int = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: Literal[False], + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + **fig_kw ) -> tuple[Figure, np.ndarray]: # TODO numpy/numpy#24738 ... @overload def subplots( - nrows: int = ..., - ncols: int = ..., - *, - sharex: bool | Literal["none", "all", "row", "col"] = ..., - sharey: bool | Literal["none", "all", "row", "col"] = ..., - squeeze: bool = ..., - width_ratios: Sequence[float] | None = ..., - height_ratios: Sequence[float] | None = ..., - subplot_kw: dict[str, Any] | None = ..., - gridspec_kw: dict[str, Any] | None = ..., - **fig_kw + nrows: int = ..., + ncols: int = ..., + *, + sharex: bool | Literal["none", "all", "row", "col"] = ..., + sharey: bool | Literal["none", "all", "row", "col"] = ..., + squeeze: bool = ..., + width_ratios: Sequence[float] | None = ..., + height_ratios: Sequence[float] | None = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + **fig_kw ) -> tuple[Figure, Any]: ... def subplots( - nrows: int = 1, ncols: int = 1, *, - sharex: bool | Literal["none", "all", "row", "col"] = False, - sharey: bool | Literal["none", "all", "row", "col"] = False, - squeeze: bool = True, - width_ratios: Sequence[float] | None = None, - height_ratios: Sequence[float] | None = None, - subplot_kw: dict[str, Any] | None = None, - gridspec_kw: dict[str, Any] | None = None, - **fig_kw + nrows: int = 1, ncols: int = 1, *, + sharex: bool | Literal["none", "all", "row", "col"] = False, + sharey: bool | Literal["none", "all", "row", "col"] = False, + squeeze: bool = True, + width_ratios: Sequence[float] | None = None, + height_ratios: Sequence[float] | None = None, + subplot_kw: dict[str, Any] | None = None, + gridspec_kw: dict[str, Any] | None = None, + **fig_kw ) -> tuple[Figure, Any]: """ Create a figure and a set of subplots. @@ -1814,66 +1815,66 @@ def subplots( @overload def subplot_mosaic( - mosaic: str, - *, - sharex: bool = ..., - sharey: bool = ..., - width_ratios: ArrayLike | None = ..., - height_ratios: ArrayLike | None = ..., - empty_sentinel: str = ..., - subplot_kw: dict[str, Any] | None = ..., - gridspec_kw: dict[str, Any] | None = ..., - per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] | None = ..., - **fig_kw: Any + mosaic: str, + *, + sharex: bool = ..., + sharey: bool = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + empty_sentinel: str = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] | None = ..., + **fig_kw: Any ) -> tuple[Figure, dict[str, matplotlib.axes.Axes]]: ... @overload def subplot_mosaic( - mosaic: list[HashableList[_T]], - *, - sharex: bool = ..., - sharey: bool = ..., - width_ratios: ArrayLike | None = ..., - height_ratios: ArrayLike | None = ..., - empty_sentinel: _T = ..., - subplot_kw: dict[str, Any] | None = ..., - gridspec_kw: dict[str, Any] | None = ..., - per_subplot_kw: dict[_T | tuple[_T, ...], dict[str, Any]] | None = ..., - **fig_kw: Any + mosaic: list[HashableList[_T]], + *, + sharex: bool = ..., + sharey: bool = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + empty_sentinel: _T = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + per_subplot_kw: dict[_T | tuple[_T, ...], dict[str, Any]] | None = ..., + **fig_kw: Any ) -> tuple[Figure, dict[_T, matplotlib.axes.Axes]]: ... @overload def subplot_mosaic( - mosaic: list[HashableList[Hashable]], - *, - sharex: bool = ..., - sharey: bool = ..., - width_ratios: ArrayLike | None = ..., - height_ratios: ArrayLike | None = ..., - empty_sentinel: Any = ..., - subplot_kw: dict[str, Any] | None = ..., - gridspec_kw: dict[str, Any] | None = ..., - per_subplot_kw: dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = ..., - **fig_kw: Any + mosaic: list[HashableList[Hashable]], + *, + sharex: bool = ..., + sharey: bool = ..., + width_ratios: ArrayLike | None = ..., + height_ratios: ArrayLike | None = ..., + empty_sentinel: Any = ..., + subplot_kw: dict[str, Any] | None = ..., + gridspec_kw: dict[str, Any] | None = ..., + per_subplot_kw: dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = ..., + **fig_kw: Any ) -> tuple[Figure, dict[Hashable, matplotlib.axes.Axes]]: ... def subplot_mosaic( - mosaic: str | list[HashableList[_T]] | list[HashableList[Hashable]], - *, - sharex: bool = False, - sharey: bool = False, - width_ratios: ArrayLike | None = None, - height_ratios: ArrayLike | None = None, - empty_sentinel: Any = '.', - subplot_kw: dict[str, Any] | None = None, - gridspec_kw: dict[str, Any] | None = None, - per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] | - dict[_T | tuple[_T, ...], dict[str, Any]] | - dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = None, - **fig_kw: Any + mosaic: str | list[HashableList[_T]] | list[HashableList[Hashable]], + *, + sharex: bool = False, + sharey: bool = False, + width_ratios: ArrayLike | None = None, + height_ratios: ArrayLike | None = None, + empty_sentinel: Any = '.', + subplot_kw: dict[str, Any] | None = None, + gridspec_kw: dict[str, Any] | None = None, + per_subplot_kw: dict[str | tuple[str, ...], dict[str, Any]] | + dict[_T | tuple[_T, ...], dict[str, Any]] | + dict[Hashable | tuple[Hashable, ...], dict[str, Any]] | None = None, + **fig_kw: Any ) -> tuple[Figure, dict[str, matplotlib.axes.Axes]] | \ tuple[Figure, dict[_T, matplotlib.axes.Axes]] | \ tuple[Figure, dict[Hashable, matplotlib.axes.Axes]]: @@ -1990,10 +1991,10 @@ def subplot_mosaic( def subplot2grid( - shape: tuple[int, int], loc: tuple[int, int], - rowspan: int = 1, colspan: int = 1, - fig: Figure | None = None, - **kwargs + shape: tuple[int, int], loc: tuple[int, int], + rowspan: int = 1, colspan: int = 1, + fig: Figure | None = None, + **kwargs ) -> matplotlib.axes.Axes: """ Create a subplot at a specific location inside a regular grid. @@ -2116,15 +2117,89 @@ def box(on: bool | None = None) -> None: on = not ax.get_frame_on() ax.set_frame_on(on) - ## Axis ## + +def xlim(*args, **kwargs) -> tuple[float, float]: + """ + Get or set the x limits of the current Axes. + + Call signatures:: + + left, right = xlim() # return the current xlim + xlim((left, right)) # set the xlim to left, right + xlim(left, right) # set the xlim to left, right + + If you do not specify args, you can pass *left* or *right* as kwargs, + i.e.:: + + xlim(right=3) # adjust the right leaving left unchanged + xlim(left=1) # adjust the left leaving right unchanged + + Setting limits turns autoscaling off for the x-axis. + + Returns + ------- + left, right + A tuple of the new x-axis limits. + + Notes + ----- + Calling this function with no arguments (e.g. ``xlim()``) is the pyplot + equivalent of calling `~.Axes.get_xlim` on the current Axes. + Calling this function with arguments is the pyplot equivalent of calling + `~.Axes.set_xlim` on the current Axes. All arguments are passed though. + """ + ax = gca() + if not args and not kwargs: + return ax.get_xlim() + ret = ax.set_xlim(*args, **kwargs) + return ret + + +def ylim(*args, **kwargs) -> tuple[float, float]: + """ + Get or set the y-limits of the current Axes. + + Call signatures:: + + bottom, top = ylim() # return the current ylim + ylim((bottom, top)) # set the ylim to bottom, top + ylim(bottom, top) # set the ylim to bottom, top + + If you do not specify args, you can alternatively pass *bottom* or + *top* as kwargs, i.e.:: + + ylim(top=3) # adjust the top leaving bottom unchanged + ylim(bottom=1) # adjust the bottom leaving top unchanged + + Setting limits turns autoscaling off for the y-axis. + + Returns + ------- + bottom, top + A tuple of the new y-axis limits. + + Notes + ----- + Calling this function with no arguments (e.g. ``ylim()``) is the pyplot + equivalent of calling `~.Axes.get_ylim` on the current Axes. + Calling this function with arguments is the pyplot equivalent of calling + `~.Axes.set_ylim` on the current Axes. All arguments are passed though. + """ + ax = gca() + if not args and not kwargs: + return ax.get_ylim() + ret = ax.set_ylim(*args, **kwargs) + return ret + + def xticks( - ticks: ArrayLike | None = None, - labels: Sequence[str] | None = None, - *, - minor: bool = False, - **kwargs + ticks: ArrayLike | None = None, + labels: Sequence[str] | None = None, + *, + minor: bool = False, + **kwargs ) -> tuple[list[Tick] | np.ndarray, list[Text]]: """ Get or set the current tick locations and labels of the x-axis. @@ -2206,11 +2281,11 @@ def xticks( def yticks( - ticks: ArrayLike | None = None, - labels: Sequence[str] | None = None, - *, - minor: bool = False, - **kwargs + ticks: ArrayLike | None = None, + labels: Sequence[str] | None = None, + *, + minor: bool = False, + **kwargs ) -> tuple[list[Tick] | np.ndarray, list[Text]]: """ Get or set the current tick locations and labels of the y-axis. @@ -2291,11 +2366,11 @@ def yticks( def rgrids( - radii: ArrayLike | None = None, - labels: Sequence[str | Text] | None = None, - angle: float | None = None, - fmt: str | None = None, - **kwargs + radii: ArrayLike | None = None, + labels: Sequence[str | Text] | None = None, + angle: float | None = None, + fmt: str | None = None, + **kwargs ) -> tuple[list[Line2D], list[Text]]: """ Get or set the radial gridlines on the current polar plot. @@ -2370,10 +2445,10 @@ def rgrids( def thetagrids( - angles: ArrayLike | None = None, - labels: Sequence[str | Text] | None = None, - fmt: str | None = None, - **kwargs + angles: ArrayLike | None = None, + labels: Sequence[str | Text] | None = None, + fmt: str | None = None, + **kwargs ) -> tuple[list[Line2D], list[Text]]: """ Get or set the theta gridlines on the current polar plot. @@ -2466,8 +2541,8 @@ def _get_pyplot_commands() -> list[str]: return sorted( name for name, obj in globals().items() if not name.startswith('_') and name not in exclude - and inspect.isfunction(obj) - and inspect.getmodule(obj) is this_module) + and inspect.isfunction(obj) + and inspect.getmodule(obj) is this_module) ## Plotting part 1: manually generated functions and wrappers ## @@ -2475,10 +2550,10 @@ def _get_pyplot_commands() -> list[str]: @_copy_docstring_and_deprecators(Figure.colorbar) def colorbar( - mappable: ScalarMappable | ColorizingArtist | None = None, - cax: matplotlib.axes.Axes | None = None, - ax: matplotlib.axes.Axes | Iterable[matplotlib.axes.Axes] | None = None, - **kwargs + mappable: ScalarMappable | ColorizingArtist | None = None, + cax: matplotlib.axes.Axes | None = None, + ax: matplotlib.axes.Axes | Iterable[matplotlib.axes.Axes] | None = None, + **kwargs ) -> Colorbar: if mappable is None: mappable = gci() @@ -2573,7 +2648,7 @@ def imread( @_copy_docstring_and_deprecators(matplotlib.image.imsave) def imsave( - fname: str | os.PathLike | BinaryIO, arr: ArrayLike, **kwargs + fname: str | os.PathLike | BinaryIO, arr: ArrayLike, **kwargs ) -> None: matplotlib.image.imsave(fname, arr, **kwargs) @@ -2696,12 +2771,11 @@ def polar( requested_backend = None if requested_backend is None else requested_backend.lower() available_backends = backend_registry.list_builtin(BackendFilter.INTERACTIVE) if ( - requested_backend in (set(available_backends) - {'webagg', 'nbagg'}) - and cbook._get_running_interactive_framework() + requested_backend in (set(available_backends) - {'webagg', 'nbagg'}) + and cbook._get_running_interactive_framework() ): rcParams._set("backend", rcsetup._auto_backend_sentinel) - # fmt: on ################# REMAINING CONTENT GENERATED BY boilerplate.py ############## From 64e7921b0b3f56c88c1f449a4f2081e862289279 Mon Sep 17 00:00:00 2001 From: Corenthin ZOZOR Date: Fri, 20 Jun 2025 21:12:24 +0200 Subject: [PATCH 11/12] Format with ruff --- lib/matplotlib/tests/test_pyplot.py | 9 ++++++++- tools/boilerplate.py | 12 ++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/tests/test_pyplot.py b/lib/matplotlib/tests/test_pyplot.py index 8bfc1951f4bb..5254ed65a845 100644 --- a/lib/matplotlib/tests/test_pyplot.py +++ b/lib/matplotlib/tests/test_pyplot.py @@ -512,7 +512,14 @@ def get_signature(meth): params = dict(inspect.signature(meth).parameters) args = node.args - for param in (*args.posonlyargs, *args.args, args.vararg, *args.kwonlyargs, args.kwarg): + allargs = ( + *args.posonlyargs, + *args.args, + args.vararg, + *args.kwonlyargs, + args.kwarg + ) + for param in allargs: if param is None: continue if param.annotation is None: diff --git a/tools/boilerplate.py b/tools/boilerplate.py index 72e4100f0f16..778729abe12d 100644 --- a/tools/boilerplate.py +++ b/tools/boilerplate.py @@ -129,7 +129,13 @@ def __repr__(self): return self._repr -def generate_function(name, called_fullname, template, gettersetter=False, **kwargs): +def generate_function( + name, + called_fullname, + template, + gettersetter=False, + **kwargs +): """ Create a wrapper function *pyplot_name* calling *call_name*. @@ -199,7 +205,9 @@ def call_param(param: Parameter): return '**{0}' return None - call = '(' + ', '.join((call_param(param)).format(param.name) for param in params) + ')' + call = '(' + ', '.join( + (call_param(param)).format(param.name) for param in params + ) + ')' return_statement = 'return ' if has_return_value else '' # Bail out in case of name collision. for reserved in ('gca', 'gci', 'gcf', '__ret'): From 00624eca09bbbe2df1c9f81de648250511d06e51 Mon Sep 17 00:00:00 2001 From: Corenthin ZOZOR Date: Fri, 20 Jun 2025 21:14:49 +0200 Subject: [PATCH 12/12] Re-remove (bad old revert) --- lib/matplotlib/pyplot.py | 74 ---------------------------------------- 1 file changed, 74 deletions(-) diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index ddce303c1efc..9e213330a637 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -2120,80 +2120,6 @@ def box(on: bool | None = None) -> None: ## Axis ## -def xlim(*args, **kwargs) -> tuple[float, float]: - """ - Get or set the x limits of the current Axes. - - Call signatures:: - - left, right = xlim() # return the current xlim - xlim((left, right)) # set the xlim to left, right - xlim(left, right) # set the xlim to left, right - - If you do not specify args, you can pass *left* or *right* as kwargs, - i.e.:: - - xlim(right=3) # adjust the right leaving left unchanged - xlim(left=1) # adjust the left leaving right unchanged - - Setting limits turns autoscaling off for the x-axis. - - Returns - ------- - left, right - A tuple of the new x-axis limits. - - Notes - ----- - Calling this function with no arguments (e.g. ``xlim()``) is the pyplot - equivalent of calling `~.Axes.get_xlim` on the current Axes. - Calling this function with arguments is the pyplot equivalent of calling - `~.Axes.set_xlim` on the current Axes. All arguments are passed though. - """ - ax = gca() - if not args and not kwargs: - return ax.get_xlim() - ret = ax.set_xlim(*args, **kwargs) - return ret - - -def ylim(*args, **kwargs) -> tuple[float, float]: - """ - Get or set the y-limits of the current Axes. - - Call signatures:: - - bottom, top = ylim() # return the current ylim - ylim((bottom, top)) # set the ylim to bottom, top - ylim(bottom, top) # set the ylim to bottom, top - - If you do not specify args, you can alternatively pass *bottom* or - *top* as kwargs, i.e.:: - - ylim(top=3) # adjust the top leaving bottom unchanged - ylim(bottom=1) # adjust the bottom leaving top unchanged - - Setting limits turns autoscaling off for the y-axis. - - Returns - ------- - bottom, top - A tuple of the new y-axis limits. - - Notes - ----- - Calling this function with no arguments (e.g. ``ylim()``) is the pyplot - equivalent of calling `~.Axes.get_ylim` on the current Axes. - Calling this function with arguments is the pyplot equivalent of calling - `~.Axes.set_ylim` on the current Axes. All arguments are passed though. - """ - ax = gca() - if not args and not kwargs: - return ax.get_ylim() - ret = ax.set_ylim(*args, **kwargs) - return ret - - def xticks( ticks: ArrayLike | None = None, labels: Sequence[str] | None = None,