Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/api/next_api_changes/deprecations/31746_REC.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Direct modification of ``(Sub)Figure`` artist lists
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Previously it was possible to modify the ``artists``, ``images``, ``lines``,
``legends``, ``patches`` and ``texts`` attributes of `.Figure` and `.SubFigure`
instances using standard `list` functionality. This is now deprecated.
Instead use `~.Figure.add_artist` to add an artist to the figure, or use the
artist's `~.Artist.remove` method to remove it.
2 changes: 1 addition & 1 deletion galleries/examples/widgets/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ def __init__(self, fig, menuitems):

item.set_extent(left, bottom, width, height, depth)

fig.artists.append(item)
fig.add_artist(item)
y0 -= maxh + MenuItem.pady

fig.canvas.mpl_connect('motion_notify_event', self.on_move)
Expand Down
43 changes: 25 additions & 18 deletions galleries/tutorials/artists.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,44 +314,51 @@ class in the Matplotlib API, and the one you will be working with most
#
#
# The figure also has its own ``images``, ``lines``, ``patches`` and ``text``
# attributes, which you can use to add primitives directly. When doing so, the
# default coordinate system for the ``Figure`` will simply be in pixels (which
# is not usually what you want). If you instead use Figure-level methods to add
# Artists (e.g., using `.Figure.text` to add text), then the default coordinate
# system will be "figure coordinates" where (0, 0) is the bottom-left of the
# figure and (1, 1) is the top-right of the figure.
#
# As with all ``Artist``\s, you can control this coordinate system by setting
# the transform property. You can explicitly use "figure coordinates" by
# setting the ``Artist`` transform to :attr:`!fig.transFigure`:
# attributes, which you can use to access any primitives that are its direct
# children. Artists may be added with the `~.Figure.add_artist` method.

import matplotlib.lines as lines

fig = plt.figure()

l1 = lines.Line2D([0, 1], [0, 1], transform=fig.transFigure, figure=fig)
l2 = lines.Line2D([0, 1], [1, 0], transform=fig.transFigure, figure=fig)
fig.lines.extend([l1, l2])
line1 = lines.Line2D([0, 1], [0, 1])
line2 = lines.Line2D([0, 1], [1, 0])
for line in line1, line2:
fig.add_artist(line)

plt.show()

# %%
#
# As a convenience for images and text, the helper methods `~.Figure.figimage` and
# `~.Figure.text` create the respective Artists and internally add them to the figure.
#
# As with all ``Artist``\s, you can control the coordinate system by setting
# the transform property (see :ref:`transforms_tutorial`). When using
# `~.Figure.figimage`, the default coordinate system is simply pixels. When
# using `~.Figure.text` or `~.Figure.add_artist`, the default coordinate system
# will be "figure coordinates" where (0, 0) is the bottom-left of the figure
# and (1, 1) is the top-right of the figure.
#
# %%
# Here is a summary of the Artists the Figure contains
#
# ================ ============================================================
# Figure attribute Description
# ================ ============================================================
# axes A list of `~.axes.Axes` instances
# subfigures A list of `.SubFigure` instances
# patch The `.Rectangle` background
# images A list of `.FigureImage` patches -
# images An `~.artist.ArtistList` of `.FigureImage` patches -
# useful for raw pixel display
# legends A list of Figure `.Legend` instances
# legends An `~.artist.ArtistList` of Figure `.Legend` instances
# (different from ``Axes.get_legend()``)
# lines A list of Figure `.Line2D` instances
# lines An `~.artist.ArtistList` of Figure `.Line2D` instances
# (rarely used, see ``Axes.lines``)
# patches A list of Figure `.Patch`\s
# patches An `~.artist.ArtistList` of Figure `.Patch`\s
# (rarely used, see ``Axes.patches``)
# texts A list Figure `.Text` instances
# texts An `~.artist.ArtistList` of Figure `.Text` instances
# artists An `~.artist.ArtistList` of all other `.Artist` instances
# ================ ============================================================
#
# .. _axes-container:
Expand Down
2 changes: 1 addition & 1 deletion galleries/users_explain/text/annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ def __call__(self, x0, y0, width, height, mutation_size):

