1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
|
.. _plotting-guide-legend:
************
Legend guide
************
Do not proceed unless you already have read :func:`~matplotlib.pyplot.legend`
and :class:`matplotlib.legend.Legend`!
What to be displayed
====================
The legend command has a following call signature::
legend(*args, **kwargs)
If len(args) is 2, the first argument should be a list of artist to be
labeled, and the second argument should a list of string labels. If
len(args) is 0, it automatically generate the legend from label
properties of the child artists by calling
:meth:`~matplotlib.axes.Axes.get_legend_handles_labels` method.
For example, *ax.legend()* is equivalent to::
handles, labels = ax.get_legend_handles_labels()
ax.legend(handles, labels)
The :meth:`~matplotlib.axes.Axes.get_legend_handles_labels` method
returns a tuple of two lists, i.e., list of artists and list of labels
(python string). However, it does not return all of its child
artists. It returns artists that are currently supported by matplotlib.
For matplotlib v1.0 and earlier, the supported artists are as follows.
* :class:`~matplotlib.lines.Line2D`
* :class:`~matplotlib.patches.Patch`
* :class:`~matplotlib.collections.LineCollection`
* :class:`~matplotlib.collections.RegularPolyCollection`
* :class:`~matplotlib.collections.CircleCollection`
And, :meth:`~matplotlib.axes.Axes.get_legend_handles_labels` returns
all artists in *ax.lines*, *ax.patches* and
artists in *ax.collection* which are instance of
:class:`~matplotlib.collections.LineCollection` or
:class:`~matplotlib.collections.RegularPolyCollection`. The label
attributes (returned by get_label() method) of collected artists are
used as text labels. If label attribute is empty string or starts with
"_", those artists will be ignored.
Therefore, plots drawn by some *pyplot* commands are not supported by
legend. For example, :func:`~matplotlib.pyplot.fill_between` creates
:class:`~matplotlib.collections.PolyCollection` that is not
supported. Also support is limted for some commands that creat
multiple artists. For example, :func:`~matplotlib.pyplot.errorbar`
creates multiples :class:`~matplotlib.lines.Line2D` instances.
Unfortunately, there is no easy workaround when you need legend for an
artist not supported by matplotlib (You may use one of the supported
artist as a proxy. See below)
In newer version of matplotlib (v1.1 and later), the matplotlib
internals are revised to support
* complex plots that creates multiple artists (e.g., bar, errorbar, etc)
* custom legend handles
See below for details of new functionality.
Adjusting the Order of Legend items
-----------------------------------
When you want to customize the list of artists to be displayed in the
legend, or their order of appearance. There are a two options. First,
you can keep lists of artists and labels, and explicitly use these for
the first two argument of the legend call.::
p1, = plot([1,2,3])
p2, = plot([3,2,1])
p3, = plot([2,3,1])
legend([p2, p1], ["line 2", "line 1"])
Or you may use :meth:`~matplotlib.axes.Axes.get_legend_handles_labels`
to retrieve list of artist and labels and manipulate them before
feeding them to legend call.::
ax = subplot(1,1,1)
p1, = ax.plot([1,2,3], label="line 1")
p2, = ax.plot([3,2,1], label="line 2")
p3, = ax.plot([2,3,1], label="line 3")
handles, labels = ax.get_legend_handles_labels()
# reverse the order
ax.legend(handles[::-1], labels[::-1])
# or sort them by labels
import operator
hl = sorted(zip(handles, labels),
key=operator.itemgetter(1))
handles2, labels2 = zip(*hl)
ax.legend(handles2, labels2)
Using Proxy Artist
------------------
When you want to display legend for an artist not supported by
matplotlib, you may use another artist as a proxy. For
example, you may create a proxy artist without adding it to the axes
(so the proxy artist will not be drawn in the main axes) and feed it
to the legend function.::
p = Rectangle((0, 0), 1, 1, fc="r")
legend([p], ["Red Rectangle"])
Multicolumn Legend
==================
By specifying the keyword argument *ncol*, you can have a multi-column
legend. Also, mode="expand" horizontally expand the legend to fill the
axes area. See `legend_demo3.py
<http://matplotlib.sourceforge.net/examples/pylab_examples/legend_demo3.html>`_
for example.
Legend location
===============
The location of the legend can be specified by the keyword argument
*loc*, either by string or a integer number.
============= ======
String Number
============= ======
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
============= ======
By default, the legend will anchor to the bbox of the axes
(for legend) or the bbox of the figure (figlegend). You can specify
your own bbox using *bbox_to_anchor* argument. *bbox_to_anchor* can be an
instance of :class:`~matplotlib.transforms.BboxBase`, a tuple of 4
floats (x, y, width, height of the bbox), or a tuple of 2 floats (x, y
with width=height=0). Unless *bbox_transform* argument is given, the
coordinates (even for the bbox instance) are considered as normalized
axes coordinates.
For example, if you want your axes legend located at the figure corner
(instead of the axes corner)::
l = legend(bbox_to_anchor=(0, 0, 1, 1), bbox_transform=gcf().transFigure)
Also, you can place above or outer right-hand side of the axes,
.. plot:: users/plotting/examples/simple_legend01.py
:include-source:
Multiple Legend
===============
Sometime, you want to split the legend into multiple ones.::
p1, = plot([1,2,3])
p2, = plot([3,2,1])
legend([p1], ["Test1"], loc=1)
legend([p2], ["Test2"], loc=4)
However, the above code only shows the second legend. When the legend
command is called, a new legend instance is created and old ones are
removed from the axes. Thus, you need to manually add the removed
legend.
.. plot:: users/plotting/examples/simple_legend02.py
:include-source:
.. _legend-complex-plots:
Legend of Complex Plots
=======================
In matplotlib v1.1 and later, the legend is
improved to support more plot commands and ease the customization.
Artist Container
----------------
The Artist Container is simple class (derived from tuple) that
contains multiple artists. This is introduced primarily to support
legends for complex plot commands that create multiple artists.
Axes instances now have a "containers" attribute (which is a list, and
this is only intended to be used for generating a legend). The items
in this attribute are also returned by
:meth:`~matplotlib.axes.Axes.get_legend_handles_labels`.
For example, "bar" command creates a series of Rectangle
patches. Previously, it returned a list of these patches. With the
current change, it creates a container object of these rectangle
patches (and these patches are added to Axes.patches attribute as
before) and return it instead. As the container class is derived from
a tuple, it should be backward-compatible. Furthermore, the container
object is added to the Axes.containers attributes so that legend
command can properly create a legend for the bar. Thus, you may do ::
b1 = bar([0, 1, 2], [0.2, 0.3, 0.1], width=0.4,
label="Bar 1", align="center")
legend()
or ::
b1 = bar([0, 1, 2], [0.2, 0.3, 0.1], width=0.4, align="center")
legend([b1], ["Bar 1"])
At this time of writing, however, only "bar", "errorbar", and "stem" are
supported (hopefully the list will increase). Here is an example.
.. plot:: mpl_examples/pylab_examples/legend_demo4.py
Legend Handler
--------------
One of the changes is that drawing of legend handles has been delegated to
legend handlers. For example, :class:`~matplotlib.lines.Line2D`
instances are handled by
:class:`~matplotlib.legend_handler.HandlerLine2D`. The mapping
between the artists and their corresponding handlers are defined in a
handler_map of the legend. The handler_map is a dictionary of
key-handler pair, where key can be an artist instance or its
class. And the handler is a Handler instance.
Let's consider the following sample code, ::
legend([p_1, p_2,..., p_i, ...], ["Test 1", "Test 2", ..., "Test i",...])
For each *p_i*, matplotlib
1. check if *p_i* is in the handler_map
2. if not, iterate over type(p_i).mro() until a matching key is found
in the handler_map
Unless specified, the defaul handler_map is used. Below is a partial
list of key-handler pairs included in the default handler map.
* Line2D : legend_handler.HandlerLine2D()
* Patch : legend_handler.HandlerPatch()
* LineCollection : legend_handler.HandlerLineCollection()
* ...
The legend() command takes an optional argument of "handler_map". When
provided, the default handler map will be updated (using dict.update
method) with the provided one. ::
p1, = plot(x, "ro", label="test1")
p2, = plot(y, "b+", ms=10, label="test2")
my_handler = HandlerLine2D(numpoints=1)
legend(handler_map={Line2D:my_handler})
The above example will use *my_handler* for any Line2D
instances (p1 and p2). ::
legend(handler_map={p1:HandlerLine2D(numpoints=1)})
In the above example, only *p1* will be handled by *my_handler*, while
others will be handled by default handlers.
The curent default handler_map has handlers for errorbar and bar
plots. Also, it includes an entry for `tuple` which is mapped to
`HandlerTuple`. It simply plots over all the handles for items in the
given tuple. For example,
.. plot::
:include-source:
z = np.random.randn(10)
p1a, = plt.plot(z, "ro", ms=10, mfc="r", mew=2, mec="r") # red filled circle
p1b, = plt.plot(z[:5], "w+", ms=10, mec="w", mew=2) # white cross
plt.legend([p1a, (p1a, p1b)], ["Attr A", "Attr A+B"])
Implement a Custom Handler
--------------------------
Handler can be any callable object with following signature. ::
def __call__(self, legend, orig_handle,
fontsize,
handlebox):
Where *legend* is the legend itself, *orig_handle* is the original
plot (*p_i* in the above example), *fontsize* is the fontsize in
pixles, and *handlebox* is a OffsetBox instance. Within the call, you
create relevant artists (using relevant properties from the *legend*
and/or *orig_handle*) and add them into the handlebox. The artists
needs to be scaled according to the fontsize (note that the size is in
pixel, i.e., this is dpi-scaled value). See :mod:`~matplotlib.legend_handler`
for more details.
|