You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Artist provides the label API intended to for legend labels:
However, only a small subset of Artists are "data" Artists and can be reasonably used in a legend. From the subclasses of Artist:
suitable for legend: Line2D, Collection, Patch
not suitable for legend: AnnotationBbox, Text, Tick, Axis, FigureBase, Legend, _AxesBase, Table, OffsetBox, _ImageBase, QuiverKey
In some of these not-suitable classes label is repurposed for other use cases, e.g. axis labels, figure labels being displayed as window title. That's quite confusing.
Proposed fix
Create a Protocol or Mixin or DataArtist subclass for legend labels and use that in Line2D, Collection, Patch. Deprecate and remove get/set_label from all other Artists; possibly taking special measures where label is currently repurposed.
I can't say I really like that, e.g. I currently have code to serialize the data of (all subplots of) a figure into a dict of {artist_label: {"class": ..., "data": ...}}. At least it would be nice to keep a way to attach user-defined strings ("name", or even other metadata) to artists; maybe overloading the (legend) "label" for that purpose is not great but that API should not go away until a replacement is provided.
serialize the data of (all subplots of) a figure into a dict of {artist_label: {"class": ..., "data": ...}}
Not sure I understand what you do. Most of the labels are not set and there's no guarantee that they are unique. For example, each Tick has 6 labels associated, one for Tick class itself, and additionally one for each of tick.tick1line, tick.tick2line, tick.gridline, tick.label1, tick.label2. All of them are empty strings by default. Do you set them all so that your artist_label becomes unique?
At least it would be nice to keep a way to attach user-defined strings ("name", or even other metadata) to artists.
One can always monkey-patch such an information onto the artist if that's needed in some special cases. Making that a builtin public API is only necessary if we need a formalized way to access such information, i.e. when that information needs to be shared/accessed in our code and/or 3rd parties. Do you have such a use case?
serialize the data of (all subplots of) a figure into a dict of {artist_label: {"class": ..., "data": ...}}
Not sure I understand what you do. Most of the labels are not set and there's no guarantee that they are unique. For example, each Tick has 6 labels associated, one for Tick class itself, and additionally one for each of tick.tick1line, tick.tick2line, tick.gridline, tick.label1, tick.label2. All of them are empty strings by default. Do you set them all so that your artist_label becomes unique?
I only serialize axes.lines, axes.images, etc. so e.g. the ticks don't show up, and I also filter based on the presence of a non-default label (i.e. check whether the label starts with an underscore, similarly to what legend() does). For the relevant data artists I indeed ensure that the labels are unique (the serializer is not a fully general purpose code, just something I wrote for various complex plots that I fully control and that I want to serialize).
Not all, but I do use this for ImageBase at least.
At least it would be nice to keep a way to attach user-defined strings ("name", or even other metadata) to artists.
One can always monkey-patch such an information onto the artist if that's needed in some special cases. Making that a builtin public API is only necessary if we need a formalized way to access such information, i.e. when that information needs to be shared/accessed in our code and/or 3rd parties. Do you have such a use case?
Obviously I could just attach a custom attribute on the artist (line._my_label = "foo") but this feels pretty dirty (somewhat related to #26881, too).
Well, we could introduce a simple Artist.metadata dict, that is explicitly for information only and we don't use it for anything. OTOH it feels a bit like YAGNI. I'm not aware of any other library doing this. - pandas.DataFrame.attrs is somewhat similar in that the library does not care about the content. But pandas propagates that information to derived DataFrames, which makes it necessarily a builtin feature.
In particular, I find it important to strictly separate such information-only data provided by the user and anything that has an effect in the library. Abusing the label parameter for "additional information" this is a recipe for confusion because depending on the Artist and the formatting (underscore prefix) you may influence the library behavior or not.
Widgets and animations are dedicated concepts in the library. If we need to store them on a figure or axes, that should get explicit attributes. I therefore claim this is a completely separate topic.
Semi-OT: It may be reasonable to be able to name artists, i.e. make it possible to retrieve them back by a string. such that this would be equivalent
fig, ax = plt.subplots()
line, = ax.plot(x, y)
fig, ax = plt.subplots()
ax.plot(x, y, name="myline")
[...]
line = fig.get_artist("myline")
This would also help with FuncAnimations, which are 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.
This is indeed more or less what I do by hand right now in other places (unrelated to the serialization problem): directly copy-pasting from my internal codebase, I have
artists= {}
defregister(art):
assertart.get_label()
artists[art.get_label()] =artreturnart# then use asregister(ax.plot(...)[0])
register(ax.imshow(...))
# etc.# and access via the artists dict.
Create a Protocol or Mixin or DataArtist subclass for legend labels and use that in Line2D, Collection, Patch. Deprecate and remove get/set_label from all other Artists; possibly taking special measures where label is currently repurposed.
What about an explicit LegendKeyArtist ? (DataArtist is too semantically overloaded for me). OT but looking at @anntzer's answer, thinking it'd be nice to be able to register artists with the legend in a way that isn't either on creation (via label kwarg) or all at once (by passing in a list of handles) but possibly on the fly. Particularly if you want multiple legends or to modify a third party legend.
Semi-OT: It may be reasonable to be able to name artists, i.e. make it possible to retrieve them back by a string. such that this would be equivalent
Seems to me like a clean solution for separating out legend label from id label? Also "fig.get_artist("myline")" would be great for my use case of putting stuff in functions but then wanting to tweak the artist just a drop when I'm putting it somewhere else. Currently I'm just stashing stuff in a dict.
I've moved the Artist name/id discussion to #29429.
What about an explicit LegendKeyArtist ? (DataArtist is too semantically overloaded for me)
Maybe. But it could be that this is more specific than we want. I feel a distinction between "data" (everything that is related to the data coordinates) and "decoration" (title, legend, spines, ticks, etc.) could be helpful. - That's not exactly the same as "can have a legend entry" ("decoration" can never have legend entries, but there can be "data" Artists like images that do not fit in a legend either) - but I'd be ok with that level of imprecision. It's not the end of the world if images would continue to have an (unused) label property.
OT: it'd be nice to be able to register artists with the legend in a way that isn't either on creation [...] or all at once
Agreed, but that's orthogonal. You basically have to add a method Legend.add_entry(handle, label) that properly updates the internal state.
feel a distinction between "data" (everything that is related to the data coordinates) and "decoration" (title, legend, spines, ticks, etc.) could be helpfu
So far as I remember, some of the motivation for the data work @ksunden is doing is so you can feed data to labels and ticks. Like the tick location/labeling: the x axis loc/lab are built around the x data values and the y axis loc/lab re built around the y data. Yes there's level of indirection b/c it's fed from the axis but I dunno if we make that distinction anywherem
And I think the most basic artists like patch and Line2D gets reused when making both "plot type (visual idiom)" visual elements and "labeling/annotation" visual elements
But also like you point out, you're making the distinction based on coordinate space - but like axline is mixed coordinate and annotation is whatever coordinate you choose. And if this abstraction will include artists that don't participate in the legend then it seems to not solve the original request of removing label from artists that don't participate in legend.
We're in a bad situation as Artist.set_label is defined as "Set a label that is displayed in a legend", but this is confusing / not helpful for some Artists that are clearly non-data (c.f. #27971, #29338 (comment)).
There are different ways to improve:
Removing the label concept from Artist and reintroducing it on lower levels (this proposal). - This would also allow for Axis.set_label with a different meaning (A label in the Axis context is something different than a label for data).
Loosening the definition of Artist.label to "some descriptive text" and leaving it up to the child classes what to do with that text (legend enty, axis label, window title etc.)
and relax label to "some descriptive text". Objects used in a context where they should not have a legend entry (either as decoration or because it makes no sense to be a legend entry for the legend in the legend) could raise or return a "Nope!" value of some sort. This would let us move a whole bunch of the complexity (the multi-layered legend factory registry) to the artist objects and allow for more natural tuning of them at the artist level.
Maybe I'm not fully understanding this. give_me_a_legend_entry() is an internal-facing API. We can always do that, but that doesn't help on the user-facing API: We won't get a way from plot(..., label="This shows up in the legend"), so label will always stay connected to legend for at least some of the Artists. The question is how do we handle label on on other artists, which are basically the three options from #29422 (comment), and "Nope!" is basically variant 2?
Activity
anntzer commentedon Jan 7, 2025
I can't say I really like that, e.g. I currently have code to serialize the data of (all subplots of) a figure into a dict of
{artist_label: {"class": ..., "data": ...}}. At least it would be nice to keep a way to attach user-defined strings ("name", or even other metadata) to artists; maybe overloading the (legend) "label" for that purpose is not great but that API should not go away until a replacement is provided.timhoffm commentedon Jan 7, 2025
Not sure I understand what you do. Most of the labels are not set and there's no guarantee that they are unique. For example, each Tick has 6 labels associated, one for
Tickclass itself, and additionally one for each oftick.tick1line,tick.tick2line,tick.gridline,tick.label1,tick.label2. All of them are empty strings by default. Do you set them all so that your artist_label becomes unique?Are you sure you need labelling capability on all ( / most) of the artist subclasses (https://matplotlib.org/stable/api/artist_api.html#inheritance-diagrams)?
One can always monkey-patch such an information onto the artist if that's needed in some special cases. Making that a builtin public API is only necessary if we need a formalized way to access such information, i.e. when that information needs to be shared/accessed in our code and/or 3rd parties. Do you have such a use case?
anntzer commentedon Jan 7, 2025
I only serialize
axes.lines,axes.images, etc. so e.g. the ticks don't show up, and I also filter based on the presence of a non-default label (i.e. check whether the label starts with an underscore, similarly to what legend() does). For the relevant data artists I indeed ensure that the labels are unique (the serializer is not a fully general purpose code, just something I wrote for various complex plots that I fully control and that I want to serialize).Not all, but I do use this for ImageBase at least.
Obviously I could just attach a custom attribute on the artist (
line._my_label = "foo") but this feels pretty dirty (somewhat related to #26881, too).timhoffm commentedon Jan 7, 2025
Well, we could introduce a simple
Artist.metadatadict, that is explicitly for information only and we don't use it for anything. OTOH it feels a bit like YAGNI. I'm not aware of any other library doing this. -pandas.DataFrame.attrsis somewhat similar in that the library does not care about the content. But pandas propagates that information to derived DataFrames, which makes it necessarily a builtin feature.In particular, I find it important to strictly separate such information-only data provided by the user and anything that has an effect in the library. Abusing the label parameter for "additional information" this is a recipe for confusion because depending on the Artist and the formatting (underscore prefix) you may influence the library behavior or not.
Widgets and animations are dedicated concepts in the library. If we need to store them on a figure or axes, that should get explicit attributes. I therefore claim this is a completely separate topic.
timhoffm commentedon Jan 7, 2025
Semi-OT: It may be reasonable to be able to name artists, i.e. make it possible to retrieve them back by a string. such that this would be equivalent
This would also help with FuncAnimations, which are 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
could be replaced by
where we pass the figure into the function and from that retrieve the relevant artists. This would eliminate the global state.
@anntzer I believe this kind of naming would cover your use case as well?
anntzer commentedon Jan 7, 2025
This is indeed more or less what I do by hand right now in other places (unrelated to the serialization problem): directly copy-pasting from my internal codebase, I have
story645 commentedon Jan 7, 2025
What about an explicit LegendKeyArtist ? (DataArtist is too semantically overloaded for me). OT but looking at @anntzer's answer, thinking it'd be nice to be able to register artists with the legend in a way that isn't either on creation (via label kwarg) or all at once (by passing in a list of handles) but possibly on the fly. Particularly if you want multiple legends or to modify a third party legend.
Seems to me like a clean solution for separating out legend label from id label? Also "fig.get_artist("myline")" would be great for my use case of putting stuff in functions but then wanting to tweak the artist just a drop when I'm putting it somewhere else. Currently I'm just stashing stuff in a dict.
timhoffm commentedon Jan 7, 2025
I've moved the Artist name/id discussion to #29429.
Maybe. But it could be that this is more specific than we want. I feel a distinction between "data" (everything that is related to the data coordinates) and "decoration" (title, legend, spines, ticks, etc.) could be helpful. - That's not exactly the same as "can have a legend entry" ("decoration" can never have legend entries, but there can be "data" Artists like images that do not fit in a legend either) - but I'd be ok with that level of imprecision. It's not the end of the world if images would continue to have an (unused) label property.
Agreed, but that's orthogonal. You basically have to add a method
Legend.add_entry(handle, label)that properly updates the internal state.story645 commentedon Jan 8, 2025
So far as I remember, some of the motivation for the data work @ksunden is doing is so you can feed data to labels and ticks. Like the tick location/labeling: the x axis loc/lab are built around the x data values and the y axis loc/lab re built around the y data. Yes there's level of indirection b/c it's fed from the axis but I dunno if we make that distinction anywherem
And I think the most basic artists like patch and Line2D gets reused when making both "plot type (visual idiom)" visual elements and "labeling/annotation" visual elements
But also like you point out, you're making the distinction based on coordinate space - but like axline is mixed coordinate and annotation is whatever coordinate you choose. And if this abstraction will include artists that don't participate in the legend then it seems to not solve the original request of removing label from artists that don't participate in legend.
timhoffm commentedon Jan 8, 2025
We're in a bad situation as
Artist.set_labelis defined as "Set a label that is displayed in a legend", but this is confusing / not helpful for some Artists that are clearly non-data (c.f. #27971, #29338 (comment)).There are different ways to improve:
Axis.set_labelwith a different meaning (A label in the Axis context is something different than a label for data).Artist.labelto "some descriptive text" and leaving it up to the child classes what to do with that text (legend enty, axis label, window title etc.)Overall, I'm not clear what the best solution is.
tacaswell commentedon Jan 9, 2025
A more radical change here is to add an API to
Artistwhich is something likeand relax label to "some descriptive text". Objects used in a context where they should not have a legend entry (either as decoration or because it makes no sense to be a legend entry for the legend in the legend) could raise or return a "Nope!" value of some sort. This would let us move a whole bunch of the complexity (the multi-layered legend factory registry) to the artist objects and allow for more natural tuning of them at the artist level.
timhoffm commentedon Jan 10, 2025
Maybe I'm not fully understanding this.
give_me_a_legend_entry()is an internal-facing API. We can always do that, but that doesn't help on the user-facing API: We won't get a way fromplot(..., label="This shows up in the legend"), solabelwill always stay connected to legend for at least some of the Artists. The question is how do we handlelabelon on other artists, which are basically the three options from #29422 (comment), and "Nope!" is basically variant 2?