diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index 0e199889cb07..fc542989066f 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -56,3 +56,6 @@ matplotlib\.animation\.EventSourceProtocol # Avoid a regression in NewType handling for stubtest # https://github.com/python/mypy/issues/19877 matplotlib\.ft2font\.GlyphIndexType\.__init__ + +# 3.12 deprecation +matplotlib\.axes\._base\._AxesBase\.ArtistList diff --git a/doc/api/artist_api.rst b/doc/api/artist_api.rst index f256d2b7164e..e3b9bf1da2f7 100644 --- a/doc/api/artist_api.rst +++ b/doc/api/artist_api.rst @@ -200,4 +200,14 @@ Functions getp setp kwdoc + +Helper classes +============== + +.. autosummary:: + :template: autosummary.rst + :toctree: _as_gen + :nosignatures: + ArtistInspector + ArtistList diff --git a/doc/api/axes__base_api.rst b/doc/api/axes__base_api.rst new file mode 100644 index 000000000000..7c22bb898527 --- /dev/null +++ b/doc/api/axes__base_api.rst @@ -0,0 +1,12 @@ +************************* +``matplotlib.axes._base`` +************************* + +.. This is a stub which is only included to make inheritance links work. It + deliberately does not include any docstrings, so the class-doc-from directive + specifies a non-existent docstring. + +.. autoclass:: matplotlib.axes._base._AxesBase + :no-members: + :class-doc-from: class + :show-inheritance: diff --git a/doc/api/axes_api.rst b/doc/api/axes_api.rst index 1f5f3e403bee..e62a73fb3a60 100644 --- a/doc/api/axes_api.rst +++ b/doc/api/axes_api.rst @@ -632,5 +632,3 @@ Other Axes.get_figure Axes.figure Axes.remove - -.. autoclass:: matplotlib.axes.Axes.ArtistList diff --git a/doc/api/image_api.rst b/doc/api/image_api.rst index df3177395eef..75589aea1797 100644 --- a/doc/api/image_api.rst +++ b/doc/api/image_api.rst @@ -6,3 +6,7 @@ :members: :undoc-members: :show-inheritance: + +.. autoclass:: _ImageBase + :class-doc-from: class + :show-inheritance: diff --git a/doc/api/index.rst b/doc/api/index.rst index 04c0e279a4fe..96339860173f 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -146,6 +146,7 @@ Alphabetical list of modules: _type1font.rst _tight_bbox_api.rst _tight_layout_api.rst + axes__base_api.rst toolkits/mplot3d.rst toolkits/axes_grid1.rst toolkits/axisartist.rst diff --git a/doc/api/next_api_changes/deprecations/31794-REC.rst b/doc/api/next_api_changes/deprecations/31794-REC.rst new file mode 100644 index 000000000000..80cf2a6e1ed7 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/31794-REC.rst @@ -0,0 +1,3 @@ +The ``Axes.AxesList`` attribute +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +... is deprecated. Use `.artist.ArtistList` instead. diff --git a/doc/api/text_api.rst b/doc/api/text_api.rst index af37e5c526a3..03f35b4c835e 100644 --- a/doc/api/text_api.rst +++ b/doc/api/text_api.rst @@ -12,6 +12,9 @@ :undoc-members: :show-inheritance: +.. autoclass:: matplotlib.text._AnnotationBase + :class-doc-from: class + .. autoclass:: matplotlib.text.Annotation :members: :undoc-members: diff --git a/galleries/tutorials/artists.py b/galleries/tutorials/artists.py index b3440d71fe7f..867502fb27a4 100644 --- a/galleries/tutorials/artists.py +++ b/galleries/tutorials/artists.py @@ -562,13 +562,13 @@ class in the Matplotlib API, and the one you will be working with most # ============== ========================================= # Axes attribute Description # ============== ========================================= -# artists An `.ArtistList` of `.Artist` instances +# artists An `~.artist.ArtistList` of `.Artist` instances # patch `.Rectangle` instance for Axes background -# collections An `.ArtistList` of `.Collection` instances -# images An `.ArtistList` of `.AxesImage` -# lines An `.ArtistList` of `.Line2D` instances -# patches An `.ArtistList` of `.Patch` instances -# texts An `.ArtistList` of `.Text` instances +# collections An `~.artist.ArtistList` of `.Collection` instances +# images An `~.artist.ArtistList` of `.AxesImage` +# lines An `~.artist.ArtistList` of `.Line2D` instances +# patches An `~.artist.ArtistList` of `.Patch` instances +# texts An `~.artist.ArtistList` of `.Text` instances # xaxis A `matplotlib.axis.XAxis` instance # yaxis A `matplotlib.axis.YAxis` instance # ============== ========================================= diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 88e38634b5b1..c91ddce642b3 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -1,4 +1,5 @@ from collections import namedtuple +from collections.abc import Sequence import contextlib from functools import cache, reduce, wraps import inspect @@ -1789,6 +1790,75 @@ def pprint_getters(self): return lines +class ArtistList(Sequence): + """ + A sublist of Axes children based on their type. + + The type-specific children sublists were made immutable in Matplotlib + 3.7. In the future these artist lists may be replaced by tuples. Use + as if this is a tuple already. + """ + def __init__(self, axes, prop_name, valid_types=None, invalid_types=None): + """ + Parameters + ---------- + axes : `~matplotlib.axes.Axes` + The Axes from which this sublist will pull the children + Artists. + prop_name : str + The property name used to access this sublist from the Axes; + used to generate deprecation warnings. + valid_types : list of type, optional + A list of types that determine which children will be returned + by this sublist. If specified, then the Artists in the sublist + must be instances of any of these types. If unspecified, then + any type of Artist is valid (unless limited by + *invalid_types*.) + invalid_types : tuple, optional + A list of types that determine which children will *not* be + returned by this sublist. If specified, then Artists in the + sublist will never be an instance of these types. Otherwise, no + types will be excluded. + """ + self._axes = axes + self._prop_name = prop_name + self._type_check = lambda artist: ( + (not valid_types or isinstance(artist, valid_types)) and + (not invalid_types or not isinstance(artist, invalid_types)) + ) + + def __repr__(self): + return f'' + + def __len__(self): + return sum(self._type_check(artist) + for artist in self._axes._children) + + def __iter__(self): + for artist in list(self._axes._children): + if self._type_check(artist): + yield artist + + def __getitem__(self, key): + return [artist + for artist in self._axes._children + if self._type_check(artist)][key] + + def __add__(self, other): + if isinstance(other, (list, ArtistList)): + return [*self, *other] + if isinstance(other, (tuple, ArtistList)): + return (*self, *other) + return NotImplemented + + def __radd__(self, other): + if isinstance(other, list): + return other + list(self) + if isinstance(other, tuple): + return other + tuple(self) + return NotImplemented + + def getp(obj, property=None): """ Return the value of an `.Artist`'s *property*, or print all of them. diff --git a/lib/matplotlib/artist.pyi b/lib/matplotlib/artist.pyi index 7b8b0c36be69..d2b25a4a22c4 100644 --- a/lib/matplotlib/artist.pyi +++ b/lib/matplotlib/artist.pyi @@ -14,7 +14,7 @@ from .transforms import ( import numpy as np -from collections.abc import Callable, Iterable +from collections.abc import Callable, Iterable, Iterator, Sequence from typing import Any, Literal, NamedTuple, TextIO, overload, TypeVar from numpy.typing import ArrayLike @@ -189,6 +189,36 @@ class ArtistInspector: def properties(self) -> dict[str, Any]: ... def pprint_getters(self) -> list[str]: ... + +class ArtistList(Sequence[_T_Artist]): + def __init__( + self, + axes: _AxesBase, + prop_name: str, + valid_types: type | Iterable[type] | None = ..., + invalid_types: type | Iterable[type] | None = ..., + ) -> None: ... + def __len__(self) -> int: ... + def __iter__(self) -> Iterator[_T_Artist]: ... + @overload + def __getitem__(self, key: int) -> _T_Artist: ... + @overload + def __getitem__(self, key: slice) -> list[_T_Artist]: ... + + @overload + def __add__(self, other: ArtistList[_T_Artist]) -> list[_T_Artist]: ... + @overload + def __add__(self, other: list[Any]) -> list[Any]: ... + @overload + def __add__(self, other: tuple[Any]) -> tuple[Any]: ... + + @overload + def __radd__(self, other: ArtistList[_T_Artist]) -> list[_T_Artist]: ... + @overload + def __radd__(self, other: list[Any]) -> list[Any]: ... + @overload + def __radd__(self, other: tuple[Any]) -> tuple[Any]: ... + def getp(obj: Artist, property: str | None = ...) -> Any: ... get = getp diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index d2589cfc74d3..9a2f575becb5 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -1,4 +1,4 @@ -from collections.abc import Iterable, Sequence +from collections.abc import Iterable from contextlib import ExitStack import functools import inspect @@ -1481,105 +1481,40 @@ def cla(self): else: self.clear() - class ArtistList(Sequence): - """ - A sublist of Axes children based on their type. - - The type-specific children sublists were made immutable in Matplotlib - 3.7. In the future these artist lists may be replaced by tuples. Use - as if this is a tuple already. - """ - def __init__(self, axes, prop_name, - valid_types=None, invalid_types=None): - """ - Parameters - ---------- - axes : `~matplotlib.axes.Axes` - The Axes from which this sublist will pull the children - Artists. - prop_name : str - The property name used to access this sublist from the Axes; - used to generate deprecation warnings. - valid_types : list of type, optional - A list of types that determine which children will be returned - by this sublist. If specified, then the Artists in the sublist - must be instances of any of these types. If unspecified, then - any type of Artist is valid (unless limited by - *invalid_types*.) - invalid_types : tuple, optional - A list of types that determine which children will *not* be - returned by this sublist. If specified, then Artists in the - sublist will never be an instance of these types. Otherwise, no - types will be excluded. - """ - self._axes = axes - self._prop_name = prop_name - self._type_check = lambda artist: ( - (not valid_types or isinstance(artist, valid_types)) and - (not invalid_types or not isinstance(artist, invalid_types)) - ) - - def __repr__(self): - return f'' - - def __len__(self): - return sum(self._type_check(artist) - for artist in self._axes._children) - - def __iter__(self): - for artist in list(self._axes._children): - if self._type_check(artist): - yield artist - - def __getitem__(self, key): - return [artist - for artist in self._axes._children - if self._type_check(artist)][key] - - def __add__(self, other): - if isinstance(other, (list, _AxesBase.ArtistList)): - return [*self, *other] - if isinstance(other, (tuple, _AxesBase.ArtistList)): - return (*self, *other) - return NotImplemented - - def __radd__(self, other): - if isinstance(other, list): - return other + list(self) - if isinstance(other, tuple): - return other + tuple(self) - return NotImplemented + @_api.deprecated('3.12', alternative='matplotlib.artist.ArtistList') + @property + def ArtistList(self): + return martist.ArtistList @property def artists(self): - return self.ArtistList(self, 'artists', invalid_types=( + return martist.ArtistList(self, 'artists', invalid_types=( mcoll.Collection, mimage.AxesImage, mlines.Line2D, mpatches.Patch, mtable.Table, mtext.Text)) @property def collections(self): - return self.ArtistList(self, 'collections', - valid_types=mcoll.Collection) + return martist.ArtistList(self, 'collections', valid_types=mcoll.Collection) @property def images(self): - return self.ArtistList(self, 'images', valid_types=mimage.AxesImage) + return martist.ArtistList(self, 'images', valid_types=mimage.AxesImage) @property def lines(self): - return self.ArtistList(self, 'lines', valid_types=mlines.Line2D) + return martist.ArtistList(self, 'lines', valid_types=mlines.Line2D) @property def patches(self): - return self.ArtistList(self, 'patches', valid_types=mpatches.Patch) + return martist.ArtistList(self, 'patches', valid_types=mpatches.Patch) @property def tables(self): - return self.ArtistList(self, 'tables', valid_types=mtable.Table) + return martist.ArtistList(self, 'tables', valid_types=mtable.Table) @property def texts(self): - return self.ArtistList(self, 'texts', valid_types=mtext.Text) + return martist.ArtistList(self, 'texts', valid_types=mtext.Text) def get_facecolor(self): """Get the facecolor of the Axes.""" diff --git a/lib/matplotlib/axes/_base.pyi b/lib/matplotlib/axes/_base.pyi index 4a70405346a5..8bada00030fb 100644 --- a/lib/matplotlib/axes/_base.pyi +++ b/lib/matplotlib/axes/_base.pyi @@ -1,9 +1,9 @@ import matplotlib.artist as martist import datetime -from collections.abc import Callable, Iterable, Iterator, Sequence +from collections.abc import Callable, Iterable, Sequence from matplotlib import cbook -from matplotlib.artist import Artist +from matplotlib.artist import Artist, ArtistList from matplotlib.axes import Axes from matplotlib.axis import Axis, XAxis, YAxis, Tick from matplotlib.backend_bases import RendererBase, MouseButton, MouseEvent @@ -27,10 +27,9 @@ from cycler import Cycler import numpy as np from numpy.typing import ArrayLike -from typing import Any, Literal, TypeVar, overload +from typing import Any, Literal, overload from matplotlib.typing import ColorType -_T = TypeVar("_T", bound=Artist) class _axis_method_wrapper: attr_name: str @@ -136,49 +135,20 @@ class _AxesBase(martist.Artist): def clear(self) -> None: ... def cla(self) -> None: ... - class ArtistList(Sequence[_T]): - def __init__( - self, - axes: _AxesBase, - prop_name: str, - valid_types: type | Iterable[type] | None = ..., - invalid_types: type | Iterable[type] | None = ..., - ) -> None: ... - def __len__(self) -> int: ... - def __iter__(self) -> Iterator[_T]: ... - @overload - def __getitem__(self, key: int) -> _T: ... - @overload - def __getitem__(self, key: slice) -> list[_T]: ... - - @overload - def __add__(self, other: _AxesBase.ArtistList[_T]) -> list[_T]: ... - @overload - def __add__(self, other: list[Any]) -> list[Any]: ... - @overload - def __add__(self, other: tuple[Any]) -> tuple[Any]: ... - - @overload - def __radd__(self, other: _AxesBase.ArtistList[_T]) -> list[_T]: ... - @overload - def __radd__(self, other: list[Any]) -> list[Any]: ... - @overload - def __radd__(self, other: tuple[Any]) -> tuple[Any]: ... - @property - def artists(self) -> _AxesBase.ArtistList[Artist]: ... + def artists(self) -> ArtistList[Artist]: ... @property - def collections(self) -> _AxesBase.ArtistList[Collection]: ... + def collections(self) -> ArtistList[Collection]: ... @property - def images(self) -> _AxesBase.ArtistList[AxesImage]: ... + def images(self) -> ArtistList[AxesImage]: ... @property - def lines(self) -> _AxesBase.ArtistList[Line2D]: ... + def lines(self) -> ArtistList[Line2D]: ... @property - def patches(self) -> _AxesBase.ArtistList[Patch]: ... + def patches(self) -> ArtistList[Patch]: ... @property - def tables(self) -> _AxesBase.ArtistList[Table]: ... + def tables(self) -> ArtistList[Table]: ... @property - def texts(self) -> _AxesBase.ArtistList[Text]: ... + def texts(self) -> ArtistList[Text]: ... def get_facecolor(self) -> ColorType: ... def set_facecolor(self, color: ColorType | None) -> None: ... @overload diff --git a/lib/matplotlib/text.py b/lib/matplotlib/text.py index 9c6478f9c7df..23eecaa4a1e8 100644 --- a/lib/matplotlib/text.py +++ b/lib/matplotlib/text.py @@ -1677,6 +1677,7 @@ def __call__(self, renderer): class _AnnotationBase: + """Mixin class for annotations.""" def __init__(self, xy, xycoords='data',