-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
custom legends example #9451
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
custom legends example #9451
Conversation
29fdcae
to
6f77d8e
Compare
At the moment, I'm not quite sure how this is different from the legend tutorial? |
It's the same content in a concise, example format. I don't see a problem with having the same content spread across the examples and the tutorials...i think it makes the content more discoverable... |
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.
A few remarks.
|
||
fig, ax = plt.subplots() | ||
lines = ax.plot(data) | ||
ax.legend(lines, loc=(1.05, .05), ) |
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.
Isn't the rightest part of the legend cut out if one doesn't tweak the layout (and calling plt.tight_layout()
is currently even worse if I am correct)?
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 yeah that's a good point - ugh that's such annoying behavior from matplotlib :-)`
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.
Winter Layout manager is coming (#9082)! This should help to get rid of this kind of issues if I understood correctly.
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.
Well actually it can be gotten rid of with #9164 which is a two liner instead of 20-file fix :-).
|
||
fig, ax = plt.subplots() | ||
lines = ax.plot(data) | ||
ax.legend(lines, loc=(1.05, .05), ) |
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.
Is the “tuple notation” really mandatory, or could the trailing coma be removed?
|
||
fig, ax = plt.subplots() | ||
lines = ax.plot(data) | ||
ax.legend(custom_lines, ['Cold', 'Medium', 'Hot'], loc=(1.05, .8),) |
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.
Same remark as before about the legend being possibly cut out.
legend_elements = {'Line': Line2D([0], [0], color='b', lw=4), | ||
'Scatter': Line2D([0], [0], marker='o', color='w', | ||
markerfacecolor='g', markersize=15), | ||
'Color Patch': Patch(facecolor='orange', edgecolor='r')} |
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.
More a note for the future: if #8292 is some day merged in (I hope so!), it would be nice to remember updating this example to include one case or two.
# is a list of common ones. | ||
|
||
from matplotlib.patches import Patch | ||
from matplotlib.lines import Line2D |
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.
Maybe a dumb comment, but Line2D has already been loaded, hasn't 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.
yeah here I was loading it again just so that it's easier for people to trace the imports in case they wanna do it themselves. Think it should be removed? I agree it's not standard PEP style
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.
Well I do not know what exactly are our guidelines about this kind of things. I am not particularly pushing for removing it, I just wanted to be sure it had been done on purpose.
About PEP8, there are other imports that are not done at the top of the file anyway, so I guess that it does not really matter anyway ^^.
=> my call would be to keep it this way except if somebody more involved in the docs than I am complains ;)
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 think @choldgraf might be the one most involved in the docs. 😛
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.
@QuLogic I know ^^, but some other core devs are also quite involved about the docs and do care about what are the relevant guidelines that the docs should follow (hint: from my perspective, you are counted in that group :P)
markerfacecolor='g', markersize=15), | ||
'Color Patch': Patch(facecolor='orange', edgecolor='r')} | ||
fig, ax = plt.subplots() | ||
ax.legend(legend_elements.values(), legend_elements.keys(), loc=(1.05, .8),) |
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.
Again, when I am trying this locally, the right part of the legend is cut out (by the figure right border).
|
||
fig, ax = plt.subplots() | ||
lines = ax.plot(data) | ||
ax.legend(lines, loc=(1.05, .05), ) |
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 may be wrong but I think that one tries to use the leading zeros wherever possible: (1.05, .05)
<- (1.05, 0.05)
?
|
||
fig, ax = plt.subplots() | ||
lines = ax.plot(data) | ||
ax.legend(custom_lines, ['Cold', 'Medium', 'Hot'], loc=(1.05, .8),) |
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.
Similarly to previous plot:
- is the trailing coma mandatory?
(1.05, .8)
<-(1.05, 0.8)
?
markerfacecolor='g', markersize=15), | ||
'Color Patch': Patch(facecolor='orange', edgecolor='r')} | ||
fig, ax = plt.subplots() | ||
ax.legend(legend_elements.values(), legend_elements.keys(), loc=(1.05, .8),) |
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.
Similarly to previous plot:
- is the trailing coma mandatory?
(1.05, .8)
<-(1.05, 0.8)
?
'Scatter': Line2D([0], [0], marker='o', color='w', | ||
markerfacecolor='g', markersize=15), | ||
'Color Patch': Patch(facecolor='orange', edgecolor='r')} | ||
fig, ax = plt.subplots() |
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.
Purely cosmetic remark if it was not done on purpose: in the example above, there isn't a blank line after the imports but there is one before instantiating the figure, while in the current example it is the opposite
Well, I would say that the current legend tutorial may be quite scary for beginners: it quickly becomes quite advanced (at least from my point of view). This example keep things rather simple and should already cover most of the usual "custom legend" needs (apart maybe from the "multiple legends on the same Axes instance" one). Maybe it could point toward the “advanced” legend tutorial with an hyperlink at the end: most of the end-users (, who I know ^^,) start scrolling the example gallery before even trying to look for a proper tutorial. |
For sure the tutorial should be linked from the example. I'll include that w/ the next round of edits when I address your comments @afvincent ... thanks for the feedback! |
markerfacecolor='g', markersize=15), | ||
'Color Patch': Patch(facecolor='orange', edgecolor='r')} | ||
fig, ax = plt.subplots() | ||
ax.legend(legend_elements.values(), legend_elements.keys(), loc=(1.05, .8),) |
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.
Note that the order of iteration over values()/keys() is not guaranteed (in fact it will change between iterations on Py3.4/3.5), so please use lists.
Also I think it is much better style to set the label on the artist itself:
legend_elements = [
Line2D(..., label="foo"),
Patch(..., label="bar)
]
ax.legend(legend_elements)
The comments apply throughout the tutorial of course.
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.
Good point re: lists
for setting the label on the artist, IIRC doing that didn't automatically add labels when I called ax.legend
but I could be wrong...
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.
ya, when I do
legend_elements = [Line2D([0], [0], color='b', lw=4, label='Line'),
Line2D([0], [0], marker='o', color='w', label='Scatter',
markerfacecolor='g', markersize=15),
Patch(facecolor='orange', edgecolor='r',
label='Color Patch')]
fig, ax = plt.subplots()
ax.legend(legend_elements)
is that a bug?
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.
Of course :/, this has to be plt.legend(handles=[...])
, otherwise it thinks you're passing labels for some nonexistent artists. Stupid API, refactoring it is somewhere on my (thick) todo.
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.
Well we could do it here: #9324 I agree its a backwards API.
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 would suggest having this on a separate PR. Basically, if only one positional argument is passed and it's a list of artists, then consider it as handles instead of labels.
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.
in the meantime, I'll just use plt
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.
hmmm, I actually just tried it w/ plt
and had the same behavior...
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 nvm, I see what you mean about handles=
. That works for ax.legend
as well actually, but just giving it as a positional argument works for neither.
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.
Right - the positional argument is for labels believe it or not. i.e. ax.legend({'a', 'b', 'c'}) will label the first three artists it finds on
ax` 'a', 'b', and 'c'. I guess artists are stored in the order they are drawn, but it seems like a strange choice to me. Once #9324 gets merged it'll be easy to check for a list of handles instead and make the call you were trying to make work.
6f77d8e
to
c3bd838
Compare
ok, comments addressed in latest push. |
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.
Mainly an outdated comment to remove. But otherwise, my previous comments have indeed been addressed :).
Do not forget to include a link toward the legend tutorial somewhere.
# Create the figure | ||
# If you use `ax.legend`, you'll also need to give the labels manually. | ||
fig, ax = plt.subplots() | ||
ax.legend(handles=legend_elements, loc=(0.5, 0.5)) |
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 understand that one kind of gave up on the idea on using out-of-axes legend before the aforementioned @jklymak's PRs are merged, but then is there a peculiar reason not to simply use loc="center"
instead of loc=(0.5, 0.5)
. Except of course if one really want to align the lower left of the legend with the center of the axes.
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.
nope, I'm just being dumb :-)
label='Color Patch')] | ||
|
||
# Create the figure | ||
# If you use `ax.legend`, you'll also need to give the labels manually. |
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 think that the line above should not be here anymore, should 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.
nope, will remove
Just to "me too" @anntzer's comment. It'd be great if this new tutorial could find a nice home in one of the other tutorials. |
re: creating a new example vs. merging it with pre-existing examples, I'm of two minds on this one. On one hand, I agree that we don't want content to be spread too thinly. On the other hand, I think that appending this content to the currently-existing legend examples isn't a great idea, because A. those examples are already long enough that they should be in the legend tutorial, not as examples, I think most people treat the examples gallery by quickly scanning the page of That said, I totally agree w/ you that we the way legends are handled in the docs should be improved, and I agree some should be removed. We should open an issue on streamlining the legend content using the list that you put together. |
c3bd838
to
1097a48
Compare
latest push addresses @afvincent 's comments |
you get to do it :-) |
d991533
to
0c6fca9
Compare
ok, #9458 created @afvincent you're currently blocking on requested changes...anything else I need to do? |
0c6fca9
to
058dda7
Compare
LGTM. A remaining comment, but non-blocking from my side, is the following: should one add a fixed seed to make the random traces reproducible (like @NelleV did a while ago for all the examples that were drawing random data), or are this kind of things now directly handled by SG? |
058dda7
to
561180a
Compare
good call, just added randomness fixer |
This is an example to demonstrate how to create legends from scratch using Matplotlib objects. This is often useful if you add a lot of elements to a plot but only want to point out a couple in your legend.
Some stuff that could be improved: