8000 update legend demo examples and legend guide · matplotlib/matplotlib@8d5110d · GitHub
[go: up one dir, main page]

Skip to content

Commit 8d5110d

Browse files
committed
update legend demo examples and legend guide
1 parent c96052e commit 8d5110d

File tree

3 files changed

+170
-56
lines changed

3 files changed

+170
-56
lines changed

doc/users/legend_guide.rst

Lines changed: 153 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -28,39 +28,43 @@ For example, *ax.legend()* is equivalent to::
2828
The :meth:`~matplotlib.axes.Axes.get_legend_handles_labels` method
2929
returns a tuple of two lists, i.e., list of artists and list of labels
3030
(python string). However, it does not return all of its child
31-
artists. It returns all artists in *ax.lines* and *ax.patches* and
32-
some artists in *ax.collection* which are instance of
31+
artists. It returns artists that are currently supported by matplotlib.
32+
33+
For matplotlib v1.0 and before, the supported artists are as follows.
34+
35+
* :class:`~matplotlib.lines.Line2D`
36+
* :class:`~matplotlib.patches.Patch`
37+
* :class:`~matplotlib.collections.LineCollection`
38+
* :class:`~matplotlib.collections.RegularPolyCollection`
39+
40+
And, :meth:`~matplotlib.axes.Axes.get_legend_handles_labels` returns
41+
all artists in *ax.lines*, *ax.patches* and
42+
artists in *ax.collection* which are instance of
3343
:class:`~matplotlib.collections.LineCollection` or
3444
:class:`~matplotlib.collections.RegularPolyCollection`. The label
3545
attributes (returned by get_label() method) of collected artists are
3646
used as text labels. If label attribute is empty string or starts with
37-
"_", that artist will be ignored.
47+
"_", those artists will be ignored.
3848

3949

40-
* Note that not all kind of artists are supported by the legend. The
41-
following is the list of artists that are currently supported.
50+
Therefore, plots drawn by some *pyplot* commands are not supported by
51+
legend. For example, :func:`~matplotlib.pyplot.fill_between` creates
52+
:class:`~matplotlib.collections.PolyCollection` that is not
53+
supported. Also support is limted for some commands that creat
54+
multiple artists. For example, :func:`~matplotlib.pyplot.errorbar`
55+
creates multiples :class:`~matplotlib.lines.Line2D` instances.
4256

43-
* :class:`~matplotlib.lines.Line2D`
44-
* :class:`~matplotlib.patches.Patch`
45-
* :class:`~matplotlib.collections.LineCollection`
46-
* :class:`~matplotlib.collections.RegularPolyCollection`
57+
Unfortunately, there is no easy workaround when you need legend for an
58+
artist not supported by matplotlib (You may use one of the supported
59+
artist as a proxy. See below)
4760

48-
Unfortunately, there is no easy workaround when you need legend for
49-
an artist not in the above list (You may use one of the supported
50-
artist as a proxy. See below), or customize it beyond what is
51-
supported by :class:`matplotlib.legend.Legend`.
61+
In newer version of matplotlib (v1.1 and later), the matplotlib
62+
internals are revised to support
5263

53-
* Remember that some *pyplot* commands return artist not supported by
54-
legend, e.g., :func:`~matplotlib.pyplot.fill_between` returns
55-
:class:`~matplotlib.collections.PolyCollection` that is not
56-
supported. Or some return multiple artists. For example,
57-
:func:`~matplotlib.pyplot.plot` returns list of
58-
:class:`~matplotlib.lines.Line2D` instances, and
59-
:func:`~matplotlib.pyplot.errorbar` returns a length 3 tuple of
60-
:class:`~matplotlib.lines.Line2D` instances.
64+
* complex plots that creates multiple artists (e.g., bar, errorbar, etc)
65+
* custom legend handles
6166

62-
* The legend does not care about the axes that given artists belongs,
63-
i.e., the artists may belong to other axes or even none.
67+
See below for details of new functionality.
6468

6569

6670
Adjusting the Order of Legend items
@@ -180,3 +184,129 @@ legend.
180184

181185
.. plot:: users/plotting/examples/simple_legend02.py
182186
:include-source:
187+
188+
189+
Legend of Complex Plots
190+
=======================
191+
192+
In matplotlib v1.1 (FIXME when released) and later, the legend is
193+
improved to support more plot commands and ease the customization.
194+
195+
Legend Handler
196+
--------------
197+
198+
One of the change is that drawing of legend handles is delegated to
199+
legend handlers. For example, :class:`~matplotlib.lines.Line2D`
200+
instances are handled by
201+
:class:`~matplotlib.legend_handler.HandlerLine2D`. The mapping
202+
between the artists and their corresponding handlers are defined in a
203+
the handler_map of the legend. The handler_map is a dictionary of
204+
key-handler pair, where key can be an artist instance or its
205+
class. And the handler is a Handler instance.
206+
207+
Let's consider the following sample code, ::
208+
209+
legend([p_1, p_2,..., p_i, ...], ["Test 1", "Test 2", ..., "Test i",...])
210+
211+
For each *p_i*, matplotlib
212+
213+
1. check if *p_i* itself is in the handler_map
214+
2. if not, iterate over type(p_i).mro() until a matching key is found in the handler_map
215+
216+
217+
For example, the default handler_map
218+
has following key-handler pairs (below is only a part of them).
219+
220+
* Line2D : legend_handler.HandlerLine2D()
221+
* Patch : legend_handler.HandlerPatch()
222+
* LineCollection : legend_handler.HandlerLineCollection()
223+
* ...
224+
225+
The legend command takes an optional argument of "handler_map". When
226+
provided, the default handler will be updated (using dict.update
227+
method) with the provided one. ::
228+
229+
p1, = plot(x, "ro", label="test1")
230+
p2, = plot(y, "b+", ms=10, label="test2")
231+
232+
my_handler = HandlerLine2D(numpoints=1)
233+
234+
legend(handler_map={Line2D:my_handler})
235+
236+
The above example will use *my_handler* for any Line2D
237+
instances (p1 and p2). ::
238+
239+
legend(handler_map={p1:HandlerLine2D(numpoints=1)})
240+
241+
In the above example, only *p1* will be handled by *my_handler*, while
242+
others will be handled by default handlers.
243+
244+
245+
Artist Container
246+
----------------
247+
248+
The Artist Container is simple class (derived from tuple) that
249+
contains multiple artists. This is introduced primarily to support
250+
legends for complex plot commands that create multiple artists.
251+
252+
Axes instances now have a "containers" attribute (which is a list, and
253+
this is only intended to be used for generating a legend). The items
254+
in this attribute are also returned by
255+
:meth:`~matplotlib.axes.Axes.get_legend_handles_labels`.
256+
257+
For example, "bar" command creates a series of Rectangle
258+
patches. Previously, it returned a list of these patches. With the
259+
current change, it creates a container object of these rectangle
260+
patches (and these patches are added to Axes.patches attribute as
261+
before) and return it instead. As the container class is derived from
262+
a tuple, it should be backward-compatible. Furthermore, the container
263+
object is added to the Axes.containers attributes so that legend
264+
command can properly create a legend for the bar. Thus, you may do ::
265+
266+
b1 = bar([0, 1, 2], [0.2, 0.3, 0.1], width=0.4,
267+
label="Bar 1", align="center")
268+
legend()
269+
270+
or ::
271+
272+
b1 = bar([0, 1, 2], [0.2, 0.3, 0.1], width=0.4, align="center")
273+
legend([b1], ["Bar 1"])
274+
275+
276+
At this time of writing, however, "bar" and "errorbar" are only
277+
supported (hopefully the list will increase). Here is an example.
278+
279+
.. plot:: mpl_examples/pylab_examples/legend_demo4.py
280+
281+
The default *handler_map* has an entry for "tuple" which is mapped to
282+
*HandlerTuple*. It simply overplots all the handles for items in the
283+
given tuple. For example,
284+
285+
.. plot::
286+
:include-source:
287+
288+
z = np.random.randn(10)
289+
290+
p1a, = plt.plot(z, "ro", ms=10, mfc="r", mew=2, mec="r") # red filled circle
291+
p1b, = plt.plot(z, "w+", ms=10, mec="w", mew=2) # white cross
292+
293+
plt.legend([(p1a, p1b)], ["Attr A+B"])
294+
295+
296+
Implement a Custom Handler
297+
--------------------------
298+
299+
Handler can any callable object with following signature. ::
300+
301+
def __call__(self, legend, orig_handle,
302+
fontsize,
303+
handlebox):
304+
305+
Where *legend* is the legend itself, *orig_handle* is the original
306+
plot (*p_i* in the above example), *fontsize* is the fontsize in
307+
pixles, and *handlebox* is a OffsetBox instance. Within the call, you
308+
create relevant artists (using relevant properties from the *legend*
309+
and/or *orig_handle*) and add them into the handlebox. The artists
310+
needs to be scaled according to the fontsize (note that the size is in
311+
pixel, i.e., this is dpi-scaled value). See legend_handler.py for more
312+
details.
Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,22 @@
11
import matplotlib.pyplot as plt
22

