Skip to content

Add an .annotate method to Artist #25094

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

Closed
story645 opened this issue Jan 27, 2023 · 14 comments
Closed

Add an .annotate method to Artist #25094

story645 opened this issue Jan 27, 2023 · 14 comments

Comments

@story645
Copy link
Member

story645 commented Jan 27, 2023

I use FancyArrowPatch a lot to make diagrams and they have a lot of arrows and I'd really like tikz-like positioning of labels along the arrow. The arrow demo has an example of the sort of manual work that goes into trying to position each label correctly and it'd be nice to move all that positioning logic inside a method on FancyArrowPatch.

image

(Discussion of migrating arrow to vector and therefore FancyArrowPatch so it could take advantage is at #22435)

For this method, we can reuse the 'ha' and 'va' arguments relative to the line, w/ n additional offset argument (here relpos, but possibly something closer to the annotate api offset(xoffset, yoffset))

ar = FancyArrowPatch
ar.set_label(str, offset= ratio on interval of line, pad) #base off tikz api
left right center (default)
label
----->
----->
label
--label-->
top bottom center (default)
label -----> --label-->
----->
label
relpos=0 relpos=1 relpos=.5 (default)
label-----> ----->label ---label--->

maybe |--a1--[bbox]--a2-->

Originally posted by @story645 in #22223 (comment)

ETA: Based on the discussion below, where I've landed is that an .annotate method on artist would give me the bookkeeping I want - ease of keeping track of which annotation I put on what-and be fairly generalizable.

def annotate(self, label=None, xy=None, xytext=None, **annotation_kwargs):
     matplotlib.text.Annotation(text, xy, xytext, xycoords=self, **kwargs)

with annotations I guess added as child artists/sibling artists -> do we have a mechanism for this?

@jklymak
Copy link
Member

jklymak commented Jan 27, 2023

An Annotation class is a FancyArrowPatch and a Text. This sounds like it could be a compound Artist like that, or even just an Annotation object, perhaps with more Text placement knobs, or a child class. I don't think FancyArrowPatch on its own should grow a text method, and it almost certainly should not be called set_label since for every other Artist set_label sets the label to be used for legends.

@story645
Copy link
Member Author

story645 commented Jan 27, 2023

or even just an Annotation object, perhaps with more Text placement knobs,

Looking at annotate again, this kinda already works out of the box with xycoords=Artist

fig, ax = plt.subplots(figsize=(3,3))

arr = mpatches.FancyArrowPatch((.25,.25), (.75,.75),   arrowstyle='->,head_width=.15', mutation_scale=20, )
ax.add_patch(arr)
ax.annotate("label", (.5, .5), xycoords=arr, ha='center', va='center')
ax.set(xlim=(0,1), ylim=(0,1))

image

so maybe my actual issue is that annotate has way way too many knobs and artists should maybe get very thin annotate method that sets xycoords to itself.

def annotate(self, label, xy, **kwargs):
     self.axes.annotate(label, xy, xycoords=self, **kwargs)

which I know seems pointless but like I struggle w/ how to communicate and make discoverable all the things annotation can do.

@tacaswell
Copy link
Member

The core problem here is that we need all of those knobs. Reducing the number or changing the parameterization either throws out useful functionality or just masks the problem. I think we should take inspiration from https://docs.python.org/3/library/itertools.html#itertools-recipes and write up as functions a whole lot of these alternate paramaterization.

Either they happen to be what users want (and then they can copy-paste and be done), the are close enough that users can tweak them a little bit, or they are extensive enough to provide inspiration for what the users actually need to write for them selves.

@story645
Copy link
Member Author

story645 commented Jan 27, 2023

The core problem here is that we need all of those knob

Yeah I'm not suggesting we take any away from annotate. Where I think I landed here is explicitly attach an annotation to an artist via Artist.annotate, which is one parameterization. It would also address the bookkeeping problem which is why I really opened the PR in the first place.

That being said, I think a good-first-issue way to address my request is to add the following section (or something like it) to Annotations: Basic Annotation

# Annotating an Artist
# ====================
#
# Annotations can be positioned relative to an artist by setting the *xycoords* to the artist. 

 
fig, ax = plt.subplots(figsize=(3,3))

arr = mpatches.FancyArrowPatch((.25,.25), (.75,.75),   arrowstyle='->,head_width=.15', mutation_scale=20, )
ax.add_patch(arr)
ax.annotate("label", (.5, .5), xycoords=arr, ha='center', va='center')
ax.set(xlim=(0,1), ylim=(0,1))

@tacaswell
Copy link
Member

Given that you just wrote the example can you please directly open the PR 😄 .

@story645 story645 changed the title Add a .set_label to FancyArrowPatch Add an .annotate method to Artist Jan 27, 2023
@anntzer
Copy link
Contributor

anntzer commented Jan 27, 2023

See also #22223 (comment): I think the idea of having an easy way to specify coordinates in units relative to another artist's bounding box is something worth exploring.

@timhoffm
Copy link
Member

AFAIK, we don't have Artists that have functions to create and add other Artists to a figure/ axes. I'm hesitant to change this as this adds complexity to the Artist (for example the function would have to determine whether the parent artist is in an Axes or figure and behave accordingly) Also, artist.annotate() conveys the impression to me that the annotation is attached to the artist, which e.g. would imply that the annotation would move with the artist. However, I don't think we can do this (or implement this without strong user demand).
IMHO the current API is good enough. It should only be documented better, which is done in #25100.

@story645
Copy link
Member Author

story645 commented Jan 29, 2023

which e.g. would imply that the annotation would move with the artist.

I think draggable annotations would be really really cool, and is kinda what's happening in AnnotatedCursor I think. And more than that for me is the bookkeeping aspect, but maybe @jklymak's compound artist suggestion "AnnotatedArtist" suggestion would work for that? Basically, something like

annotated_arrow = AnnotatedArtist(FancyArrowPatch)
annotated_arrow.add_annotation(...)
annotated_arrow.add_annotation(...)

so that the complexity isn't in the artist? Granted, this is maybe more useful for something that's getting annotated a lot or something interactive/animated than my actual use case of annotating almost everything in a figure I'm making.

@jklymak
Copy link
Member

jklymak commented Jan 29, 2023

I think draggable annotations would be really really cool,

Can draggable annotations not already be supported by draggable text? If you can just drag the annotation wherever you like, why would it need to be attached to a parent artist?

than my actual use case of annotating almost everything in a figure I'm making.

A lot of your use cases seem to be diagraming. I'm not against diagramming in Matplotlib per-se, but it's a bit orthogonal to the main point of Matplotlib - there are lots of great diagramming GUI-based programs in the world.

@timhoffm
Copy link
Member

timhoffm commented Jan 29, 2023

Can draggable annotations not already be supported by draggable text?

The point here was rather that you modify the artist and the annotation follows along, e.g. you have
image
and either drag the artist (here: arrow) or modify it in an animation, then if the annotation belongs to the artist, it should follow where the artist is going.

@jklymak
Copy link
Member

jklymak commented Jan 29, 2023

That seems OK - Do we have an artist transform object - having that evaluated at draw time doesn't seem too hard.

@rcomer
Copy link
Member

rcomer commented Jan 29, 2023

What currently happens if you set xycoords=my_artist within annotate and then move the artist? Intuitively, I would expect the text moves with the artist.

@anntzer
Copy link
Contributor

anntzer commented Jan 29, 2023

@jklymak I think that's close to my IndirectTransform proposal at #22223 (comment)?

@story645
Copy link
Member Author

What currently happens if you set xycoords=my_artist within annotate and then move the artist?

strut

So ok, this is totally doable 😄 I'm not a fan of having to do the bookkeeping by hand, but I can live w/ that given the lack of enthusiasm. And @anntzer's IndirectTransform proposal would probably solve most of my other use cases, so I'll close in favor of that.

@story645 story645 closed this as not planned Won't fix, can't repro, duplicate, stale Jan 29, 2023
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

6 participants