Skip to content
Closed
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
51 changes: 45 additions & 6 deletions lib/matplotlib/artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

_log = logging.getLogger(__name__)

# a tombstone sentinel to ensure we never reset the axes or figure on an Artist
_tombstone = object()


def _prevent_rasterization(draw):
# We assume that by default artists are not allowed to rasterize (unless
Expand Down Expand Up @@ -264,7 +267,7 @@ def remove(self):
if (fig := self.get_figure(root=False)) is not None:
if not _ax_flag:
fig.stale = True
self._parent_figure = None
self._parent_figure = _tombstone

else:
raise NotImplementedError('cannot remove artist')
Expand Down Expand Up @@ -301,18 +304,35 @@ def convert_yunits(self, y):
@property
def axes(self):
"""The `~.axes.Axes` instance the artist resides in, or *None*."""
return self._axes
return self._axes if self._axes is not _tombstone else None

@axes.setter
def axes(self, new_axes):
if (new_axes is not None and self._axes is not None
and new_axes != self._axes):
raise ValueError("Can not reset the Axes. You are probably trying to reuse "
"an artist in more than one Axes which is not supported")
if self._axes is _tombstone:
extra = (
f"The artist {self!r} has had its Axes reset to None " +
"but was previously included in one."
)
else:
extra = (
f"The artist {self!r} is currently in {self._axes!r} " +
f"in the figure {self._axes.figure!r}."
)
raise RuntimeError(
"Can not reset the Axes. You are probably " +
"trying to reuse an artist in more than one " +
"Axes which is not supported. " +
extra
)
if self._axes is not None and new_axes is None:
new_axes = _tombstone
self._axes = new_axes
if new_axes is not None and new_axes is not self:
self.stale_callback = _stale_axes_callback


@property
def stale(self):
"""
Expand Down Expand Up @@ -759,6 +779,9 @@ def get_figure(self, root=False):
If False, return the (Sub)Figure this artist is on. If True,
return the root Figure for a nested tree of SubFigures.
"""
if self._parent_figure is _tombstone:
return None

if root and self._parent_figure is not None:
return self._parent_figure.get_figure(root=True)

Expand Down Expand Up @@ -794,8 +817,24 @@ def set_figure(self, fig):
# is not allowed for the same reason as adding the same instance
# to more than one Axes
if self._parent_figure is not None:
raise RuntimeError("Can not put single artist in "
"more than one figure")
if fig is None:
self._parent_figure = _tombstone
return
if self._parent_figure is _tombstone:
extra = (
f"The artist {self!r} has had its Figure reset to None " +
"but was previously included in one."
)
else:
extra = (
f"The artist {self!r} is currently in {self._parent_figure!r}."
)
raise RuntimeError(
"Can not reset the figure. You are probably " +
"trying to reuse an artist in more than one " +
"Figure which is not supported. " +
extra
)
self._parent_figure = fig
if self._parent_figure and self._parent_figure is not self:
self.pchanged()
Expand Down
34 changes: 34 additions & 0 deletions lib/matplotlib/tests/test_artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,3 +623,37 @@ def test_get_figure():
assert ax.figure is sfig2
assert fig.figure is fig
assert sfig2.figure is fig


def test_no_reset():
fig, (ax1, ax2) = plt.subplots(2)
fig2 = plt.figure()
ln, = ax1.plot(range(5))
with pytest.raises(
RuntimeError, match=f"The artist {ln!r} is currently in {ax1!r}"
):
ax2.add_line(ln)
with pytest.raises(
RuntimeError, match=f"The artist {ln!r} is currently in {ax1!r}"
):
ln.axes = ax2
with pytest.raises(
RuntimeError, match=f"The artist {ln!r} is currently in {fig!r}"
):
ln.figure = fig2

ln.remove()
assert ln.figure is None
assert ln.axes is None
with pytest.raises(
RuntimeError, match=f"The artist {ln!r} has had its Figure reset "
):
ax2.add_line(ln)

with pytest.raises(
RuntimeError, match=f"The artist {ln!r} has had its Axes reset "
):
ln.axes = ax2

assert ln.axes is None
assert ln.figure is None
3 changes: 2 additions & 1 deletion lib/matplotlib/tests/test_offsetbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,9 @@ def test_offsetbox_loc_codes():
'center': 10,
}
fig, ax = plt.subplots()
da = DrawingArea(100, 100)

for code in codes:
da = DrawingArea(100, 100)
anchored_box = AnchoredOffsetbox(loc=code, child=da)
ax.add_artist(anchored_box)
fig.canvas.draw()
Expand Down
Loading