Recommended way of deleting lines

Hello everyone,

in the past I always used something like this to delete lines from a plot and draw new ones:

fig = plt.figure()
ax = fig.add_subplot()

x = np.linspace(0, 1, 100)

ax.plot(x, np.sin(x))
ax.scatter(np.random.random(10), np.random.random(10))

ax.lines.clear()
ax.collections.clear()

After reading https://github.com/matplotlib/matplotlib/pull/18216 and the documentation changes in Document ArtistList by timhoffm · Pull Request #22177 · matplotlib/matplotlib · GitHub it looks like this is discouraged and won’t be possible in the future anymore. However I couldn’t find a way how this should be realized in the future. Especially if I have class structure where I only can access self.fig / self.ax and not the single Artists.

What will be the recommended way in the future to achieve this?

Thanks,
Jan

@Jaro That is a very good question and in writing this answer up I ended up opening FIX: make safe to add / remove artists during ArtistList iteration by tacaswell · Pull Request #22229 · matplotlib/matplotlib · GitHub to fix a bug I found!

If you want to clear the entire the entire axes you can do

ax.cla()

however that will also do things like rest the limits, tickers, etc

If you want to remove just the lines (or any of the other named Artists):

# PR linked above makes list unnecessary 
for art in list(ax.lines):
   art.remove()

I would also encourage programming styles where you do capture and track the individual artists when you create them, but I know that is not always easy. It is not an unfair case to say the Axes has them so why should the user also track them!


Is this something that you do often? I am becoming a bit worried that we have underestimated how well used the lines / collections / patches / … attributes are used.

On one hand, I am very wary of implementing part of some well know interface (it is pretty easy to make an API that is in the uncanny valley where it is enough like a well-known interface that it feels like it should “just work” but is enough different that you guess the behavior wrong half the time), on the other I think there is a case for these attributes keeping a clear or remove_all method.

The obvious ways to do this do lead to weird places (e.g. sub class tuple to add .clear() … but then ax1.lines.clear() works and ax2.lines.clear() works so should (ax2.lines + ax1.lines).clear() work? If not, tat is a bit awkward as for the most part we expect type-stability on +. If yes…you now are re-writing all of the __add__ methods now have to sort out what to do about (ax1.lines[:4] + ax2.lines).clear(). If no, that is a bit off because we expect (for the most part) type-stability on slicing and sensible type-promotion on +). If yes then now we end up also re-writing __getitem__ to make sure we return our sub-class in slice…

Sorry, that got very rambling.

@tacaswell Thank you very much for your comprehensive answer!

Indeed

ax.cla()

is not what we usually want while your proposed for-loop looks good.

To clarify our use case: We mainly use this in the context of GUI or user interactions with a plot where it stays the same but its content changes. While I don’t know how widespread this is, the usage of lines etc. and even “wrong solutions” (in the future) for deleting them are very widespread if you search for something like “matplotlib remove all lines”. Another option one finds is

ax.lines = []

which is even worse and already stopped working with 3.5. (Which is indeed the reason I noticed the change.) After some research I found the fitting changelog entry which describes the change quite well but does not really outline its implications.

Another way I found for example in the Artist tutorial (Artist tutorial — Matplotlib 3.5.1 documentation) is

del ax.lines[0]

which would lead to something like

del ax.lines[:]

What do you think about this?

I totally unterstand the reasons for the change and why making lines.clear() still possible is not a good solution for you. But would it help to name it something like your proposed lines.remove_all() which internally is implemented as the for-loop? Naming it different than clear() at least I would not assume that something like (ax1.lines + ax2.lines).remove_all() is possible while clear() somehow implies that the underlying objects are lists.

Thanks again.

Looking at the tutorial again I noticed that even there

ax.lines.remove(line)

is used which implies that ax.lines is mutable and is therefore not future-safe (?).

Ah, this tutorial needs updating; both removal options are wrong.

Thank you. So the only remaining option at the moment and probably in the future is the for-loop over every sublist?

I am worried that we may have to slow down our deprecation timeline on the list-like removals. I think they are more widely used than we thought and they were documented!

1 Like

That sounds reasonable to me.