-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
make FancyArrow animatable #18807
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
make FancyArrow animatable #18807
Conversation
This approach looks good to me, I don't see why anyone would object to it. |
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.
FancyArrow
is also a bit special: It's basically only a wrapper around Polygon
that transforms the given arrow parameters into a Polygon
. I agree that it's reasonable to store all attributes need to construct the arrow shape, so that single parameters can be changed and the arrow be regenerated.
I'm not yet clear how to approach this consistently. While not directly comparable, the update_arrow()
method is not in line with other parts of the Matplotlib API (see note below). Proposals welcome.
Side-note: FancyArrow
is the only Polygon subclass. There's no directly comparable case we can learn from, but at least also no other class we'd have to adapt to make such an update mechanism consistent.
Additional Remark: How useful is this FancyArrow
anyway? It's generated in data coordinates, which means that the arrow will be distorted by the scaling ratio of the y- and x-axis. IMHO that's broken by design, and I thought basically nobody is using it. Isn't annotate() with suitable arrowprops the way to go? (c.f. https://matplotlib.org/devdocs/gallery/text_labels_and_annotations/fancyarrow_demo.html).
lib/matplotlib/patches.py
Outdated
if head_width is None: | ||
head_width = 3 * width | ||
if head_length is None: | ||
self.x = x |
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.
Let's keep all the attributes private for now. Assigning to one of these would not update the underlying polygon. If you want to expose them, we'd probably need to make them properties, like Polygon.xy
.
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.
Attributes still need to be made private.
lib/matplotlib/patches.py
Outdated
self._make_verts() | ||
super().__init__(self.verts, closed=True, **kwargs) | ||
|
||
def update_arrow(self, *, x=None, y=None, dx=None, dy=None, width=None, |
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.
This is not how we manage updates in Artist
s within Matplotlib.
While it's not 100% consistent:
- We don't have
update_*()
methods taking keyword arguments. - Usually we set individual properties via
set_*()
and that marks the Artist as_stale
. - There's
Artist.update(props)
, but that's just a convenience wrapper callingset_*()
methods internally, and (for some reason) the single argument is a dict.
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.
So my understanding is that when an artist has _stale=True
then on the next fig.canvas.draw
event it will be asked to re-render itself, is that correct?
I was trying to avoid having to call _make_verts
multiple times if someone wanted to update the width and the xy positions all at once. Two ideas:
- call
_make_verts
in whatever gets called on account of_stale
. Like this:
def method_I_dont_know_the_name_of(self):
self._make_verts()
super().method_I_dont_know_the_name_of()
- There are both styling (e.g. color) args and args that are analogous to to data. So maybe we can take inspiration from the
set_data
methods for lines? i.e. have aset_data(x,y,dx,dy,width)
andset_color
,set_...
methods?
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.
-
I've never dug into the exact stale mechanism. Maybe someone else can help here. (Also just found out, that you should use the property
Artist.stale
instead of the private attributeArtist._stale
. -
set_data(x, y, dx, dy, width)
seems a good choice. Possibly with all parameters defaulting toNone
. I think in that case, it's also feasible to leave out the stale-mechanism and run_make_verts()
inset_data()
.
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.
What about properties like length_includes_head
or shape
? They aren't as exactly analogous to data, but there doesn't seem to be a good reason to exclude them. Should they get their own methods or be included in a set_data method?
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.
One could take the "pure" standpoint at data is only the position and length and everything else is styling. But then you'd also need to handle width separately. For simplicity, let's put all parameters that affect the polygon shape in set_data
, so that we can compute the polygon right there.
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.
Thinking again, the styling-related parameters are typically not modified in animations. So we can leave them as separate setters and explicitly let them call _make_verts as well. That's a bit more clean API-wise and not a significant performance limitation. (One could later still introduce stale if one wants to delay generation of the polygon). What do you think?
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.
To my eye the break down of data vs style is this:
data:
- x
- y
- dx
- dy
- width
- head_width
- head_length
style:
- length_includes_head
- shape
- head_starts_at_zero
- overhang (arguably data)
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.
Ok by me. I'm not sure about width, head_width, and head_length. But then again, it's not too invasive having them in set_data
because they will be optional.
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.
Should the style setters have a single setter. i.e. set_style(overhang=None...)
or each get their own? Each their own can lead to clunky names like set_head_starts_at_zero
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.
I'm somewhat opposed to set_style()
. We're not using it currently and "style" is a heavily overloaded name.
If you don't need to set styles for your use case, I'm fine with not adding any setters for them for now. It's probably very rare that one wants to change them.
Is there a path to a more general
It's currently the object returned by plt.arrow which is how I ended up using it. I've also used |
A more uniform API would be desirable. However that'll be a major effort. I suppose it's not going to happen before Matplotlib 4, which has the idea of a more clean separation/interaction between data and Artists. Scatter returns a |
Needs revision: Change from |
matplotlib/lib/matplotlib/tests/test_patches.py Lines 366 to 367 in d17905c
I'm not sure I fully agree that they are only historical as |
09d3d5f
to
3026f92
Compare
Does this need an example? I can see not needing one as tt's pretty consistent with other |
If there is not anything particularly special (which I don't see), we don't need an example. More is not always better with examples. You can hide relevant information among repetition an clutter. |
Done. |
previously the FancyArrow docstring was missing x,y,dx, and dy
3026f92
to
bbe2c90
Compare
Now done for real 😄 |
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.
This looks good to me!
PR Summary
Addresses point 7 of #5699
Makes the
FancyArrow
Patch animatable. WhileFancyArrowPatch
is somewhat animatable withset_positions
there is no easy way to animate the Polygon that is returned fromplt.arrow
. This PR addresses that gap allowing things like this:PR Checklist
pytest
passes).flake8
on changed files to check).flake8-docstrings
and runflake8 --docstring-convention=all
).doc/users/next_whats_new/
(follow instructions in README.rst there).doc/api/next_api_changes/
(follow instructions in README.rst there).