legend_handler.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742
  1. """
  2. This module defines default legend handlers.
  3. It is strongly encouraged to have read the :doc:`legend guide
  4. </tutorials/intermediate/legend_guide>` before this documentation.
  5. Legend handlers are expected to be a callable object with a following
  6. signature. ::
  7. legend_handler(legend, orig_handle, fontsize, handlebox)
  8. Where *legend* is the legend itself, *orig_handle* is the original
  9. plot, *fontsize* is the fontsize in pixels, and *handlebox* is a
  10. OffsetBox instance. Within the call, you should create relevant
  11. artists (using relevant properties from the *legend* and/or
  12. *orig_handle*) and add them into the handlebox. The artists needs to
  13. be scaled according to the fontsize (note that the size is in pixel,
  14. i.e., this is dpi-scaled value).
  15. This module includes definition of several legend handler classes
  16. derived from the base class (HandlerBase) with the following method::
  17. def legend_artist(self, legend, orig_handle, fontsize, handlebox)
  18. """
  19. from itertools import cycle
  20. import numpy as np
  21. from matplotlib.lines import Line2D
  22. from matplotlib.patches import Rectangle
  23. import matplotlib.collections as mcoll
  24. import matplotlib.colors as mcolors
  25. def update_from_first_child(tgt, src):
  26. first_child = next(iter(src.get_children()), None)
  27. if first_child is not None:
  28. tgt.update_from(first_child)
  29. class HandlerBase:
  30. """
  31. A Base class for default legend handlers.
  32. The derived classes are meant to override *create_artists* method, which
  33. has a following signature.::
  34. def create_artists(self, legend, orig_handle,
  35. xdescent, ydescent, width, height, fontsize,
  36. trans):
  37. The overridden method needs to create artists of the given
  38. transform that fits in the given dimension (xdescent, ydescent,
  39. width, height) that are scaled by fontsize if necessary.
  40. """
  41. def __init__(self, xpad=0., ypad=0., update_func=None):
  42. self._xpad, self._ypad = xpad, ypad
  43. self._update_prop_func = update_func
  44. def _update_prop(self, legend_handle, orig_handle):
  45. if self._update_prop_func is None:
  46. self._default_update_prop(legend_handle, orig_handle)
  47. else:
  48. self._update_prop_func(legend_handle, orig_handle)
  49. def _default_update_prop(self, legend_handle, orig_handle):
  50. legend_handle.update_from(orig_handle)
  51. def update_prop(self, legend_handle, orig_handle, legend):
  52. self._update_prop(legend_handle, orig_handle)
  53. legend._set_artist_props(legend_handle)
  54. legend_handle.set_clip_box(None)
  55. legend_handle.set_clip_path(None)
  56. def adjust_drawing_area(self, legend, orig_handle,
  57. xdescent, ydescent, width, height, fontsize,
  58. ):
  59. xdescent = xdescent - self._xpad * fontsize
  60. ydescent = ydescent - self._ypad * fontsize
  61. width = width - self._xpad * fontsize
  62. height = height - self._ypad * fontsize
  63. return xdescent, ydescent, width, height
  64. def legend_artist(self, legend, orig_handle,
  65. fontsize, handlebox):
  66. """
  67. Return the artist that this HandlerBase generates for the given
  68. original artist/handle.
  69. Parameters
  70. ----------
  71. legend : :class:`matplotlib.legend.Legend` instance
  72. The legend for which these legend artists are being created.
  73. orig_handle : :class:`matplotlib.artist.Artist` or similar
  74. The object for which these legend artists are being created.
  75. fontsize : float or int
  76. The fontsize in pixels. The artists being created should
  77. be scaled according to the given fontsize.
  78. handlebox : :class:`matplotlib.offsetbox.OffsetBox` instance
  79. The box which has been created to hold this legend entry's
  80. artists. Artists created in the `legend_artist` method must
  81. be added to this handlebox inside this method.
  82. """
  83. xdescent, ydescent, width, height = self.adjust_drawing_area(
  84. legend, orig_handle,
  85. handlebox.xdescent, handlebox.ydescent,
  86. handlebox.width, handlebox.height,
  87. fontsize)
  88. artists = self.create_artists(legend, orig_handle,
  89. xdescent, ydescent, width, height,
  90. fontsize, handlebox.get_transform())
  91. # create_artists will return a list of artists.
  92. for a in artists:
  93. handlebox.add_artist(a)
  94. # we only return the first artist
  95. return artists[0]
  96. def create_artists(self, legend, orig_handle,
  97. xdescent, ydescent, width, height, fontsize,
  98. trans):
  99. raise NotImplementedError('Derived must override')
  100. class HandlerNpoints(HandlerBase):
  101. """
  102. A legend handler that shows *numpoints* points in the legend entry.
  103. """
  104. def __init__(self, marker_pad=0.3, numpoints=None, **kw):
  105. """
  106. Parameters
  107. ----------
  108. marker_pad : float
  109. Padding between points in legend entry.
  110. numpoints : int
  111. Number of points to show in legend entry.
  112. Notes
  113. -----
  114. Any other keyword arguments are given to `HandlerBase`.
  115. """
  116. HandlerBase.__init__(self, **kw)
  117. self._numpoints = numpoints
  118. self._marker_pad = marker_pad
  119. def get_numpoints(self, legend):
  120. if self._numpoints is None:
  121. return legend.numpoints
  122. else:
  123. return self._numpoints
  124. def get_xdata(self, legend, xdescent, ydescent, width, height, fontsize):
  125. numpoints = self.get_numpoints(legend)
  126. if numpoints > 1:
  127. # we put some pad here to compensate the size of the marker
  128. pad = self._marker_pad * fontsize
  129. xdata = np.linspace(-xdescent + pad,
  130. -xdescent + width - pad,
  131. numpoints)
  132. xdata_marker = xdata
  133. else:
  134. xdata = [-xdescent, -xdescent + width]
  135. xdata_marker = [-xdescent + 0.5 * width]
  136. return xdata, xdata_marker
  137. class HandlerNpointsYoffsets(HandlerNpoints):
  138. """
  139. A legend handler that shows *numpoints* in the legend, and allows them to
  140. be individually offest in the y-direction.
  141. """
  142. def __init__(self, numpoints=None, yoffsets=None, **kw):
  143. """
  144. Parameters
  145. ----------
  146. numpoints : int
  147. Number of points to show in legend entry.
  148. yoffsets : array of floats
  149. Length *numpoints* list of y offsets for each point in
  150. legend entry.
  151. Notes
  152. -----
  153. Any other keyword arguments are given to `HandlerNpoints`.
  154. """
  155. HandlerNpoints.__init__(self, numpoints=numpoints, **kw)
  156. self._yoffsets = yoffsets
  157. def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize):
  158. if self._yoffsets is None:
  159. ydata = height * legend._scatteryoffsets
  160. else:
  161. ydata = height * np.asarray(self._yoffsets)
  162. return ydata
  163. class HandlerLine2D(HandlerNpoints):
  164. """
  165. Handler for `.Line2D` instances.
  166. """
  167. def __init__(self, marker_pad=0.3, numpoints=None, **kw):
  168. """
  169. Parameters
  170. ----------
  171. marker_pad : float
  172. Padding between points in legend entry.
  173. numpoints : int
  174. Number of points to show in legend entry.
  175. Notes
  176. -----
  177. Any other keyword arguments are given to `HandlerNpoints`.
  178. """
  179. HandlerNpoints.__init__(self, marker_pad=marker_pad,
  180. numpoints=numpoints, **kw)
  181. def create_artists(self, legend, orig_handle,
  182. xdescent, ydescent, width, height, fontsize,
  183. trans):
  184. xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
  185. width, height, fontsize)
  186. ydata = np.full_like(xdata, ((height - ydescent) / 2))
  187. legline = Line2D(xdata, ydata)
  188. self.update_prop(legline, orig_handle, legend)
  189. legline.set_drawstyle('default')
  190. legline.set_marker("")
  191. legline_marker = Line2D(xdata_marker, ydata[:len(xdata_marker)])
  192. self.update_prop(legline_marker, orig_handle, legend)
  193. legline_marker.set_linestyle('None')
  194. if legend.markerscale != 1:
  195. newsz = legline_marker.get_markersize() * legend.markerscale
  196. legline_marker.set_markersize(newsz)
  197. # we don't want to add this to the return list because
  198. # the texts and handles are assumed to be in one-to-one
  199. # correspondence.
  200. legline._legmarker = legline_marker
  201. legline.set_transform(trans)
  202. legline_marker.set_transform(trans)
  203. return [legline, legline_marker]
  204. class HandlerPatch(HandlerBase):
  205. """
  206. Handler for `.Patch` instances.
  207. """
  208. def __init__(self, patch_func=None, **kw):
  209. """
  210. Parameters
  211. ----------
  212. patch_func : callable, optional
  213. The function that creates the legend key artist.
  214. *patch_func* should have the signature::
  215. def patch_func(legend=legend, orig_handle=orig_handle,
  216. xdescent=xdescent, ydescent=ydescent,
  217. width=width, height=height, fontsize=fontsize)
  218. Subsequently the created artist will have its ``update_prop``
  219. method called and the appropriate transform will be applied.
  220. Notes
  221. -----
  222. Any other keyword arguments are given to `HandlerBase`.
  223. """
  224. HandlerBase.__init__(self, **kw)
  225. self._patch_func = patch_func
  226. def _create_patch(self, legend, orig_handle,
  227. xdescent, ydescent, width, height, fontsize):
  228. if self._patch_func is None:
  229. p = Rectangle(xy=(-xdescent, -ydescent),
  230. width=width, height=height)
  231. else:
  232. p = self._patch_func(legend=legend, orig_handle=orig_handle,
  233. xdescent=xdescent, ydescent=ydescent,
  234. width=width, height=height, fontsize=fontsize)
  235. return p
  236. def create_artists(self, legend, orig_handle,
  237. xdescent, ydescent, width, height, fontsize, trans):
  238. p = self._create_patch(legend, orig_handle,
  239. xdescent, ydescent, width, height, fontsize)
  240. self.update_prop(p, orig_handle, legend)
  241. p.set_transform(trans)
  242. return [p]
  243. class HandlerLineCollection(HandlerLine2D):
  244. """
  245. Handler for `.LineCollection` instances.
  246. """
  247. def get_numpoints(self, legend):
  248. if self._numpoints is None:
  249. return legend.scatterpoints
  250. else:
  251. return self._numpoints
  252. def _default_update_prop(self, legend_handle, orig_handle):
  253. lw = orig_handle.get_linewidths()[0]
  254. dashes = orig_handle._us_linestyles[0]
  255. color = orig_handle.get_colors()[0]
  256. legend_handle.set_color(color)
  257. legend_handle.set_linestyle(dashes)
  258. legend_handle.set_linewidth(lw)
  259. def create_artists(self, legend, orig_handle,
  260. xdescent, ydescent, width, height, fontsize, trans):
  261. xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
  262. width, height, fontsize)
  263. ydata = np.full_like(xdata, (height - ydescent) / 2)
  264. legline = Line2D(xdata, ydata)
  265. self.update_prop(legline, orig_handle, legend)
  266. legline.set_transform(trans)
  267. return [legline]
  268. class HandlerRegularPolyCollection(HandlerNpointsYoffsets):
  269. """
  270. Handler for `.RegularPolyCollections`.
  271. """
  272. def __init__(self, yoffsets=None, sizes=None, **kw):
  273. HandlerNpointsYoffsets.__init__(self, yoffsets=yoffsets, **kw)
  274. self._sizes = sizes
  275. def get_numpoints(self, legend):
  276. if self._numpoints is None:
  277. return legend.scatterpoints
  278. else:
  279. return self._numpoints
  280. def get_sizes(self, legend, orig_handle,
  281. xdescent, ydescent, width, height, fontsize):
  282. if self._sizes is None:
  283. handle_sizes = orig_handle.get_sizes()
  284. if not len(handle_sizes):
  285. handle_sizes = [1]
  286. size_max = max(handle_sizes) * legend.markerscale ** 2
  287. size_min = min(handle_sizes) * legend.markerscale ** 2
  288. numpoints = self.get_numpoints(legend)
  289. if numpoints < 4:
  290. sizes = [.5 * (size_max + size_min), size_max,
  291. size_min][:numpoints]
  292. else:
  293. rng = (size_max - size_min)
  294. sizes = rng * np.linspace(0, 1, numpoints) + size_min
  295. else:
  296. sizes = self._sizes
  297. return sizes
  298. def update_prop(self, legend_handle, orig_handle, legend):
  299. self._update_prop(legend_handle, orig_handle)
  300. legend_handle.set_figure(legend.figure)
  301. # legend._set_artist_props(legend_handle)
  302. legend_handle.set_clip_box(None)
  303. legend_handle.set_clip_path(None)
  304. def create_collection(self, orig_handle, sizes, offsets, transOffset):
  305. p = type(orig_handle)(orig_handle.get_numsides(),
  306. rotation=orig_handle.get_rotation(),
  307. sizes=sizes,
  308. offsets=offsets,
  309. transOffset=transOffset,
  310. )
  311. return p
  312. def create_artists(self, legend, orig_handle,
  313. xdescent, ydescent, width, height, fontsize,
  314. trans):
  315. xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
  316. width, height, fontsize)
  317. ydata = self.get_ydata(legend, xdescent, ydescent,
  318. width, height, fontsize)
  319. sizes = self.get_sizes(legend, orig_handle, xdescent, ydescent,
  320. width, height, fontsize)
  321. p = self.create_collection(orig_handle, sizes,
  322. offsets=list(zip(xdata_marker, ydata)),
  323. transOffset=trans)
  324. self.update_prop(p, orig_handle, legend)
  325. p._transOffset = trans
  326. return [p]
  327. class HandlerPathCollection(HandlerRegularPolyCollection):
  328. """
  329. Handler for `.PathCollections`, which are used by `~.Axes.scatter`.
  330. """
  331. def create_collection(self, orig_handle, sizes, offsets, transOffset):
  332. p = type(orig_handle)([orig_handle.get_paths()[0]],
  333. sizes=sizes,
  334. offsets=offsets,
  335. transOffset=transOffset,
  336. )
  337. return p
  338. class HandlerCircleCollection(HandlerRegularPolyCollection):
  339. """
  340. Handler for `.CircleCollections`.
  341. """
  342. def create_collection(self, orig_handle, sizes, offsets, transOffset):
  343. p = type(orig_handle)(sizes,
  344. offsets=offsets,
  345. transOffset=transOffset,
  346. )
  347. return p
  348. class HandlerErrorbar(HandlerLine2D):
  349. """
  350. Handler for Errorbars.
  351. """
  352. def __init__(self, xerr_size=0.5, yerr_size=None,
  353. marker_pad=0.3, numpoints=None, **kw):
  354. self._xerr_size = xerr_size
  355. self._yerr_size = yerr_size
  356. HandlerLine2D.__init__(self, marker_pad=marker_pad,
  357. numpoints=numpoints, **kw)
  358. def get_err_size(self, legend, xdescent, ydescent,
  359. width, height, fontsize):
  360. xerr_size = self._xerr_size * fontsize
  361. if self._yerr_size is None:
  362. yerr_size = xerr_size
  363. else:
  364. yerr_size = self._yerr_size * fontsize
  365. return xerr_size, yerr_size
  366. def create_artists(self, legend, orig_handle,
  367. xdescent, ydescent, width, height, fontsize,
  368. trans):
  369. plotlines, caplines, barlinecols = orig_handle
  370. xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
  371. width, height, fontsize)
  372. ydata = np.full_like(xdata, (height - ydescent) / 2)
  373. legline = Line2D(xdata, ydata)
  374. xdata_marker = np.asarray(xdata_marker)
  375. ydata_marker = np.asarray(ydata[:len(xdata_marker)])
  376. xerr_size, yerr_size = self.get_err_size(legend, xdescent, ydescent,
  377. width, height, fontsize)
  378. legline_marker = Line2D(xdata_marker, ydata_marker)
  379. # when plotlines are None (only errorbars are drawn), we just
  380. # make legline invisible.
  381. if plotlines is None:
  382. legline.set_visible(False)
  383. legline_marker.set_visible(False)
  384. else:
  385. self.update_prop(legline, plotlines, legend)
  386. legline.set_drawstyle('default')
  387. legline.set_marker('None')
  388. self.update_prop(legline_marker, plotlines, legend)
  389. legline_marker.set_linestyle('None')
  390. if legend.markerscale != 1:
  391. newsz = legline_marker.get_markersize() * legend.markerscale
  392. legline_marker.set_markersize(newsz)
  393. handle_barlinecols = []
  394. handle_caplines = []
  395. if orig_handle.has_xerr:
  396. verts = [((x - xerr_size, y), (x + xerr_size, y))
  397. for x, y in zip(xdata_marker, ydata_marker)]
  398. coll = mcoll.LineCollection(verts)
  399. self.update_prop(coll, barlinecols[0], legend)
  400. handle_barlinecols.append(coll)
  401. if caplines:
  402. capline_left = Line2D(xdata_marker - xerr_size, ydata_marker)
  403. capline_right = Line2D(xdata_marker + xerr_size, ydata_marker)
  404. self.update_prop(capline_left, caplines[0], legend)
  405. self.update_prop(capline_right, caplines[0], legend)
  406. capline_left.set_marker("|")
  407. capline_right.set_marker("|")
  408. handle_caplines.append(capline_left)
  409. handle_caplines.append(capline_right)
  410. if orig_handle.has_yerr:
  411. verts = [((x, y - yerr_size), (x, y + yerr_size))
  412. for x, y in zip(xdata_marker, ydata_marker)]
  413. coll = mcoll.LineCollection(verts)
  414. self.update_prop(coll, barlinecols[0], legend)
  415. handle_barlinecols.append(coll)
  416. if caplines:
  417. capline_left = Line2D(xdata_marker, ydata_marker - yerr_size)
  418. capline_right = Line2D(xdata_marker, ydata_marker + yerr_size)
  419. self.update_prop(capline_left, caplines[0], legend)
  420. self.update_prop(capline_right, caplines[0], legend)
  421. capline_left.set_marker("_")
  422. capline_right.set_marker("_")
  423. handle_caplines.append(capline_left)
  424. handle_caplines.append(capline_right)
  425. artists = [
  426. *handle_barlinecols, *handle_caplines, legline, legline_marker,
  427. ]
  428. for artist in artists:
  429. artist.set_transform(trans)
  430. return artists
  431. class HandlerStem(HandlerNpointsYoffsets):
  432. """
  433. Handler for plots produced by `~.Axes.stem`.
  434. """
  435. def __init__(self, marker_pad=0.3, numpoints=None,
  436. bottom=None, yoffsets=None, **kw):
  437. """
  438. Parameters
  439. ----------
  440. marker_pad : float
  441. Padding between points in legend entry. Default is 0.3.
  442. numpoints : int, optional
  443. Number of points to show in legend entry.
  444. bottom : float, optional
  445. yoffsets : array of floats, optional
  446. Length *numpoints* list of y offsets for each point in
  447. legend entry.
  448. Notes
  449. -----
  450. Any other keyword arguments are given to `HandlerNpointsYoffsets`.
  451. """
  452. HandlerNpointsYoffsets.__init__(self, marker_pad=marker_pad,
  453. numpoints=numpoints,
  454. yoffsets=yoffsets,
  455. **kw)
  456. self._bottom = bottom
  457. def get_ydata(self, legend, xdescent, ydescent, width, height, fontsize):
  458. if self._yoffsets is None:
  459. ydata = height * (0.5 * legend._scatteryoffsets + 0.5)
  460. else:
  461. ydata = height * np.asarray(self._yoffsets)
  462. return ydata
  463. def create_artists(self, legend, orig_handle,
  464. xdescent, ydescent, width, height, fontsize,
  465. trans):
  466. markerline, stemlines, baseline = orig_handle
  467. # Check to see if the stemcontainer is storing lines as a list or a
  468. # LineCollection. Eventually using a list will be removed, and this
  469. # logic can also be removed.
  470. using_linecoll = isinstance(stemlines, mcoll.LineCollection)
  471. xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent,
  472. width, height, fontsize)
  473. ydata = self.get_ydata(legend, xdescent, ydescent,
  474. width, height, fontsize)
  475. if self._bottom is None:
  476. bottom = 0.
  477. else:
  478. bottom = self._bottom
  479. leg_markerline = Line2D(xdata_marker, ydata[:len(xdata_marker)])
  480. self.update_prop(leg_markerline, markerline, legend)
  481. leg_stemlines = [Line2D([x, x], [bottom, y])
  482. for x, y in zip(xdata_marker, ydata)]
  483. if using_linecoll:
  484. # change the function used by update_prop() from the default
  485. # to one that handles LineCollection
  486. orig_update_func = self._update_prop_func
  487. self._update_prop_func = self._copy_collection_props
  488. for line in leg_stemlines:
  489. self.update_prop(line, stemlines, legend)
  490. else:
  491. for lm, m in zip(leg_stemlines, stemlines):
  492. self.update_prop(lm, m, legend)
  493. if using_linecoll:
  494. self._update_prop_func = orig_update_func
  495. leg_baseline = Line2D([np.min(xdata), np.max(xdata)],
  496. [bottom, bottom])
  497. self.update_prop(leg_baseline, baseline, legend)
  498. artists = [*leg_stemlines, leg_baseline, leg_markerline]
  499. for artist in artists:
  500. artist.set_transform(trans)
  501. return artists
  502. def _copy_collection_props(self, legend_handle, orig_handle):
  503. """
  504. Method to copy properties from a LineCollection (orig_handle) to a
  505. Line2D (legend_handle).
  506. """
  507. legend_handle.set_color(orig_handle.get_color()[0])
  508. legend_handle.set_linestyle(orig_handle.get_linestyle()[0])
  509. class HandlerTuple(HandlerBase):
  510. """
  511. Handler for Tuple.
  512. Additional kwargs are passed through to `HandlerBase`.
  513. Parameters
  514. ----------
  515. ndivide : int, optional
  516. The number of sections to divide the legend area into. If None,
  517. use the length of the input tuple. Default is 1.
  518. pad : float, optional
  519. If None, fall back to ``legend.borderpad`` as the default.
  520. In units of fraction of font size. Default is None.
  521. """
  522. def __init__(self, ndivide=1, pad=None, **kwargs):
  523. self._ndivide = ndivide
  524. self._pad = pad
  525. HandlerBase.__init__(self, **kwargs)
  526. def create_artists(self, legend, orig_handle,
  527. xdescent, ydescent, width, height, fontsize,
  528. trans):
  529. handler_map = legend.get_legend_handler_map()
  530. if self._ndivide is None:
  531. ndivide = len(orig_handle)
  532. else:
  533. ndivide = self._ndivide
  534. if self._pad is None:
  535. pad = legend.borderpad * fontsize
  536. else:
  537. pad = self._pad * fontsize
  538. if ndivide > 1:
  539. width = (width - pad * (ndivide - 1)) / ndivide
  540. xds_cycle = cycle(xdescent - (width + pad) * np.arange(ndivide))
  541. a_list = []
  542. for handle1 in orig_handle:
  543. handler = legend.get_legend_handler(handler_map, handle1)
  544. _a_list = handler.create_artists(
  545. legend, handle1,
  546. next(xds_cycle), ydescent, width, height, fontsize, trans)
  547. a_list.extend(_a_list)
  548. return a_list
  549. class HandlerPolyCollection(HandlerBase):
  550. """
  551. Handler for `.PolyCollection` used in `~.Axes.fill_between` and
  552. `~.Axes.stackplot`.
  553. """
  554. def _update_prop(self, legend_handle, orig_handle):
  555. def first_color(colors):
  556. if colors is None:
  557. return None
  558. colors = mcolors.to_rgba_array(colors)
  559. if len(colors):
  560. return colors[0]
  561. else:
  562. return "none"
  563. def get_first(prop_array):
  564. if len(prop_array):
  565. return prop_array[0]
  566. else:
  567. return None
  568. edgecolor = getattr(orig_handle, '_original_edgecolor',
  569. orig_handle.get_edgecolor())
  570. legend_handle.set_edgecolor(first_color(edgecolor))
  571. facecolor = getattr(orig_handle, '_original_facecolor',
  572. orig_handle.get_facecolor())
  573. legend_handle.set_facecolor(first_color(facecolor))
  574. legend_handle.set_fill(orig_handle.get_fill())
  575. legend_handle.set_hatch(orig_handle.get_hatch())
  576. legend_handle.set_linewidth(get_first(orig_handle.get_linewidths()))
  577. legend_handle.set_linestyle(get_first(orig_handle.get_linestyles()))
  578. legend_handle.set_transform(get_first(orig_handle.get_transforms()))
  579. legend_handle.set_figure(orig_handle.get_figure())
  580. legend_handle.set_alpha(orig_handle.get_alpha())
  581. def create_artists(self, legend, orig_handle,
  582. xdescent, ydescent, width, height, fontsize, trans):
  583. p = Rectangle(xy=(-xdescent, -ydescent),
  584. width=width, height=height)
  585. self.update_prop(p, orig_handle, legend)
  586. p.set_transform(trans)
  587. return [p]