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