-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Example showing scale-invariant angle arc #12518
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
Example showing scale-invariant angle arc #12518
Conversation
Mhh, it's not easy, is it?! We need an arc whose radius is in screen coordinates (or better axes coordinates?), whose start and stop angle are dependent on other elements in the axes and whose position is in data coordinates. I wonder if it'd be better to defer much of the calculations to draw time? I can currently only promise to have a closer look in the next few days. |
theta2 = get_vector_angle(vec2) | ||
arc = mpatches.Arc((0, 0), width, height, theta1=theta1, theta2=theta2) | ||
try: | ||
ax.patches[0].remove() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's dangerous to remove the first patch from the axes, since we don't know if this is the one we target.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I didn't like that either. Is this any better?
matplotlib/examples/lines_bars_and_markers/angle_arc.py
Lines 70 to 72 in f4a63d5
arc = mpatches.Arc( | |
(0, 0), width, height, theta1=theta1, theta2=theta2, gid="angle_arc") | |
[p.remove() for p in ax.patches if p.get_gid() == "angle_arc"] |
No, it's not! I've found a way to keep the radius a constant in axes coordinates, but of course that messes up theta1 and theta2 for the arc. I'll keep working at it 😄 |
Okay, I think I got it! Regardless of the size of the figure or the axes, the arc is circular and at a constant pixel distance from the origin. |
I agree with @ImportanceOfBeingErnest that doing the relevant calculations in draw() may work better (or better, fix |
When running the proposed code, I observe some very strange behaviour. Does this work as expected for you? My thinking was that maybe one can subclass
This is in pixel coordinates. So independent of the figure size and dpi it will have the same radius. I'm not sure in which units the radius should really be, to be honest. Maybe better in inches? or relative axes width? Points? |
Oh wow, I didn't even try panning, but yes, mine does that as well 😆 I do like the idea of subclassing Arc; your solution is great! I don't think the radius should be a constant pixel or inch value, because wouldn't that result in the arc being drawn outside of the axes limits if the figure itself is made too skinny? |
Hm, I copied your solution as is, and it doesn't seem to work on my system. This is with |
I just tested and with matplotlib 3.0.0 and current master it works as expected for me, it doesn't work with 2.x though. Also I only tested with python 3.6. If you can share your versions one might more easily find out where the problem lies. |
Yep, I was in the wrong virtual environment. My mistake! |
So what is the next step? Your solution is certainly the better one, so I would be fine with you taking this PR. How do you feel about having the option to specify units for the radius? |
Note that my interest is not to "take this PR" (not even sure what that means), but to help with finding a good solution here. Indeed the main question is the one about the units of the radius. Pixels, as above, is not good, since it will not let the radius scale with dpi. Inches or points would be better. This would lead to a constant radius independent of the axes or figure size, which may, or may not be desired. I might add that if all of this becomes too complicated, stepping back and writing a new Artist with its own I think once we find an acceptable solution we may revisit the question of whether it makes sense to add this to the library itself - seen from how this is not as easy as anticipated, there may be true benefit of such function. |
What I meant was that at this point, anything I add to my solution would be built off of yours, and I don't want to take credit where it's not due. Forgive me, but this is the first PR I've made to a library, let alone one as popular as Matplotlib, so I'm a little intimidated. If it's okay with you, I'll try my best to expand on your solution to allow for the radius units to be specified by the user. |
Yes, totally. A project like matplotlib lives on user contributions like yours. So please go ahead. I will label as "Work in progress" for now. |
Okay, thank you for the guidance and for the opportunity! |
I've modified your AngleMarker class to accept However, to do this, I am setting the
when these attributes actually belong to the |
There isn't any necessity for getters/setters in python and actually many people consider them as bad style. On the other hand, matplotlib has a tradition of using them in the public API. Of course the exact reason for the precise case of Rectangle and Ellipse is beyond my knowledge - possibly they have been written by two different people simply and noone has ever cared to add them. There is no technical necessity, right? It would only be for API consistency. For the use case here it doesn't look like there is any problem resulting from missing getters/setters? |
Right. Like you said, I had noticed them in other parts of the API, so it just caught my attention and I wondered if there was a known reason they weren't included for For my current solution, I'm using a callback for the I'm pushing my solution as is for you to see, so forgive any weirdness/incompleteness. I look forward to your advice! |
Well, if you use a callback anyways you can directly update everything through the callback. I guess you can do that, if you want, I just wouldn't actually mix the two concepts. I updated the code, see below. It'll now take pixels, points, axes width, height and the minimum or maximum of those as input units for the circle radius. All of those have some advantage and some drawbacks.
Note that the code may not be PEP8 compatible as of now and may not be valid rst either. Also, it hasn't got any text attached - could be useful to add... And then here is a code to look at the differences for the parameters. I think the problem you mention about the axes width/height depending on the direction of the angle should be observable with this. There is no good solution, I fear. Hence providing all the options to the user is probably beneficial.
|
Yeah, I didn't like mixing a callback function with the draw-time calculations either. Thank you for showing me a better way, though I should have thought of that when I saw you doing the same thing with the angles originally. I'm definitely learning a few things from you! I also like that you condensed the four lines matplotlib/examples/lines_bars_and_markers/AngleArc.py Lines 26 to 29 in b98621a
down to just
I'll remember that in the future. For the text, I think we'll want to make sure it doesn't overlap with anything else on the axes, but how do you feel about also having the font size change alongside the radius (maybe by a smaller factor)? |
It'll be hard to make sure the text doesn't overlap with anything. Of course it should not overlap with the arc itself. I would vote against making the fontsize dependent on the arc size. This would be quite unusual. After all, the effort here is to make the angle marker size constant in some units, such that you get similarly sized markers. Of course the user should be able to set all the text properties to his liking. I see two options:
There are also still some open question as to how to place the text: or introduce a keyword argument to steer that behaviour. |
Would you recommend against having AngleMarker inherit from Text in addition to Arc? |
No, that might be possible (you need to check if there are attributes that clash). Also consider using |
Okay, that's the route I had started to go, so I'll continue. I haven't found any attributes that clash. |
Okay, so AngleMarker now inherits from both Arc and Text. I'm still working out the general placement of the text, but in my most recently pushed commit you can see that its position relative to |
I'm going to try to have it always appear just outside of the arc halfway between theta1 and theta2. Or do you think it would make sense to allow the user to enter an angle for the position of the text? |
I think the angle might always be half of the angle between the vectors. So if anything should be custumizable it would be the distance from the center - either the two options shown as picture above or more generally any distance in the units of |
I've moved this to draft until @timhoffm's review is addressed. @daniel-s-ingram did you want to see this through? |
@daniel-s-ingram If we don't hear from you within a week, we'll take over and do the requested changes ourselves. This is almost there and it would be a pity if it gets stalled and lost. |
d078ffa
to
49b9504
Compare
Since it's been about 3 weeks, I've gone ahead and rebased this, squashing the flake8 commits, and fixed the suggestions, plus a few prose changes from myself. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mandatory
I don't think this belongs in "Lines, bars and markers". While both are called "marker", the example uses it in the sense of an annotation, whereas the gallery section means "plot data points using markers". The example should be moved to "Text, labels and annotations".
Optional
Let's squash into one commit. This is a newly added standalone example and does not need it's creation history reflected in git.
I'd favor calling this AngleLabel
or AngleIndicator
, but won't insist because it's only an example and not part of the Matplotlib API.
Also, simplify locating some annotations.
Otherwise, panning around leaves the angle text visible even outside the Axes.
49b9504
to
ecf51a5
Compare
Sure, I'd say just do that on merge.
I renamed to |
Twhanks to all contributors! |
* Example showing scale-invariant angle arc * Remove arc based on gid * Made radius scale invariant * Add option for relative units for arc radius * Add option for text * Add annotation to AngleMarker * Separate test from AngleMarker class * finishing angle arc example * Clean up prose in invariant angle marker example. Also, simplify locating some annotations. * Enable annotation clipping on invariant angle example. Otherwise, panning around leaves the angle text visible even outside the Axes. * Fix types. * Rename angle marker example to angle annotation. * Split angle annotation example figure in two. * Fix a few more marker -> annotation. Co-authored-by: ImportanceOfBeingErnest <elch.rz@ruetz-online.de> Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>
PR Summary
Addresses Issue #12414
@ImportanceOfBeingErnest,
Is this closer to what you were talking about? I'm completely new to Matplotlib transforms, so please let me know if there are better ways to do what I'm doing. I don't like how I'm calculating the angles of the FancyArrows - do you know of a better way? It would be nice to be able to get the dx and dy a FancyArrow is constructed with from the instance, but I didn't see a way for this to be done.
PR Checklist