-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Allow figure.legend to be called without arguments #7811
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
figure.legend() can be called without arguments | ||
----------------------------------------------- | ||
|
||
Calling :func:`figure.legend` can now be done with no arguments. In this case a | ||
legend will be created that contains all the artists on all the axes contained | ||
within the figure. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,28 @@ | ||
""" | ||
================== | ||
Figure legend demo | ||
================== | ||
|
||
Instead of plotting a legend on each axis, a legend for all the artists on all | ||
the sub-axes of a figure can be plotted instead. | ||
""" | ||
|
||
import numpy as np | ||
import matplotlib.pyplot as plt | ||
|
||
fig = plt.figure() | ||
ax1 = fig.add_axes([0.1, 0.1, 0.4, 0.7]) | ||
ax2 = fig.add_axes([0.55, 0.1, 0.4, 0.7]) | ||
fig, axs = plt.subplots(1, 2) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
x = np.arange(0.0, 2.0, 0.02) | ||
y1 = np.sin(2*np.pi*x) | ||
y2 = np.exp(-x) | ||
l1, l2 = ax1.plot(x, y1, 'rs-', x, y2, 'go') | ||
l1, l2 = axs[0].plot(x, y1, 'rs-', x, y2, 'go') | ||
|
||
y3 = np.sin(4*np.pi*x) | ||
y4 = np.exp(-2*x) | ||
l3, l4 = ax2.plot(x, y3, 'yd-', x, y4, 'k^') | ||
l3, l4 = axs[1].plot(x, y3, 'yd-', x, y4, 'k^') | ||
|
||
fig.legend((l1, l2), ('Line 1', 'Line 2'), 'upper left') | ||
fig.legend((l3, l4), ('Line 3', 'Line 4'), 'upper right') | ||
|
||
plt.tight_layout() | ||
plt.show() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1262,127 +1262,217 @@ def draw_artist(self, a): | |
def get_axes(self): | ||
return self.axes | ||
|
||
def legend(self, handles, labels, *args, **kwargs): | ||
def legend(self, *args, **kwargs): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While you are at it, do you mind migrating this to a numpydoc compatible docstring? |
||
""" | ||
Place a legend in the figure. Labels are a sequence of | ||
strings, handles is a sequence of | ||
:class:`~matplotlib.lines.Line2D` or | ||
:class:`~matplotlib.patches.Patch` instances, and loc can be a | ||
string or an integer specifying the legend location | ||
Place a legend on the figure. | ||
|
||
USAGE:: | ||
To make a legend from existing artists on every axes:: | ||
|
||
legend() | ||
|
||
To make a legend for a list of lines and labels:: | ||
|
||
legend( (line1, line2, line3), | ||
('label1', 'label2', 'label3'), | ||
'upper right') | ||
|
||
The *loc* location codes are:: | ||
|
||
'best' : 0, (currently not supported for figure legends) | ||
'upper right' : 1, | ||
'upper left' : 2, | ||
'lower left' : 3, | ||
'lower right' : 4, | ||
'right' : 5, | ||
'center left' : 6, | ||
'center right' : 7, | ||
'lower center' : 8, | ||
'upper center' : 9, | ||
'center' : 10, | ||
|
||
*loc* can also be an (x,y) tuple in figure coords, which | ||
specifies the lower left of the legend box. figure coords are | ||
(0,0) is the left, bottom of the figure and 1,1 is the right, | ||
top. | ||
|
||
Keyword arguments: | ||
|
||
prop: [ *None* | FontProperties | dict ] | ||
A :class:`matplotlib.font_manager.FontProperties` | ||
instance. If *prop* is a dictionary, a new instance will be | ||
created with *prop*. If *None*, use rc settings. | ||
|
||
numpoints: integer | ||
Parameters | ||
---------- | ||
loc : string or integer | ||
The location of the legend. Possible codes are: | ||
|
||
=============== ============= | ||
Location String Location Code | ||
=============== ============= | ||
'upper right' 1 | ||
'upper left' 2 | ||
'lower left' 3 | ||
'lower right' 4 | ||
'right' 5 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder why there's this seemingly extra location. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's the same in axes.legend() too. |
||
'center left' 6 | ||
'center right' 7 | ||
'lower center' 8 | ||
'upper center' 9 | ||
'center' 10 | ||
=============== ============= | ||
|
||
*loc* can also be an (x,y) tuple in figure coords, which specifies | ||
the lower left of the legend box. In figure coords (0,0) is the | ||
bottom left of the figure, and (1,1) is the top right. | ||
|
||
prop : None or FontProperties or dict | ||
A :class:`matplotlib.font_manager.FontProperties` instance. If | ||
*prop* is a dictionary, a new instance will be created with *prop*. | ||
If *None*, use rc settings. | ||
|
||
numpoints : integer | ||
The number of points in the legend line, default is 4 | ||
|
||
scatterpoints: integer | ||
scatterpoints : integer | ||
The number of points in the legend line, default is 4 | ||
|
||
scatteryoffsets: list of floats | ||
a list of yoffsets for scatter symbols in legend | ||
scatteryoffsets : list of floats | ||
A list of yoffsets for scatter symbols in legend | ||
|
||
markerscale: [ *None* | scalar ] | ||
markerscale : None or scalar | ||
The relative size of legend markers vs. original. If *None*, use rc | ||
settings. | ||
|
||
markerfirst: [ *True* | *False* ] | ||
if *True*, legend marker is placed to the left of the legend label | ||
if *False*, legend marker is placed to the right of the legend | ||
label | ||
markerfirst : bool | ||
If *True*, legend marker is placed to the left of the legend label. | ||
If *False*, legend marker is placed to the right of the legend | ||
label. | ||
|
||
frameon: [ *None* | bool ] | ||
frameon : None or bool | ||
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>`. | ||
|
||
fancybox: [ *None* | *False* | *True* ] | ||
if *True*, draw a frame with a round fancybox. If *None*, use rc | ||
fancybox : None or bool | ||
If *True*, draw a frame with a round fancybox. If *None*, use rc | ||
settings. | ||
|
||
shadow: [ *None* | *False* | *True* ] | ||
shadow : None or bool | ||
If *True*, draw a shadow behind legend. If *None*, use rc settings. | ||
|
||
framealpha: [ *None* | float ] | ||
framealpha : None or float | ||
Control the alpha transparency of the legend's background. | ||
Default is *None* which will take the value from the | ||
``legend.framealpha`` :data:`rcParam<matplotlib.rcParams>`. | ||
|
||
facecolor: [ *None* | "inherit" | a color spec ] | ||
facecolor : None or "inherit" or a color spec | ||
Control the legend's background color. | ||
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>`. | ||
|
||
edgecolor: [ *None* | "inherit" | a color spec ] | ||
edgecolor : None or "inherit" or a color spec | ||
Control the legend's background patch edge color. | ||
Default is *None* which will take the value from the | ||
``legend.edgecolor`` :data:`rcParam<matplotlib.rcParams>`. | ||
If ``"inherit"``, it will take the ``axes.edgecolor`` | ||
:data:`rcParam<matplotlib.rcParams>`. | ||
|
||
ncol : integer | ||
number of columns. default is 1 | ||
ncol : integer | ||
Number of columns. Default is 1. | ||
|
||
mode : [ "expand" | *None* ] | ||
if mode is "expand", the legend will be horizontally expanded | ||
mode : "expand" or None | ||
If mode is "expand", the legend will be horizontally expanded | ||
to fill the axes area (or *bbox_to_anchor*) | ||
|
||
title : string | ||
the legend title | ||
title : string | ||
The legend title | ||
|
||
borderpad : float or None | ||
The fractional whitespace inside the legend border, measured in | ||
font-size units. | ||
Default is *None* which will take the value from the | ||
``legend.borderpad`` :data:`rcParam<matplotlib.rcParams>`. | ||
|
||
labelspacing : float or None | ||
The vertical space between the legend entries, measured in | ||
font-size units. | ||
Default is *None* which will take the value from the | ||
``legend.labelspacing`` :data:`rcParam<matplotlib.rcParams>`. | ||
|
||
handlelength : float or None | ||
The length of the legend handles, measured in font-size units. | ||
Default is *None* which will take the value from the | ||
``legend.handlelength`` :data:`rcParam<matplotlib.rcParams>`. | ||
|
||
handletextpad : float or None | ||
The padding between the legend handle and text, measured in | ||
font-size units. | ||
Default is *None* which will take the value from the | ||
``legend.handletextpad`` :data:`rcParam<matplotlib.rcParams>`. | ||
|
||
Padding and spacing between various elements use following keywords | ||
parameters. The dimensions of these values are given as a fraction | ||
of the fontsize. Values from rcParams will be used if None. | ||
borderaxespad : float or None | ||
The padding between the axes and legend border, measured in | ||
font-size units. | ||
Default is *None* which will take the value from the | ||
``legend.borderaxespad`` :data:`rcParam<matplotlib.rcParams>`. | ||
|
||
================ ==================================================== | ||
Keyword Description | ||
================ ==================================================== | ||
borderpad the fractional whitespace inside the legend border | ||
labelspacing the vertical space between the legend entries | ||
handlelength the length of the legend handles | ||
handletextpad the pad between the legend handle and text | ||
borderaxespad the pad between the axes and legend border | ||
columnspacing the spacing between columns | ||
================ ==================================================== | ||
columnspacing : float or None | ||
The spacing between columns, measured in font-size units. | ||
Default is *None* which will take the value from the | ||
``legend.columnspacing`` :data:`rcParam<matplotlib.rcParams>`. | ||
|
||
.. Note:: Not all kinds of artist are supported by the legend. | ||
See LINK (FIXME) for details. | ||
Returns | ||
------- | ||
:class:`matplotlib.legend.Legend` instance | ||
|
||
**Example:** | ||
Notes | ||
----- | ||
Not all kinds of artist are supported by the legend command. | ||
See :ref:`plotting-guide-legend` for details. | ||
|
||
Examples | ||
-------- | ||
.. plot:: mpl_examples/pylab_examples/figlegend_demo.py | ||
""" | ||
l = Legend(self, handles, labels, *args, **kwargs) | ||
|
||
# If no arguments given, collect up all the artists on the figure | ||
if len(args) == 0: | ||
handles = [] | ||
labels = [] | ||
|
||
def in_handles(h, l): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be nice to leave a comment as to what's happening here (at least it's not obvious to me at all...). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How's that? If you can think of better wording feel free to push to my branch. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good. Typo: "consdier". |
||
# Method to check if we already have a given handle and label. | ||
# Consider two handles to be the same if they share a label, | ||
# color, facecolor, and edgecolor. | ||
|
||
# Loop through each handle and label already collected | ||
for f_h, f_l in zip(handles, labels): | ||
if f_l != l: | ||
continue | ||
if type(f_h) != type(h): | ||
continue | ||
try: | ||
if f_h.get_color() != h.get_color(): | ||
continue | ||
except AttributeError: | ||
pass | ||
try: | ||
if f_h.get_facecolor() != h.get_facecolor(): | ||
continue | ||
except AttributeError: | ||
pass | ||
try: | ||
if f_h.get_edgecolor() != h.get_edgecolor(): | ||
continue | ||
except AttributeError: | ||
pass | ||
return True | ||
return False | ||
|
||
for ax in self.axes: | ||
ax_handles, ax_labels = ax.get_legend_handles_labels() | ||
for h, l in zip(ax_handles, ax_labels): | ||
if not in_handles(h, l): | ||
handles.append(h) | ||
labels.append(l) | ||
if len(handles) == 0: | ||
warnings.warn("No labeled objects found. " | ||
"Use label='...' kwarg on individual plots.") | ||
return None | ||
|
||
elif len(args) == 2: | ||
# LINES, LABELS | ||
handles, labels = args | ||
|
||
elif len(args) == 3: | ||
# LINES, LABELS, LOC | ||
handles, labels, loc = args | ||
kwargs['loc'] = loc | ||
|
||
else: | ||
raise TypeError('Invalid number of arguments passed to legend. ' | ||
'Please specify either 0 args, 2 args ' | ||
'(artist handles, figure labels) or 3 args ' | ||
'(artist handles, figure labels, legend location)') | ||
|
||
l = Legend(self, handles, labels, **kwargs) | ||
self.legends.append(l) | ||
l._remove_method = lambda h: self.legends.remove(h) | ||
self.stale = True | ||
|
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.
While you are at it, do you mind adding a docstring to give this example a title and explain what it does? See http://matplotlib.org/devdocs/devel/MEP/MEP12.html for examples.