-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Add legend handler and artist for FancyArrow #10688
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
Conversation
@magedhennawy you'll need to rebase onto master to get the correct tests etc to run. Sorry, a lot has chnaged recently with the move to MPL 3.0 (python 3 only). |
@jklymak rebased :) |
@magedhennawy Thanks for taking #8292 over! I was not realistically going to complete it anytime soon. |
@jlecoeur no problem, thank YOU for the extra features, i had only fixed the issue, you sir went above and beyond. and made my life easy <3 |
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 really cool. Thanks so much for everyone's work on it. Some gotchyas:
Please revert all the legend doc style changes (or some folks who just made all those changes are going to be pretty sore at you).
Please provide a better docstring for width_ratios
. I didn't really dig into what you are talking about, but I shouldn't have to to understand a doc string.
A more comprehensive test in the modern style would be appreciated. (also why is the arrow off the page?)
I think this should go in. It'll need a whats new entry, and ideally an example.
lib/matplotlib/legend.py
Outdated
Default is ``None``, which will take the value from | ||
:rc:`legend.markerscale`. | ||
drawn ones. Default is ``None`` which will take the value from | ||
the ``legend.markerscale`` :data:`rcParam <matplotlib.rcParams>`. |
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.
:rc:
is the proper markup for rc params. Please undo this.
lib/matplotlib/legend.py
Outdated
:rc:`legend.numpoints`. | ||
entry for a line/:class:`matplotlib.lines.Line2D`. | ||
Default is ``None`` which will take the value from the | ||
``legend.numpoints`` :data:`rcParam<matplotlib.rcParams>`. |
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.
please leave the :rc:
markup as it was.
lib/matplotlib/legend.py
Outdated
@@ -134,14 +136,15 @@ def _update_bbox_to_anchor(self, loc_in_canvas): | |||
corner of the legend in axes coordinates (in which case | |||
``bbox_to_anchor`` will be ignored). | |||
bbox_to_anchor : `.BboxBase` or pair of floats | |||
bbox_to_anchor : :class:`matplotlib.transforms.BboxBase` instance \ |
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.
The original link to .BboxBase
should have been fine as it was. Saves you having to continue the line.
lib/matplotlib/legend.py
Outdated
:rc:`legend.frameon`. | ||
Control whether the legend should be drawn on a patch (frame). | ||
Default is ``None`` which will take the value from the | ||
``legend.frameon`` :data:`rcParam<matplotlib.rcParams>`. |
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.
as above
lib/matplotlib/legend.py
Outdated
the :class:`~matplotlib.patches.FancyBboxPatch` which | ||
makes up the legend's background. | ||
Default is ``None`` which will take the value from the | ||
``legend.fancybox`` :data:`rcParam<matplotlib.rcParams>`. |
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.
as above
lib/matplotlib/legend.py
Outdated
Default is ``None`` which will take the value from the | ||
``legend.facecolor`` :data:`rcParam<matplotlib.rcParams>`. | ||
If ``"inherit"``, it will take the ``axes.facecolor`` | ||
:data:`rcParam<matplotlib.rcParams>`. |
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.
etc
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'll not note the rest of these. These should all be reverted.
lib/matplotlib/legend_handler.py
Outdated
handler = legend.get_legend_handler(handler_map, handle1) | ||
|
||
_a_list = handler.create_artists(legend, handle1, | ||
six.next(xds_cycle), |
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.
We don't need six
anymore. This will be py3 only....
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.
Can you explain (like I'm dumb) why we need widths to cycle here? I'm not grokking the width_ratios and why they are needed.
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.
my fault for the six
, i merged with the other pr without seeking the py2->py3 changes
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 was pulled from #8292 and i played with it in order to find a reason to keep it.
basically, in the use case were a user would like both an arrow AND text in the legend, it would show up as
and the "sections" that you were asking for further explanation for, are what overlap in the latter picture.
When the widths aren't cycled, the part of the legend where a text or an arrow would show up for the annotation would be drawn on top of one another.
So width_ratios
was defined so that each arrow "section" and text "section" would be given their respective space in the legend box in the appropriate location, where their respective widths would be cycled in order to place them both together.
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.
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 this seems great. but some of this should be in the doc string so it’s clear. Sections is a bit vague.
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.
Cool, if my explanation to you was good enough, ill write it in 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.
Could be shorter. Ie width_ratios specifies the restive widths of a text/arrow legend annotation pair
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.
Gotcha 👍
thanks again!
last thing left on my list is writing better tests :)
@@ -649,11 +650,26 @@ class HandlerTuple(HandlerBase): | |||
pad : float, optional | |||
If None, fall back to ``legend.borderpad`` as the default. | |||
In units of fraction of font size. Default is None. | |||
width_ratios : tuple, 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.
The term "section" here is opaque to me. What is a "section" and why does it need different widths?
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.
done
lib/matplotlib/tests/test_legend.py
Outdated
@@ -250,6 +250,15 @@ def test_hatching(): | |||
ax.legend(handlelength=4, handleheight=4) | |||
|
|||
|
|||
@image_comparison(baseline_images=['legend_arrow_annotation']) |
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.
add extensions=['png'], style='mpl20'
lib/matplotlib/tests/test_legend.py
Outdated
# Related to issue 8236 | ||
# Tests arrow annotations showing up in legend | ||
fig, ax = plt.subplots() | ||
ax.annotate("foo", (0, 1), xytext=(2, 3), arrowprops={}, label='bar') |
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.
please add:
ax.set_xtick_labels('')
ax.set_ytick_labels('')
This helps us cut down on extra text comparison failures.
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 test seems a bit minimal. Might as well test all the new artists, as in your example in the PR (or a good subset of them).
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.
will do :)
thanks for working on this @magedhennawy ! |
You must have a typo somewhere, because this doesn't run at all now! |
taking a look |
|
@jklymak im not entirely sure what your second point is referring too :/ |
lib/matplotlib/legend.py
Outdated
@@ -428,7 +430,7 @@ def __init__(self, parent, handles, labels, | |||
The vertical offset (relative to the font size) for the markers | |||
created for a scatter plot legend entry. 0.0 is at the base the | |||
legend text, and 1.0 is at the top. To draw all markers at the | |||
same height, set to ``[0.5]``. Default is ``[0.375, 0.5, 0.3125]``. | |||
same height, set to ``[0.5]``. Default ``[0.375, 0.5, 0.3125]``. |
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.
You changed this docstring here. If you want to make the change, you also need to change it in figure.py
and axes/_axes.py
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.
ah. okay. gotcha, ill keep it consistent to how the other files are.
Thanks again.
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.
Yes, see #10799 for the proposed fix to this lamentable state of affairs. But in this case, I'd just remove the change.
Now I'm confused - who is authoring this PR? |
We are working on it together, picking up since he had something to do. |
@jklymak i had to step out for an event, so my partner stepped in for me :) |
@jklymak finally. sorry it took a while |
lib/matplotlib/tests/test_legend.py
Outdated
|
||
@image_comparison(baseline_images=['legend_all_annotation_text'], | ||
extensions=['png'], | ||
style='mpl20') |
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.
Why the other tests rather than just this one?
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.
Not entirely sure, I'm thinking that he wanted to have them all tested separately and then just a cumulative one, also as a "how to use" kind of thing
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.
Image tests have a time cost and a repo-space cost. I'd remove the redundatn ones unless there is a pressing need for them.
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.
got it, push incoming.
Can you guys squash your commits? Please (please) make a local backup branch before you try unless you are very comfortable w/ what you are doing. Please see the instructions in the developers guide. |
@jklymak the squash is going to be very messy and i'm unsure as to how ill do it properly due to the merges amidst my commits. |
Suggest you
|
8b5b650
to
4c2e1d6
Compare
@jklymak works perfectly. thank you 👍 |
No problem. I only had to be told how to do that 10 or 12 times on here 😉 |
does this still need revision? |
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.
LGTM asside from a couple of formatting issues that are not blockers but could be addressed if there is a more stringent review.
handlebox.xdescent, handlebox.ydescent, | ||
handlebox.width, handlebox.height, | ||
fontsize) | ||
legend, orig_handle, |
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.
Please leave the double indent - this makes it easier to tell its an indent vs a code block...
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.
will be changed asap! :)
@@ -441,6 +451,7 @@ class HandlerErrorbar(HandlerLine2D): | |||
""" | |||
Handler for Errorbars. | |||
""" | |||
|
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.
Were all these extra carriage returns called for somehow?
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 believe the pep8 rules that I followed required a blank line after dosctrings in methods, correct me if i'm wrong please.
Don't be shy about pinging for reviews (on a reasonable basis) - we have a somewhat stringent review policy, but not enough reviewers, and github tends to make it too easy for old PRs to get burried. |
This is great stuff. I would think this is valuable enough to deserve a What's new entry. |
Got it! Thank you guys for all your feedback |
Took the liberty of adding a whats new entry, @magedhennawy please feel free to tweak it! |
self._handlers = handlers | ||
|
||
if (width_ratios is not None) and (len(width_ratios) == ndivide): | ||
self._width_ratios = width_ratios |
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.
Can you raise here if the width_rations and ndivide are not compatible? I can imaging this being a very frustrating bug to track down where we are ignoring one of the inputs!
|
||
self._ndivide = ndivide | ||
self._pad = pad | ||
self._handlers = handlers |
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.
Can you validate that handlers
is the correct length? That way we can raise an exception as early as possible.
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.
Small changes to the __init__
to validate the inputs that have to be the same length.
ping @magedhennawy ! |
pinging again... |
Thanks a lot for the contribution. I'm going to close this as having had no action for quite a while. OTOH, if there is still a community that wants this, please feel free to re-open, with my apologies! |
closes #8236
I added new legend handlers for FancyArrowPatch, Annotation artists and HandlerAnnotation.
HandlerAnnotation can draw FancyArrowPatch artist in the legend:
if the annotation has an arrow, it uses HandlerFancyArrowPatch to draw an arrow
Example code:
**************** Added extra features. ******************
after merge with #8292
I added new legend handlers for Text, FancyArrowPatch and Annotation artists (HandlerText, HandlerFancyArrowPatch and HandlerAnnotation).
HandlerAnnotation can draw different artists in the legend:
if the annotation has no text and no arrow, it draws a white Rectangle
if the annotation has only text, it uses HandlerText to draw a text. The text is replaced by 'Aa' if it is longer than one character. (This is configurable by setting rep_str and rep_maxlen.)
if the annotation has only an arrow, it uses HandlerFancyArrowPatch to draw an arrow
If the annotation has both text and arrow, it uses HandlerTuple to draw both.
In HandlerTuple, I added the possibility of different width ratios for the artists. HandlerAnnotation uses a width_ratios=[1,4] (ie. the arrow is 4 times wider than the text).
Example code: