8000 minor api improvement and doc change · matplotlib/matplotlib@5fd689b · GitHub
[go: up one dir, main page]

Skip to content

Commit 5fd689b

Browse files
committed
minor api improvement and doc change
1 parent 71dad75 commit 5fd689b

File tree

5 files changed

+214
-135
lines changed

5 files changed

+214
-135
lines changed

doc/users/legend_guide.rst

Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,42 @@ Legend of Complex Plots
192192
In matplotlib v1.1 (FIXME when released) and later, the legend is
193193
improved to support more plot commands and ease the customization.
194194

195+
Artist Container
196+
----------------
197+
198+
The Artist Container is simple class (derived from tuple) that
199+
contains multiple artists. This is introduced primarily to support
200+
legends for complex plot commands that create multiple artists.
201+
202+
Axes instances now have a "containers" attribute (which is a list, and
203+
this is only intended to be used for generating a legend). The items
204+
in this attribute are also returned by
205+
:meth:`~matplotlib.axes.Axes.get_legend_handles_labels`.
206+
207+
For example, "bar" command creates a series of Rectangle
208+
patches. Previously, it returned a list of these patches. With the
209+
current change, it creates a container object of these rectangle
210+
patches (and these patches are added to Axes.patches attribute as
211+
before) and return it instead. As the container class is derived from
212+
a tuple, it should be backward-compatible. Furthermore, the container
213+
object is added to the Axes.containers attributes so that legend
214+
command can properly create a legend for the bar. Thus, you may do ::
215+
216+
b1 = bar([0, 1, 2], [0.2, 0.3, 0.1], width=0.4,
217+
label="Bar 1", align="center")
218+
legend()
219+
220+
or ::
221+
222+
b1 = bar([0, 1, 2], [0.2, 0.3, 0.1], width=0.4, align="center")
223+
legend([b1], ["Bar 1"])
224+
225+
226+
At this time of writing, however, "bar" and "errorbar" are only
227+
supported (hopefully the list will increase). Here is an example.
228+
229+
.. plot:: mpl_examples/pylab_examples/legend_demo4.py
230+
195231
Legend Handler
196232
--------------
197233

@@ -200,7 +236,7 @@ legend handlers. For example, :class:`~matplotlib.lines.Line2D`
200236
instances are handled by
201237
:class:`~matplotlib.legend_handler.HandlerLine2D`. The mapping
202238
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
239+
handler_map of the legend. The handler_map is a dictionary of
204240
key-handler pair, where key can be an artist instance or its
205241
class. And the handler is a Handler instance.
206242

@@ -214,16 +250,17 @@ For each *p_i*, matplotlib
214250
2. if not, iterate over type(p_i).mro() until a matching key is found in the handler_map
215251

216252

217-
For example, the default handler_map
218-
has following key-handler pairs (below is only a part of them).
253+
Unless specified, the defaul handler_map is used. Below is a partial
254+
list of key-handler pairs included in the default handler map.
219255

220256
* Line2D : legend_handler.HandlerLine2D()
221257
* Patch : legend_handler.HandlerPatch()
222258
* LineCollection : legend_handler.HandlerLineCollection()
223259
* ...
224260