3-
if 1:
4-
5-
ax = plt.subplot(111)
3+
ax = plt.subplot(111)
64

7-
b1 = ax.bar([0, 1, 2], [0.2, 0.3, 0.1], width=0.4,
8-
label="Bar 1", align="center")
9-
10-
b2 = ax.bar([0.5, 1.5, 2.5], [0.3, 0.2, 0.2], color="red", width=0.4,
11-
label="Bar 2", align="center")
12-
13-
err1 = ax.errorbar([0, 1, 2], [2, 3, 1], xerr=0.4, fmt="s",
14-
label="test 1")
15-
err2 = ax.errorbar([0, 1, 2], [3, 2, 4], yerr=0.3, fmt="o",
16-
label="test 2")
17-
err3 = ax.errorbar([0, 1, 2], [1, 1, 3], xerr=0.4, yerr=0.3, fmt="^",
18-
label="test 3")
19-
20-
# legend
21-
leg1 = plt.legend(loc=1)
5+
b1 = ax.bar([0, 1, 2], [0.2, 0.3, 0.1], width=0.4,
6+
label="Bar 1", align="center")
227

23-
# legend of selected artists
24-
artists = [b1, err2]
25-
leg2 = plt.legend(artists, [a.get_label() for a in artists], loc=2)
8+
b2 = ax.bar([0.5, 1.5, 2.5], [0.3, 0.2, 0.2], color="red", width=0.4,
9+
label="Bar 2", align="center")
2610

27-
# custome handler
28-
import matplotlib.legend_handler as mlegend_handler
29-
myhandler = mlegend_handler.HandlerErrorbar(npoints=1)
11+
err1 = ax.errorbar([0, 1, 2], [2, 3, 1], xerr=0.4, fmt="s",
12+
label="test 1")
13+
err2 = ax.errorbar([0, 1, 2], [3, 2, 4], yerr=0.3, fmt="o",
14+
label="test 2")
15+
err3 = ax.errorbar([0, 1, 2], [1, 1, 3], xerr=0.4, yerr=0.3, fmt="^",
16+
label="test 3")
3017

31-
leg3 = plt.legend([err1, err3], ["T1", "T2"], loc=3,
32-
handler_map={err3:myhandler})
18+
# legend
19+
leg1 = plt.legend(loc=1)
3320

34-
plt.gca().add_artist(leg1)
35-
plt.gca().add_artist(leg2)
36-
37-
plt.show()
21+
plt.show()
3822

examples/pylab_examples/legend_demo_custom_handler.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414

1515

16-
from matplotlib.legend_handler import HandlerPatch, HandlerMulti
16+
from matplotlib.legend_handler import HandlerPatch
1717

1818
def make_legend_ellipse(legend, orig_handle,
1919
xdescent, ydescent,
@@ -23,8 +23,8 @@ def make_legend_ellipse(legend, orig_handle,
2323

2424
return p
2525

26-
plt.legend([c, "handle 2"], ["Label 1", "Label 2"],
26+
plt.legend([c, (l1, l2)], ["Label 1", "Label 2"],
2727
handler_map={mpatches.Circle:HandlerPatch(patch_func=make_legend_ellipse),
28-
"handle 2":HandlerMulti(l1, l2)})
28+
})
2929

3030
plt.show()

0 commit comments

Comments
 (0)
0