# %%
# Similarly, you can define a custom `.ConnectionStyle` and a custom `.ArrowStyle`. View
# the source code at `.patches` to learn how each class is defined.
# the source code at `~matplotlib.patches` to learn how each class is defined.
#
# .. _annotation_with_custom_arrow:
#
Expand Down
30 changes: 26 additions & 4 deletions lib/matplotlib/_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import functools
import itertools
import pathlib
import sys
import warnings

from .deprecation import ( # noqa: F401
Expand Down Expand Up @@ -470,7 +471,28 @@ def warn_external(message, category=None):
"""
# Go to Python's `site-packages` or `lib` from an editable install.
basedir = pathlib.Path(__file__).parents[2]
skip_file_prefixes = (str(basedir / 'matplotlib'),
str(basedir / 'mpl_toolkits'))

warnings.warn(message, category, skip_file_prefixes=skip_file_prefixes)
skip_file_prefixes = (
str(basedir / 'matplotlib'),
str(basedir / 'mpl_toolkits'),
# If we subclass a collections.abc class, the user may call an abc method that
# calls our method. For example if we warn within insert on a MutableSequence,
# and the user calls append or extend.
'<frozen _collections_abc>')

stacklevel = 2
if sys.version_info[:2] < (3, 14):
# Including the collections.abc string in skip_file_prefixes is not yet honored.
# Add the relevant frame count to the stacklevel instead.
Comment on lines +483 to +485

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't find anything in the python changenotes to understand what changed here, but I have verified that the relevant test passes with python 3.14.0.

frame = sys._getframe()
Comment thread
QuLogic marked this conversation as resolved.
while True:
if frame.f_globals.get("__name__") == 'collections.abc':
stacklevel += 1

frame = frame.f_back
if frame is None:
break

del frame

warnings.warn(message, category, skip_file_prefixes=skip_file_prefixes,
stacklevel=stacklevel)
25 changes: 12 additions & 13 deletions lib/matplotlib/artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -1792,22 +1792,21 @@ def pprint_getters(self):

class ArtistList(Sequence):
"""
A sublist of Axes children based on their type.
A sublist of Axes or Figure children based on their type.

The type-specific children sublists were made immutable in Matplotlib
The Axes' 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):
def __init__(self, parent, prop_name, valid_types=None, invalid_types=None):
"""
Parameters
----------
axes : `~matplotlib.axes.Axes`
The Axes from which this sublist will pull the children
parent : `~matplotlib.axes.Axes` or `~matplotlib.figure.FigureBase`
The Axes or (Sub)Figure 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.
The property name used to access this sublist from the parent.
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
Expand All @@ -1820,28 +1819,28 @@ def __init__(self, axes, prop_name, valid_types=None, invalid_types=None):
sublist will never be an instance of these types. Otherwise, no
types will be excluded.
"""
self._axes = axes
self._parent = parent
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'<Axes.ArtistList of {len(self)} {self._prop_name}>'
parent_type = self._parent.__class__.__name__
return f'<{parent_type}.ArtistList of {len(self)} {self._prop_name}>'

def __len__(self):
return sum(self._type_check(artist)
for artist in self._axes._children)
return sum(self._type_check(artist) for artist in self._parent._children)

def __iter__(self):
for artist in list(self._axes._children):
for artist in list(self._parent._children):
if self._type_check(artist):
yield artist

def __getitem__(self, key):
return [artist
for artist in self._axes._children
for artist in self._parent._children
if self._type_check(artist)][key]

def __add__(self, other):
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/artist.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ class ArtistInspector:
class ArtistList[T: Artist](Sequence[T]):
def __init__(
self,
axes: _AxesBase,
parent: _AxesBase | Figure | SubFigure,
prop_name: str,
valid_types: type | Iterable[type] | None = ...,
invalid_types: type | Iterable[type] | None = ...,
Expand Down
Loading
Loading