261+
225262
The legend command takes an optional argument of "handler_map". When
226-
provided, the default handler will be updated (using dict.update
263+
provided, the default handler map will be updated (using dict.update
227264
method) with the provided one. ::
228265

229266
p1, = plot(x, "ro", label="test1")
@@ -241,62 +278,28 @@ instances (p1 and p2). ::
241278
In the above example, only *p1* will be handled by *my_handler*, while
242279
others will be handled by default handlers.
243280

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
281+
The curent default handler_map has handlers for errobar and bar
282+
plots. Also, it includes an entry for ¡°tuple¡± which is mapped to
282283
*HandlerTuple*. It simply overplots all the handles for items in the
283284
given tuple. For example,
284285

286+
285287
.. plot::
286288
:include-source:
287289

288290
z = np.random.randn(10)
289291

290292
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
293+
p1b, = plt.plot(z[:5], "w+", ms=10, mec="w", mew=2) # white cross
294+
295+
plt.legend([p1a, (p1a, p1b)], ["Attr A", "Attr A+B"])
292296

293-
plt.legend([(p1a, p1b)], ["Attr A+B"])
294297

295298

296299
Implement a Custom Handler
297300
--------------------------
298301

299-
Handler can any callable object with following signature. ::
302+
Handler can be any callable object with following signature. ::
300303

301304
def __call__(self, legend, orig_handle,
302305
fontsize,

lib/matplotlib/axes.py

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4199,31 +4199,32 @@ def xcorr(self, x, y, normed=True, detrend=mlab.detrend_none,
41994199
return lags, c, a, b
42004200

42014201

4202-
def _get_legend_handles(self):
4202+
def _get_legend_handles(self, legend_handler_map=None):
42034203
"return artists that will be used as handles for legend"
42044204
handles_original = self.lines + self.patches + \
42054205
self.collections + self.containers
42064206

42074207
# collections
4208-
legend_map_keys = mlegend.Legend._default_handler_map.keys()
4208+
handler_map = mlegend.Legend.get_default_handler_map()
4209+
4210+
if legend_handler_map is not None:
4211+
handler_map = handler_map.copy()
4212+
handler_map.update(legend_handler_map)
4213+
42094214
#collection_types = [cls for cls in legend_map_keys \
42104215
# if issubclass(cls, mcoll.Collection)]
42114216

42124217
handles = []
42134218
for h in handles_original:
42144219
if h.get_label() == "_nolegend_": #.startswith('_'):
42154220
continue
4216-
4217-
# check subclass
4218-
for cls in legend_map_keys:
4219-
if isinstance(h, cls):
4220-
handles.append(h)
4221-
break
4222-
4221+
if mlegend.Legend.get_legend_handler(handler_map, h):
4222+
handles.append(h)
4223+
42234224
return handles
42244225

42254226

4226-
def get_legend_handles_labels(self):
4227+
def get_legend_handles_labels(self, legend_handler_map=None):
42274228
"""
42284229
return handles and labels for legend
42294230
@@ -4236,9 +4237,10 @@ def get_legend_handles_labels(self):
42364237

42374238
handles = []
42384239
labels = []
4239-
for handle in self._get_legend_handles():
4240+
for handle in self._get_legend_handles(legend_handler_map):
42404241
label = handle.get_label()
4241-
if (label is not None and label != ''):
4242+
#if (label is not None and label != '' and not label.startswith('_')):
4243+
if label and not label.startswith('_'):
42424244
handles.append(handle)
42434245
labels.append(label)
42444246

@@ -4386,6 +4388,9 @@ def legend(self, *args, **kwargs):
43864388
columnspacing the spacing between columns
43874389
================ ==================================================================
43884390
4391+
.. Note:: Not all kinds of artist are supported by the legend command.
4392+
See LINK (FIXME) for details.
4393+
43894394
43904395
**Example:**
43914396

lib/matplotlib/figure.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,8 @@ def legend(self, handles, labels, *args, **kwargs):
973973
columnspacing the spacing between columns
974974
================ ==================================================================
975975
976+
.. Note:: Not all kinds of artist are supported by the legend.
977+
See LINK (FIXME) for details.
976978
977979
**Example:**
978980

lib/matplotlib/legend.py

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,15 @@
11
"""
2-
Place a legend on the axes at location loc. Labels are a
3-
sequence of strings and loc can be a string or an integer
4-
specifying the legend location
5-
6-
The location codes are
7-
8-
'best' : 0, (only implemented for axis legends)
9-
'upper right' : 1,
10-
'upper left' : 2,
11-
'lower left' : 3,
12-
'lower right' : 4,
13-
'right' : 5,
14-
'center left' : 6,
15-
'center right' : 7,
16-
'lower center' : 8,
17-
'upper center' : 9,
18-
'center' : 10,
19-
20-
Return value is a sequence of text, line instances that make
21-
up the legend
2+
The legend module defines the Legend class, which is responsible for
3+
drawing legends associated with axes and/or figures.
4+
5+
The Legend class can be considered as a container of legend handles
6+
and legend texts. Creation of corresponding legend handles from the
7+
plot elements in the axes or figures (e.g., lines, patches, etc.) are
8+
specified by the handler map, which defines the mapping between the
9+
plot elements and the legend handlers to be used (the default legend
10+
handlers are defined in the matplotlib.legend_handler module). Note
11+
that not all kinds of artist are supported by the legend yet (See LINK
12+
(FIXME) for details).
2213
"""
2314
from __future__ import division
2415
import warnings
@@ -115,8 +106,6 @@ class Legend(Artist):
115106
loc can be a tuple of the noramilzed coordinate values with
116107
respect its parent.
117108
118-
Return value is a sequence of text, line instances that make
119-
up the legend
120109
"""
121110

122111

@@ -174,7 +163,7 @@ def __init__(self, parent, handles, labels,
174163
):
175164
"""
176165
- *parent* : the artist that contains the legend
177-
- *handles* : a list of artists (lines, patches) to add to the legend
166+
- *handles* : a list of artists (lines, patches) to be added to the legend
178167
- *labels* : a list of strings to label the legend
179168
180169
Optional keyword arguments:
@@ -204,7 +193,7 @@ def __init__(self, parent, handles, labels,
204193
================ ==================================================================
205194
206195
207-
The pad and spacing parameters are measure in font-size units. E.g.,
196+
The pad and spacing parameters are measured in font-size units. E.g.,
208197
a fontsize of 10 points and a handlelength=5 implies a handlelength of
209198
50 points. Values from rcParams will be used if None.
210199
@@ -477,6 +466,9 @@ def _approx_text_height(self, renderer=None):
477466
return renderer.points_to_pixels(self._fontsize)
478467

479468

469+
# _default_handler_map defines the default mapping between plot
470+
# elements and the legend handlers.
471+
480472
_default_handler_map = {
481473
ErrorbarContainer:legend_handler.HandlerErrorbar(),
482474
Line2D:legend_handler.HandlerLine2D(),
@@ -488,15 +480,59 @@ def _approx_text_height(self, renderer=None):
488480
tuple:legend_handler.HandlerTuple(),
489481
}
490482

483+
# (get|set|update)_default_handler_maps are public interfaces to
484+
# modify the defalut handler map.
485+
486+
@classmethod
487+
def get_default_handler_map(cls):
488+
"""
489+
A class method that returns the default handler map.
490+
"""
491+
return cls._default_handler_map
492+
493+
@classmethod
494+
def set_default_handler_map(cls, handler_map):
495+
"""
496+
A class method to set the default handler map.
497+
"""
498+
cls._default_handler_map = handler_map
499+
500+
@classmethod
501+
def update_default_handler_map(cls, handler_map):
502+
"""
503+
A class method to update the default handler map.
504+
"""
505+
cls._default_handler_map.update(handler_map)
506+
491507
def get_legend_handler_map(self):
508+
"""
509+
return the handler map.
510+
"""
511+
512+
default_handler_map = self.get_default_handler_map()
513+
492514
if self._handler_map:
493-
hm = self._default_handler_map.copy()
515+
hm = default_handler_map.copy()
494516
hm.update(self._handler_map)
495517
return hm
496518
else:
497-
return self._default_handler_map
519+
return default_handler_map
498520

499-
def get_legend_handler(self, legend_handler_map, orig_handle):
521+
@staticmethod
522+
def get_legend_handler(legend_handler_map, orig_handle):
523+
"""
524+
return a legend handler from *legend_handler_map* that
525+
corresponds to *orig_handler*.
526+
527+
*legend_handler_map* should be a dictionary object (that is
528+
returned by the get_legend_handler_map method).
529+
530+
It first checks if the *orig_handle* itself is a key in the
531+
*legend_hanler_map* and return the associated value.
532+
Otherwised, it checks for each of the classes in its
533+
method-resolution-order. If no matching key is found, it
534+
returns None.
535+
"""
500536
legend_handler_keys = legend_handler_map.keys()
501537
if orig_handle in legend_handler_keys:
502538
handler = legend_handler_map[orig_handle]

0 commit comments

Comments
 (0)
0