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, pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy