Skip to content

[ENH]: Giving artists optional ids #29429

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
timhoffm opened this issue Jan 7, 2025 · 2 comments
Open

[ENH]: Giving artists optional ids #29429

timhoffm opened this issue Jan 7, 2025 · 2 comments

Comments

@timhoffm
Copy link
Member

timhoffm commented Jan 7, 2025

Problem

Extracted the discussion from #29422 (comment)

Proposed solution

Summary

It may be reasonable to be able to retrieve artists by a name/id. such that this would be equivalent

fig, ax = plt.subplots()
line, = ax.plot(x, y)
fig, ax = plt.subplots()
ax.plot(x, y, id="myline")

[...]

line = fig.get_artist("myline")

This alleviates the need to always hold references to created Artists.

Use cases

1. plot helper functions

These helper functions do not always return the created artists. For example we could then get hold of the Artist that is created in https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.plot.line.html via

ax = df.plot.line(id='line')
line = ax.get_figure().get_artist('line')

2. FuncAnimation

The typical usage pattern for FuncAnimations is currently quite cobbled together: typically, the plot is created globally, and relevant Artists are stored in global variables that are accessed from the function. This means, the FuncAnimation is not self-contained but the function depends on globally accessible Artists at run-time.

Taking from https://matplotlib.org/stable/gallery/animation/simple_anim.html we could then evolve the API such that

[...]
line, = ax.plot(x, np.sin(x))

def animate(i):
    artists['line'].set_ydata(np.sin(x + i / 50))  # update the data.
    return line,

[...]

could be replaced by

[...]
ax.plot(x, np.sin(x), name="myline")

def animate(i, fig):
    line = fig.get_artist("myline")
    line.set_ydata(np.sin(x + i / 50))  # update the data.
    return line,

[...]

where we pass the figure into the function and from that retrieve the relevant artists. This would eliminate the global state.

3. Other cases of "tracking" artists

E.g. #29422 (comment) or #29422 (comment)

Design decisions

  • Uniqueness/Scoping: One should not give the same identifier to two Artists within the same figure. Figure is the right scope because it's the outermost Artists. Widening the scope would mean global uniqueness - this seems unnecessary and more cumbersome to implement. Narrowing the scope, e.g. to Axes is also difficult as there can be Figure-level Artists that would need extra handling.
  • Enforcement of uniqueness: Telling people, that an ID is already used would be nice, but I would make that feature depending on the implementation. A uniqueness check can be costly/cumbersome if we don't have a central registration mechanism for the identifiers.
  • Naming: I originally started with name, but now prefer id. name clashes with Axes.name (and I think we'd want to be able to handle Axes as well). Also, id is a bit more clear in the sense that you should not give the same identifier to two Artists.
  • Mutability: While immutability makes things easier, I believe it is neccesary to allow changing the identifier after Artist creation. We sometimes create multiple Artists through factory functions and it's not always possible/reasonable to expose the ability to set ids to all of them through the factory function (e.g. errorbar()).
  • Lookup performance: We can get a simple implementation by adding a public/private attribute Artist.id and doing the lookup by building a nicer API around fig.findobj(lambda artist: artist.id == id). This is easy as it does not need any registration/update/check mechanism. The downside is a slow lookup (~1ms on a simple figure), because you have to traverse the whole Artist tree. I'd be ok with that perfomance for a start. One could always later introduce a registry and speed things up.
@tacaswell
Copy link
Member

we could cache the id -> artist mapping on the Figure (with various invalidation / initial population schemes) if the lookup time becomes a problem.

How does this intersect with Artist.get_gid/Artist.set_gid?

@timhoffm
Copy link
Member Author

I don't fully understand gid, but I think it refers to the group id in SVG (<g id=...>), so I don't expect overlap. In particular, I believe multiple artists can/should have the same gid to be grouped together. In contrast, id should be unique.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants