Skip to content
190 changes: 109 additions & 81 deletions lib/matplotlib/pyplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -338,8 +338,8 @@

# 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: str) -> None:
return matplotlib.set_loglevel(level)

Check warning on line 342 in lib/matplotlib/pyplot.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/pyplot.py#L342

Added line #L342 was not covered by tests


@_copy_docstring_and_deprecators(Artist.findobj)
Expand Down Expand Up @@ -569,6 +569,14 @@
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:
"""
Expand Down Expand Up @@ -1251,11 +1259,11 @@


@_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

Expand Down Expand Up @@ -1393,6 +1401,22 @@

## 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:
"""
Expand Down Expand Up @@ -2096,80 +2120,6 @@
## 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,
Expand Down Expand Up @@ -2690,7 +2640,13 @@
return im


def polar(*args, **kwargs) -> list[Line2D]:
def polar(
*args: float | ArrayLike | str,
scalex: bool = True,
scaley: bool = True,
data=None,
**kwargs
) -> list[Line2D]:
"""
Make a polar plot.

Expand Down Expand Up @@ -2724,7 +2680,13 @@
)
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
Expand Down Expand Up @@ -4457,6 +4419,72 @@
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]:
...

Check warning on line 4426 in lib/matplotlib/pyplot.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/pyplot.py#L4426

Added line #L4426 was not covered by tests


# 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]:
...

Check warning on line 4441 in lib/matplotlib/pyplot.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/pyplot.py#L4441

Added line #L4441 was not covered by tests


# 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()

Check warning on line 4449 in lib/matplotlib/pyplot.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/pyplot.py#L4449

Added line #L4449 was not covered by tests

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]:
...

Check warning on line 4459 in lib/matplotlib/pyplot.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/pyplot.py#L4459

Added line #L4459 was not covered by tests


# 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]:
...

Check warning on line 4474 in lib/matplotlib/pyplot.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/pyplot.py#L4474

Added line #L4474 was not covered by tests


# 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()

Check warning on line 4482 in lib/matplotlib/pyplot.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/pyplot.py#L4482

Added line #L4482 was not covered by tests

ret = ax.set_ylim(*args, **kwargs)
return ret


# Autogenerated by boilerplate.py. Do not edit as changes will be lost.
def autumn() -> None:
"""
Expand Down
75 changes: 74 additions & 1 deletion lib/matplotlib/tests/test_pyplot.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import ast
import difflib
import inspect

import numpy as np
import sys
Expand Down Expand Up @@ -449,7 +451,6 @@


def test_figure_hook():

test_rc = {
'figure.hooks': ['matplotlib.tests.test_pyplot:figure_hook_example']
}
Expand Down Expand Up @@ -484,3 +485,75 @@

# 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):
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")

Check warning on line 504 in lib/matplotlib/tests/test_pyplot.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/tests/test_pyplot.py#L504

Added line #L504 was not covered by tests

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
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:
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())

Check warning on line 536 in lib/matplotlib/tests/test_pyplot.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/tests/test_pyplot.py#L536

Added line #L536 was not covered by tests

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 ValueError(f"{original_sig} is not an instance method")

Check warning on line 546 in lib/matplotlib/tests/test_pyplot.py

View check run for this annotation

Codecov / codecov/patch

lib/matplotlib/tests/test_pyplot.py#L546

Added line #L546 was not covered by tests

original_params = original_params.copy()
del original_params["self"]

assert plt_sig.parameters == 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)
Loading
Loading