1"""
2Default legend handlers.
3
4.. important::
5
6 This is a low-level legend API, which most end users do not need.
7
8 We recommend that you are familiar with the :ref:`legend guide
9 <legend_guide>` before reading this documentation.
10
11Legend handlers are expected to be a callable object with a following
12signature::
13
14 legend_handler(legend, orig_handle, fontsize, handlebox)
15
16Where *legend* is the legend itself, *orig_handle* is the original
17plot, *fontsize* is the fontsize in pixels, and *handlebox* is an
18`.OffsetBox` instance. Within the call, you should create relevant
19artists (using relevant properties from the *legend* and/or
20*orig_handle*) and add them into the *handlebox*. The artists need to
21be scaled according to the *fontsize* (note that the size is in pixels,
22i.e., this is dpi-scaled value).
23
24This module includes definition of several legend handler classes
25derived from the base class (HandlerBase) with the following method::
26
27 def legend_artist(self, legend, orig_handle, fontsize, handlebox)
28"""
29
30from itertools import cycle
31
32import numpy as np
33
34from matplotlib import cbook
35from matplotlib.lines import Line2D
36from matplotlib.patches import Rectangle
37import matplotlib.collections as mcoll
38
39
40def update_from_first_child(tgt, src):
41 first_child = next(iter(src.get_children()), None)
42 if first_child is not None:
43 tgt.update_from(first_child)
44
45
46class HandlerBase:
47 """
48 A base class for default legend handlers.
49
50 The derived classes are meant to override *create_artists* method, which
51 has the following signature::
52
53 def create_artists(self, legend, orig_handle,
54 xdescent, ydescent, width, height, fontsize,
55 trans):
56
57 The overridden method needs to create artists of the given
58 transform that fits in the given dimension (xdescent, ydescent,
59 width, height) that are scaled by fontsize if necessary.
60
61 """
62 def __init__(self, xpad=0., ypad=0., update_func=None):
63 """
64 Parameters
65 ----------
66 xpad : float, optional
67 Padding in x-direction.
68 ypad : float, optional
69 Padding in y-direction.
70 update_func : callable, optional
71 Function for updating the legend handler properties from another
72 legend handler, used by `~HandlerBase.update_prop`.
73 """
74 self._xpad, self._ypad = xpad, ypad
75 self._update_prop_func = update_func
76
77 def _update_prop(self, legend_handle, orig_handle):
78 if self._update_prop_func is None:
79 self._default_update_prop(legend_handle, orig_handle)
80 else:
81 self._update_prop_func(legend_handle, orig_handle)
82
83 def _default_update_prop(self, legend_handle, orig_handle):
84 legend_handle.update_from(orig_handle)
85
86 def update_prop(self, legend_handle, orig_handle, legend):
87
88 self._update_prop(legend_handle, orig_handle)
89
90 legend._set_artist_props(legend_handle)
91 legend_handle.set_clip_box(None)
92 legend_handle.set_clip_path(None)
93
94 def adjust_drawing_area(self, legend, orig_handle,
95 xdescent, ydescent, width, height, fontsize,
96 ):
97 xdescent = xdescent - self._xpad * fontsize
98 ydescent = ydescent - self._ypad * fontsize
99 width = width - self._xpad * fontsize
100 height = height - self._ypad * fontsize
101 return xdescent, ydescent, width, height
102
103 def legend_artist(self, legend, orig_handle,
104 fontsize, handlebox):
105 """
106 Return the artist that this HandlerBase generates for the given
107 original artist/handle.
108
109 Parameters
110 ----------
111 legend : `~matplotlib.legend.Legend`
112 The legend for which these legend artists are being created.
113 orig_handle : :class:`matplotlib.artist.Artist` or similar
114 The object for which these legend artists are being created.
115 fontsize : int
116 The fontsize in pixels. The artists being created should
117 be scaled according to the given fontsize.
118 handlebox : `~matplotlib.offsetbox.OffsetBox`
119 The box which has been created to hold this legend entry's
120 artists. Artists created in the `legend_artist` method must
121 be added to this handlebox inside this method.
122
123 """
124 xdescent, ydescent, width, height = self.adjust_drawing_area(
125 legend, orig_handle,
126 handlebox.xdescent, handlebox.ydescent,
127 handlebox.width, handlebox.height,
128 fontsize)
129 artists = self.create_artists(legend, orig_handle,
130 xdescent, ydescent, width, height,
131 fontsize, handlebox.get_transform())
132
133 # create_artists will return a list of artists.
134 for a in artists:
135 handlebox.add_artist(a)
136
137 # we only return the first artist
138 return artists[0]
139
140 def create_artists(self, legend, orig_handle,
141 xdescent, ydescent, width, height, fontsize,
142 trans):
143 """
144 Return the legend artists generated.
145
146 Parameters
147 ----------
148 legend : `~matplotlib.legend.Legend`
149 The legend for which these legend artists are being created.
150 orig_handle : `~matplotlib.artist.Artist` or similar
151 The object for which these legend artists are being created.
152 xdescent, ydescent, width, height : int
153 The rectangle (*xdescent*, *ydescent*, *width*, *height*) that the
154 legend artists being created should fit within.
155 fontsize : int
156 The fontsize in pixels. The legend artists being created should
157 be scaled according to the given fontsize.
158 trans : `~matplotlib.transforms.Transform`
159 The transform that is applied to the legend artists being created.
160 Typically from unit coordinates in the handler box to screen
161 coordinates.
162 """
163 raise NotImplementedError('Derived must override')
164
165
166class HandlerNpoints(HandlerBase):
167 """
168 A legend handler that shows *numpoints* points in the legend entry.
169 """
170
171 def __init__(self, marker_pad=0.3, numpoints=None, **kwargs):
172 """
173 Parameters
174 ----------
175 marker_pad : float
176 Padding between points in legend entry.
177 numpoints : int
178 Number of points to show in legend entry.
179 **kwargs
180 Keyword arguments forwarded to `.HandlerBase`.
181 """
182 super().__init__(**kwargs)
183
184 self._numpoints = numpoints
185 self._marker_pad = marker_pad
186
187 def get_numpoints(self, legend):
188 if self._numpoints is None:
189 return legend.numpoints
190 else:
191 return self._numpoints
192
193 def get_xdata(self, legend, xdescent, ydescent, width, height, fontsize):
194 numpoints = self.get_numpoints(legend)
195 if numpoints > 1:
196 # we put some pad here to compensate the size of the marker
197 pad = self._marker_pad * fontsize
198 xdata = np.linspace(-xdescent + pad,
199 -xdescent + width - pad,
200 numpoints)
201 xdata_marker = xdata
202 else:
203 xdata = [-xdescent, -xdescent + width]
204 xdata_marker = [-xdescent + 0.5 * width]
205 return xdata, xdata_marker
206
207
208class HandlerNpointsYoffsets(HandlerNpoints):
209 """
210 A legend handler that shows *numpoints* in the legend, and allows them to
211 be individually offset in the y-direction.
212 """
213
214 def __init__(self, numpoints=None, yoffsets=None, **kwargs):
215 """
216 Parameters
217 ----------
218 numpoints : int
219 Number of points to show in legend entry.
220 yoffsets : array of floats
221 Length *numpoints* list of y offsets for each point in
222 legend entry.
223 **kwargs
224 Keyword arguments forwarded to `.HandlerNpoints`.
225 """
226 super().__init__(numpoints=numpoints, **kwargs)
227 self._yoffsets = yoffsets
228
229 def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize):
230 if self._yoffsets is None:
231 ydata = height * legend._scatteryoffsets
232 else:
233 ydata = height * np.asarray(self._yoffsets)
234
235 return ydata
236
237
238class HandlerLine2DCompound(HandlerNpoints):
239 """
240 Original handler for `.Line2D` instances, that relies on combining
241 a line-only with a marker-only artist. May be deprecated in the future.
242 """
243
244 def create_artists(self, legend, orig_handle,
245 xdescent, ydescent, width, height, fontsize,
246 trans):
247 # docstring inherited
248 xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
249 width, height, fontsize)
250
251 ydata = np.full_like(xdata, ((height - ydescent) / 2))
252 legline = Line2D(xdata, ydata)
253
254 self.update_prop(legline, orig_handle, legend)
255 legline.set_drawstyle('default')
256 legline.set_marker("")
257
258 legline_marker = Line2D(xdata_marker, ydata[:len(xdata_marker)])
259 self.update_prop(legline_marker, orig_handle, legend)
260 legline_marker.set_linestyle('None')
261 if legend.markerscale != 1:
262 newsz = legline_marker.get_markersize() * legend.markerscale
263 legline_marker.set_markersize(newsz)
264 # we don't want to add this to the return list because
265 # the texts and handles are assumed to be in one-to-one
266 # correspondence.
267 legline._legmarker = legline_marker
268
269 legline.set_transform(trans)
270 legline_marker.set_transform(trans)
271
272 return [legline, legline_marker]
273
274
275class HandlerLine2D(HandlerNpoints):
276 """
277 Handler for `.Line2D` instances.
278
279 See Also
280 --------
281 HandlerLine2DCompound : An earlier handler implementation, which used one
282 artist for the line and another for the marker(s).
283 """
284
285 def create_artists(self, legend, orig_handle,
286 xdescent, ydescent, width, height, fontsize,
287 trans):
288 # docstring inherited
289 xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
290 width, height, fontsize)
291
292 markevery = None
293 if self.get_numpoints(legend) == 1:
294 # Special case: one wants a single marker in the center
295 # and a line that extends on both sides. One will use a
296 # 3 points line, but only mark the #1 (i.e. middle) point.
297 xdata = np.linspace(xdata[0], xdata[-1], 3)
298 markevery = [1]
299
300 ydata = np.full_like(xdata, (height - ydescent) / 2)
301 legline = Line2D(xdata, ydata, markevery=markevery)
302
303 self.update_prop(legline, orig_handle, legend)
304
305 if legend.markerscale != 1:
306 newsz = legline.get_markersize() * legend.markerscale
307 legline.set_markersize(newsz)
308
309 legline.set_transform(trans)
310
311 return [legline]
312
313
314class HandlerPatch(HandlerBase):
315 """
316 Handler for `.Patch` instances.
317 """
318
319 def __init__(self, patch_func=None, **kwargs):
320 """
321 Parameters
322 ----------
323 patch_func : callable, optional
324 The function that creates the legend key artist.
325 *patch_func* should have the signature::
326
327 def patch_func(legend=legend, orig_handle=orig_handle,
328 xdescent=xdescent, ydescent=ydescent,
329 width=width, height=height, fontsize=fontsize)
330
331 Subsequently, the created artist will have its ``update_prop``
332 method called and the appropriate transform will be applied.
333
334 **kwargs
335 Keyword arguments forwarded to `.HandlerBase`.
336 """
337 super().__init__(**kwargs)
338 self._patch_func = patch_func
339
340 def _create_patch(self, legend, orig_handle,
341 xdescent, ydescent, width, height, fontsize):
342 if self._patch_func is None:
343 p = Rectangle(xy=(-xdescent, -ydescent),
344 width=width, height=height)
345 else:
346 p = self._patch_func(legend=legend, orig_handle=orig_handle,
347 xdescent=xdescent, ydescent=ydescent,
348 width=width, height=height, fontsize=fontsize)
349 return p
350
351 def create_artists(self, legend, orig_handle,
352 xdescent, ydescent, width, height, fontsize, trans):
353 # docstring inherited
354 p = self._create_patch(legend, orig_handle,
355 xdescent, ydescent, width, height, fontsize)
356 self.update_prop(p, orig_handle, legend)
357 p.set_transform(trans)
358 return [p]
359
360
361class HandlerStepPatch(HandlerBase):
362 """
363 Handler for `~.matplotlib.patches.StepPatch` instances.
364 """
365
366 @staticmethod
367 def _create_patch(orig_handle, xdescent, ydescent, width, height):
368 return Rectangle(xy=(-xdescent, -ydescent), width=width,
369 height=height, color=orig_handle.get_facecolor())
370
371 @staticmethod
372 def _create_line(orig_handle, width, height):
373 # Unfilled StepPatch should show as a line
374 legline = Line2D([0, width], [height/2, height/2],
375 color=orig_handle.get_edgecolor(),
376 linestyle=orig_handle.get_linestyle(),
377 linewidth=orig_handle.get_linewidth(),
378 )
379
380 # Overwrite manually because patch and line properties don't mix
381 legline.set_drawstyle('default')
382 legline.set_marker("")
383 return legline
384
385 def create_artists(self, legend, orig_handle,
386 xdescent, ydescent, width, height, fontsize, trans):
387 # docstring inherited
388 if orig_handle.get_fill() or (orig_handle.get_hatch() is not None):
389 p = self._create_patch(orig_handle, xdescent, ydescent, width,
390 height)
391 self.update_prop(p, orig_handle, legend)
392 else:
393 p = self._create_line(orig_handle, width, height)
394 p.set_transform(trans)
395 return [p]
396
397
398class HandlerLineCollection(HandlerLine2D):
399 """
400 Handler for `.LineCollection` instances.
401 """
402 def get_numpoints(self, legend):
403 if self._numpoints is None:
404 return legend.scatterpoints
405 else:
406 return self._numpoints
407
408 def _default_update_prop(self, legend_handle, orig_handle):
409 lw = orig_handle.get_linewidths()[0]
410 dashes = orig_handle._us_linestyles[0]
411 color = orig_handle.get_colors()[0]
412 legend_handle.set_color(color)
413 legend_handle.set_linestyle(dashes)
414 legend_handle.set_linewidth(lw)
415
416 def create_artists(self, legend, orig_handle,
417 xdescent, ydescent, width, height, fontsize, trans):
418 # docstring inherited
419 xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
420 width, height, fontsize)
421 ydata = np.full_like(xdata, (height - ydescent) / 2)
422 legline = Line2D(xdata, ydata)
423
424 self.update_prop(legline, orig_handle, legend)
425 legline.set_transform(trans)
426
427 return [legline]
428
429
430class HandlerRegularPolyCollection(HandlerNpointsYoffsets):
431 r"""Handler for `.RegularPolyCollection`\s."""
432
433 def __init__(self, yoffsets=None, sizes=None, **kwargs):
434 super().__init__(yoffsets=yoffsets, **kwargs)
435
436 self._sizes = sizes
437
438 def get_numpoints(self, legend):
439 if self._numpoints is None:
440 return legend.scatterpoints
441 else:
442 return self._numpoints
443
444 def get_sizes(self, legend, orig_handle,
445 xdescent, ydescent, width, height, fontsize):
446 if self._sizes is None:
447 handle_sizes = orig_handle.get_sizes()
448 if not len(handle_sizes):
449 handle_sizes = [1]
450 size_max = max(handle_sizes) * legend.markerscale ** 2
451 size_min = min(handle_sizes) * legend.markerscale ** 2
452
453 numpoints = self.get_numpoints(legend)
454 if numpoints < 4:
455 sizes = [.5 * (size_max + size_min), size_max,
456 size_min][:numpoints]
457 else:
458 rng = (size_max - size_min)
459 sizes = rng * np.linspace(0, 1, numpoints) + size_min
460 else:
461 sizes = self._sizes
462
463 return sizes
464
465 def update_prop(self, legend_handle, orig_handle, legend):
466
467 self._update_prop(legend_handle, orig_handle)
468
469 legend_handle.set_figure(legend.figure)
470 # legend._set_artist_props(legend_handle)
471 legend_handle.set_clip_box(None)
472 legend_handle.set_clip_path(None)
473
474 def create_collection(self, orig_handle, sizes, offsets, offset_transform):
475 return type(orig_handle)(
476 orig_handle.get_numsides(),
477 rotation=orig_handle.get_rotation(), sizes=sizes,
478 offsets=offsets, offset_transform=offset_transform,
479 )
480
481 def create_artists(self, legend, orig_handle,
482 xdescent, ydescent, width, height, fontsize,
483 trans):
484 # docstring inherited
485 xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
486 width, height, fontsize)
487
488 ydata = self.get_ydata(legend, xdescent, ydescent,
489 width, height, fontsize)
490
491 sizes = self.get_sizes(legend, orig_handle, xdescent, ydescent,
492 width, height, fontsize)
493
494 p = self.create_collection(
495 orig_handle, sizes,
496 offsets=list(zip(xdata_marker, ydata)), offset_transform=trans)
497
498 self.update_prop(p, orig_handle, legend)
499 p.set_offset_transform(trans)
500 return [p]
501
502
503class HandlerPathCollection(HandlerRegularPolyCollection):
504 r"""Handler for `.PathCollection`\s, which are used by `~.Axes.scatter`."""
505
506 def create_collection(self, orig_handle, sizes, offsets, offset_transform):
507 return type(orig_handle)(
508 [orig_handle.get_paths()[0]], sizes=sizes,
509 offsets=offsets, offset_transform=offset_transform,
510 )
511
512
513class HandlerCircleCollection(HandlerRegularPolyCollection):
514 r"""Handler for `.CircleCollection`\s."""
515
516 def create_collection(self, orig_handle, sizes, offsets, offset_transform):
517 return type(orig_handle)(
518 sizes, offsets=offsets, offset_transform=offset_transform)
519
520
521class HandlerErrorbar(HandlerLine2D):
522 """Handler for Errorbars."""
523
524 def __init__(self, xerr_size=0.5, yerr_size=None,
525 marker_pad=0.3, numpoints=None, **kwargs):
526
527 self._xerr_size = xerr_size
528 self._yerr_size = yerr_size
529
530 super().__init__(marker_pad=marker_pad, numpoints=numpoints, **kwargs)
531
532 def get_err_size(self, legend, xdescent, ydescent,
533 width, height, fontsize):
534 xerr_size = self._xerr_size * fontsize
535
536 if self._yerr_size is None:
537 yerr_size = xerr_size
538 else:
539 yerr_size = self._yerr_size * fontsize
540
541 return xerr_size, yerr_size
542
543 def create_artists(self, legend, orig_handle,
544 xdescent, ydescent, width, height, fontsize,
545 trans):
546 # docstring inherited
547 plotlines, caplines, barlinecols = orig_handle
548
549 xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
550 width, height, fontsize)
551
552 ydata = np.full_like(xdata, (height - ydescent) / 2)
553 legline = Line2D(xdata, ydata)
554
555 xdata_marker = np.asarray(xdata_marker)
556 ydata_marker = np.asarray(ydata[:len(xdata_marker)])
557
558 xerr_size, yerr_size = self.get_err_size(legend, xdescent, ydescent,
559 width, height, fontsize)
560
561 legline_marker = Line2D(xdata_marker, ydata_marker)
562
563 # when plotlines are None (only errorbars are drawn), we just
564 # make legline invisible.
565 if plotlines is None:
566 legline.set_visible(False)
567 legline_marker.set_visible(False)
568 else:
569 self.update_prop(legline, plotlines, legend)
570
571 legline.set_drawstyle('default')
572 legline.set_marker('none')
573
574 self.update_prop(legline_marker, plotlines, legend)
575 legline_marker.set_linestyle('None')
576
577 if legend.markerscale != 1:
578 newsz = legline_marker.get_markersize() * legend.markerscale
579 legline_marker.set_markersize(newsz)
580
581 handle_barlinecols = []
582 handle_caplines = []
583
584 if orig_handle.has_xerr:
585 verts = [((x - xerr_size, y), (x + xerr_size, y))
586 for x, y in zip(xdata_marker, ydata_marker)]
587 coll = mcoll.LineCollection(verts)
588 self.update_prop(coll, barlinecols[0], legend)
589 handle_barlinecols.append(coll)
590
591 if caplines:
592 capline_left = Line2D(xdata_marker - xerr_size, ydata_marker)
593 capline_right = Line2D(xdata_marker + xerr_size, ydata_marker)
594 self.update_prop(capline_left, caplines[0], legend)
595 self.update_prop(capline_right, caplines[0], legend)
596 capline_left.set_marker("|")
597 capline_right.set_marker("|")
598
599 handle_caplines.append(capline_left)
600 handle_caplines.append(capline_right)
601
602 if orig_handle.has_yerr:
603 verts = [((x, y - yerr_size), (x, y + yerr_size))
604 for x, y in zip(xdata_marker, ydata_marker)]
605 coll = mcoll.LineCollection(verts)
606 self.update_prop(coll, barlinecols[0], legend)
607 handle_barlinecols.append(coll)
608
609 if caplines:
610 capline_left = Line2D(xdata_marker, ydata_marker - yerr_size)
611 capline_right = Line2D(xdata_marker, ydata_marker + yerr_size)
612 self.update_prop(capline_left, caplines[0], legend)
613 self.update_prop(capline_right, caplines[0], legend)
614 capline_left.set_marker("_")
615 capline_right.set_marker("_")
616
617 handle_caplines.append(capline_left)
618 handle_caplines.append(capline_right)
619
620 artists = [
621 *handle_barlinecols, *handle_caplines, legline, legline_marker,
622 ]
623 for artist in artists:
624 artist.set_transform(trans)
625 return artists
626
627
628class HandlerStem(HandlerNpointsYoffsets):
629 """
630 Handler for plots produced by `~.Axes.stem`.
631 """
632
633 def __init__(self, marker_pad=0.3, numpoints=None,
634 bottom=None, yoffsets=None, **kwargs):
635 """
636 Parameters
637 ----------
638 marker_pad : float, default: 0.3
639 Padding between points in legend entry.
640 numpoints : int, optional
641 Number of points to show in legend entry.
642 bottom : float, optional
643
644 yoffsets : array of floats, optional
645 Length *numpoints* list of y offsets for each point in
646 legend entry.
647 **kwargs
648 Keyword arguments forwarded to `.HandlerNpointsYoffsets`.
649 """
650 super().__init__(marker_pad=marker_pad, numpoints=numpoints,
651 yoffsets=yoffsets, **kwargs)
652 self._bottom = bottom
653
654 def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize):
655 if self._yoffsets is None:
656 ydata = height * (0.5 * legend._scatteryoffsets + 0.5)
657 else:
658 ydata = height * np.asarray(self._yoffsets)
659
660 return ydata
661
662 def create_artists(self, legend, orig_handle,
663 xdescent, ydescent, width, height, fontsize,
664 trans):
665 # docstring inherited
666 markerline, stemlines, baseline = orig_handle
667 # Check to see if the stemcontainer is storing lines as a list or a
668 # LineCollection. Eventually using a list will be removed, and this
669 # logic can also be removed.
670 using_linecoll = isinstance(stemlines, mcoll.LineCollection)
671
672 xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
673 width, height, fontsize)
674
675 ydata = self.get_ydata(legend, xdescent, ydescent,
676 width, height, fontsize)
677
678 if self._bottom is None:
679 bottom = 0.
680 else:
681 bottom = self._bottom
682
683 leg_markerline = Line2D(xdata_marker, ydata[:len(xdata_marker)])
684 self.update_prop(leg_markerline, markerline, legend)
685
686 leg_stemlines = [Line2D([x, x], [bottom, y])
687 for x, y in zip(xdata_marker, ydata)]
688
689 if using_linecoll:
690 # change the function used by update_prop() from the default
691 # to one that handles LineCollection
692 with cbook._setattr_cm(
693 self, _update_prop_func=self._copy_collection_props):
694 for line in leg_stemlines:
695 self.update_prop(line, stemlines, legend)
696
697 else:
698 for lm, m in zip(leg_stemlines, stemlines):
699 self.update_prop(lm, m, legend)
700
701 leg_baseline = Line2D([np.min(xdata), np.max(xdata)],
702 [bottom, bottom])
703 self.update_prop(leg_baseline, baseline, legend)
704
705 artists = [*leg_stemlines, leg_baseline, leg_markerline]
706 for artist in artists:
707 artist.set_transform(trans)
708 return artists
709
710 def _copy_collection_props(self, legend_handle, orig_handle):
711 """
712 Copy properties from the `.LineCollection` *orig_handle* to the
713 `.Line2D` *legend_handle*.
714 """
715 legend_handle.set_color(orig_handle.get_color()[0])
716 legend_handle.set_linestyle(orig_handle.get_linestyle()[0])
717
718
719class HandlerTuple(HandlerBase):
720 """
721 Handler for Tuple.
722 """
723
724 def __init__(self, ndivide=1, pad=None, **kwargs):
725 """
726 Parameters
727 ----------
728 ndivide : int or None, default: 1
729 The number of sections to divide the legend area into. If None,
730 use the length of the input tuple.
731 pad : float, default: :rc:`legend.borderpad`
732 Padding in units of fraction of font size.
733 **kwargs
734 Keyword arguments forwarded to `.HandlerBase`.
735 """
736 self._ndivide = ndivide
737 self._pad = pad
738 super().__init__(**kwargs)
739
740 def create_artists(self, legend, orig_handle,
741 xdescent, ydescent, width, height, fontsize,
742 trans):
743 # docstring inherited
744 handler_map = legend.get_legend_handler_map()
745
746 if self._ndivide is None:
747 ndivide = len(orig_handle)
748 else:
749 ndivide = self._ndivide
750
751 if self._pad is None:
752 pad = legend.borderpad * fontsize
753 else:
754 pad = self._pad * fontsize
755
756 if ndivide > 1:
757 width = (width - pad * (ndivide - 1)) / ndivide
758
759 xds_cycle = cycle(xdescent - (width + pad) * np.arange(ndivide))
760
761 a_list = []
762 for handle1 in orig_handle:
763 handler = legend.get_legend_handler(handler_map, handle1)
764 _a_list = handler.create_artists(
765 legend, handle1,
766 next(xds_cycle), ydescent, width, height, fontsize, trans)
767 a_list.extend(_a_list)
768
769 return a_list
770
771
772class HandlerPolyCollection(HandlerBase):
773 """
774 Handler for `.PolyCollection` used in `~.Axes.fill_between` and
775 `~.Axes.stackplot`.
776 """
777 def _update_prop(self, legend_handle, orig_handle):
778 def first_color(colors):
779 if colors.size == 0:
780 return (0, 0, 0, 0)
781 return tuple(colors[0])
782
783 def get_first(prop_array):
784 if len(prop_array):
785 return prop_array[0]
786 else:
787 return None
788
789 # orig_handle is a PolyCollection and legend_handle is a Patch.
790 # Directly set Patch color attributes (must be RGBA tuples).
791 legend_handle._facecolor = first_color(orig_handle.get_facecolor())
792 legend_handle._edgecolor = first_color(orig_handle.get_edgecolor())
793 legend_handle._original_facecolor = orig_handle._original_facecolor
794 legend_handle._original_edgecolor = orig_handle._original_edgecolor
795 legend_handle._fill = orig_handle.get_fill()
796 legend_handle._hatch = orig_handle.get_hatch()
797 # Hatch color is anomalous in having no getters and setters.
798 legend_handle._hatch_color = orig_handle._hatch_color
799 # Setters are fine for the remaining attributes.
800 legend_handle.set_linewidth(get_first(orig_handle.get_linewidths()))
801 legend_handle.set_linestyle(get_first(orig_handle.get_linestyles()))
802 legend_handle.set_transform(get_first(orig_handle.get_transforms()))
803 legend_handle.set_figure(orig_handle.get_figure())
804 # Alpha is already taken into account by the color attributes.
805
806 def create_artists(self, legend, orig_handle,
807 xdescent, ydescent, width, height, fontsize, trans):
808 # docstring inherited
809 p = Rectangle(xy=(-xdescent, -ydescent),
810 width=width, height=height)
811 self.update_prop(p, orig_handle, legend)
812 p.set_transform(trans)
813 return [p]