Coverage for /usr/lib/python3/dist-packages/matplotlib/legend_handler.py: 37%

356 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-14 15:55 +0200

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 :doc:`legend guide 

9 </tutorials/intermediate/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 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). 

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 collections.abc import Sequence 

31from itertools import cycle 

32 

33import numpy as np 

34 

35from matplotlib import _api, cbook 

36from matplotlib.lines import Line2D 

37from matplotlib.patches import Rectangle 

38import matplotlib.collections as mcoll 

39 

40 

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) 

45 

46 

47class HandlerBase: 

48 """ 

49 A Base class for default legend handlers. 

50 

51 The derived classes are meant to override *create_artists* method, which 

52 has a following signature.:: 

53 

54 def create_artists(self, legend, orig_handle, 

55 xdescent, ydescent, width, height, fontsize, 

56 trans): 

57 

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. 

61 

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 

66 

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) 

72 

73 def _default_update_prop(self, legend_handle, orig_handle): 

74 legend_handle.update_from(orig_handle) 

75 

76 def update_prop(self, legend_handle, orig_handle, legend): 

77 

78 self._update_prop(legend_handle, orig_handle) 

79 

80 legend._set_artist_props(legend_handle) 

81 legend_handle.set_clip_box(None) 

82 legend_handle.set_clip_path(None) 

83 

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 

92 

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. 

98 

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. 

112 

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()) 

122 

123 if isinstance(artists, _Line2DHandleList): 

124 artists = [artists[0]] 

125 

126 # create_artists will return a list of artists. 

127 for a in artists: 

128 handlebox.add_artist(a) 

129 

130 # we only return the first artist 

131 return artists[0] 

132 

133 def create_artists(self, legend, orig_handle, 

134 xdescent, ydescent, width, height, fontsize, 

135 trans): 

136 raise NotImplementedError('Derived must override') 

137 

138 

139class HandlerNpoints(HandlerBase): 

140 """ 

141 A legend handler that shows *numpoints* points in the legend entry. 

142 """ 

143 

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) 

156 

157 self._numpoints = numpoints 

158 self._marker_pad = marker_pad 

159 

160 def get_numpoints(self, legend): 

161 if self._numpoints is None: 

162 return legend.numpoints 

163 else: 

164 return self._numpoints 

165 

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 

179 

180 

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 """ 

186 

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 

201 

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) 

207 

208 return ydata 

209 

210 

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 """ 

216 

217 def create_artists(self, legend, orig_handle, 

218 xdescent, ydescent, width, height, fontsize, 

219 trans): 

220 

221 xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent, 

222 width, height, fontsize) 

223 

224 ydata = np.full_like(xdata, ((height - ydescent) / 2)) 

225 legline = Line2D(xdata, ydata) 

226 

227 self.update_prop(legline, orig_handle, legend) 

228 legline.set_drawstyle('default') 

229 legline.set_marker("") 

230 

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 

241 

242 legline.set_transform(trans) 

243 legline_marker.set_transform(trans) 

244 

245 return [legline, legline_marker] 

246 

247 

248class _Line2DHandleList(Sequence): 

249 def __init__(self, legline): 

250 self._legline = legline 

251 

252 def __len__(self): 

253 return 2 

254 

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] 

264 

265 

266class HandlerLine2D(HandlerNpoints): 

267 """ 

268 Handler for `.Line2D` instances. 

269 

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 """ 

275 

276 def create_artists(self, legend, orig_handle, 

277 xdescent, ydescent, width, height, fontsize, 

278 trans): 

279 

280 xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent, 

281 width, height, fontsize) 

282 

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] 

290 

291 ydata = np.full_like(xdata, (height - ydescent) / 2) 

292 legline = Line2D(xdata, ydata, markevery=markevery) 

293 

294 self.update_prop(legline, orig_handle, legend) 

295 

296 if legend.markerscale != 1: 

297 newsz = legline.get_markersize() * legend.markerscale 

298 legline.set_markersize(newsz) 

299 

300 legline.set_transform(trans) 

301 

302 return _Line2DHandleList(legline) 

303 

304 

305class HandlerPatch(HandlerBase): 

306 """ 

307 Handler for `.Patch` instances. 

308 """ 

309 

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:: 

317 

318 def patch_func(legend=legend, orig_handle=orig_handle, 

319 xdescent=xdescent, ydescent=ydescent, 

320 width=width, height=height, fontsize=fontsize) 

321 

322 Subsequently the created artist will have its ``update_prop`` 

323 method called and the appropriate transform will be applied. 

324 

325 **kwargs 

326 Keyword arguments forwarded to `.HandlerBase`. 

327 """ 

328 super().__init__(**kwargs) 

329 self._patch_func = patch_func 

330 

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 

341 

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] 

349 

350 

351class HandlerStepPatch(HandlerBase): 

352 """ 

353 Handler for `~.matplotlib.patches.StepPatch` instances. 

354 """ 

355 

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 

362 

363 # Unfilled StepPatch should show as a line 

364 def _create_line(self, legend, orig_handle, 

365 xdescent, ydescent, width, height, fontsize): 

366 

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 ) 

373 

374 legline.set_drawstyle('default') 

375 legline.set_marker("") 

376 return legline 

377 

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] 

389 

390 

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 

400 

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) 

408 

409 def create_artists(self, legend, orig_handle, 

410 xdescent, ydescent, width, height, fontsize, trans): 

411 

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) 

416 

417 self.update_prop(legline, orig_handle, legend) 

418 legline.set_transform(trans) 

419 

420 return [legline] 

421 

422 

423class HandlerRegularPolyCollection(HandlerNpointsYoffsets): 

424 r"""Handler for `.RegularPolyCollection`\s.""" 

425 

426 def __init__(self, yoffsets=None, sizes=None, **kwargs): 

427 super().__init__(yoffsets=yoffsets, **kwargs) 

428 

429 self._sizes = sizes 

430 

431 def get_numpoints(self, legend): 

432 if self._numpoints is None: 

433 return legend.scatterpoints 

434 else: 

435 return self._numpoints 

436 

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 

445 

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 

455 

456 return sizes 

457 

458 def update_prop(self, legend_handle, orig_handle, legend): 

459 

460 self._update_prop(legend_handle, orig_handle) 

461 

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) 

466 

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 ) 

474 

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) 

480 

481 ydata = self.get_ydata(legend, xdescent, ydescent, 

482 width, height, fontsize) 

483 

484 sizes = self.get_sizes(legend, orig_handle, xdescent, ydescent, 

485 width, height, fontsize) 

486 

487 p = self.create_collection( 

488 orig_handle, sizes, 

489 offsets=list(zip(xdata_marker, ydata)), offset_transform=trans) 

490 

491 self.update_prop(p, orig_handle, legend) 

492 p.set_offset_transform(trans) 

493 return [p] 

494 

495 

496class HandlerPathCollection(HandlerRegularPolyCollection): 

497 r"""Handler for `.PathCollection`\s, which are used by `~.Axes.scatter`.""" 

498 

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 ) 

505 

506 

507class HandlerCircleCollection(HandlerRegularPolyCollection): 

508 r"""Handler for `.CircleCollection`\s.""" 

509 

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) 

514 

515 

516class HandlerErrorbar(HandlerLine2D): 

517 """Handler for Errorbars.""" 

518 

519 def __init__(self, xerr_size=0.5, yerr_size=None, 

520 marker_pad=0.3, numpoints=None, **kwargs): 

521 

522 self._xerr_size = xerr_size 

523 self._yerr_size = yerr_size 

524 

525 super().__init__(marker_pad=marker_pad, numpoints=numpoints, **kwargs) 

526 

527 def get_err_size(self, legend, xdescent, ydescent, 

528 width, height, fontsize): 

529 xerr_size = self._xerr_size * fontsize 

530 

531 if self._yerr_size is None: 

532 yerr_size = xerr_size 

533 else: 

534 yerr_size = self._yerr_size * fontsize 

535 

536 return xerr_size, yerr_size 

537 

538 def create_artists(self, legend, orig_handle, 

539 xdescent, ydescent, width, height, fontsize, 

540 trans): 

541 

542 plotlines, caplines, barlinecols = orig_handle 

543 

544 xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent, 

545 width, height, fontsize) 

546 

547 ydata = np.full_like(xdata, (height - ydescent) / 2) 

548 legline = Line2D(xdata, ydata) 

549 

550 xdata_marker = np.asarray(xdata_marker) 

551 ydata_marker = np.asarray(ydata[:len(xdata_marker)]) 

552 

553 xerr_size, yerr_size = self.get_err_size(legend, xdescent, ydescent, 

554 width, height, fontsize) 

555 

556 legline_marker = Line2D(xdata_marker, ydata_marker) 

557 

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) 

565 

566 legline.set_drawstyle('default') 

567 legline.set_marker('none') 

568 

569 self.update_prop(legline_marker, plotlines, legend) 

570 legline_marker.set_linestyle('None') 

571 

572 if legend.markerscale != 1: 

573 newsz = legline_marker.get_markersize() * legend.markerscale 

574 legline_marker.set_markersize(newsz) 

575 

576 handle_barlinecols = [] 

577 handle_caplines = [] 

578 

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) 

585 

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("|") 

593 

594 handle_caplines.append(capline_left) 

595 handle_caplines.append(capline_right) 

596 

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) 

603 

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("_") 

611 

612 handle_caplines.append(capline_left) 

613 handle_caplines.append(capline_right) 

614 

615 artists = [ 

616 *handle_barlinecols, *handle_caplines, legline, legline_marker, 

617 ] 

618 for artist in artists: 

619 artist.set_transform(trans) 

620 return artists 

621 

622 

623class HandlerStem(HandlerNpointsYoffsets): 

624 """ 

625 Handler for plots produced by `~.Axes.stem`. 

626 """ 

627 

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 

638 

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 

648 

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) 

654 

655 return ydata 

656 

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) 

665 

666 xdata, xdata_marker = self.get_xdata(legend, xdescent, ydescent, 

667 width, height, fontsize) 

668 

669 ydata = self.get_ydata(legend, xdescent, ydescent, 

670 width, height, fontsize) 

671 

672 if self._bottom is None: 

673 bottom = 0. 

674 else: 

675 bottom = self._bottom 

676 

677 leg_markerline = Line2D(xdata_marker, ydata[:len(xdata_marker)]) 

678 self.update_prop(leg_markerline, markerline, legend) 

679 

680 leg_stemlines = [Line2D([x, x], [bottom, y]) 

681 for x, y in zip(xdata_marker, ydata)] 

682 

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) 

690 

691 else: 

692 for lm, m in zip(leg_stemlines, stemlines): 

693 self.update_prop(lm, m, legend) 

694 

695 leg_baseline = Line2D([np.min(xdata), np.max(xdata)], 

696 [bottom, bottom]) 

697 self.update_prop(leg_baseline, baseline, legend) 

698 

699 artists = [*leg_stemlines, leg_baseline, leg_markerline] 

700 for artist in artists: 

701 artist.set_transform(trans) 

702 return artists 

703 

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]) 

711 

712 

713class HandlerTuple(HandlerBase): 

714 """ 

715 Handler for Tuple. 

716 """ 

717 

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) 

733 

734 def create_artists(self, legend, orig_handle, 

735 xdescent, ydescent, width, height, fontsize, 

736 trans): 

737 

738 handler_map = legend.get_legend_handler_map() 

739 

740 if self._ndivide is None: 

741 ndivide = len(orig_handle) 

742 else: 

743 ndivide = self._ndivide 

744 

745 if self._pad is None: 

746 pad = legend.borderpad * fontsize 

747 else: 

748 pad = self._pad * fontsize 

749 

750 if ndivide > 1: 

751 width = (width - pad * (ndivide - 1)) / ndivide 

752 

753 xds_cycle = cycle(xdescent - (width + pad) * np.arange(ndivide)) 

754 

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) 

764 

765 return a_list 

766 

767 

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]) 

778 

779 def get_first(prop_array): 

780 if len(prop_array): 

781 return prop_array[0] 

782 else: 

783 return None 

784 

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. 

801 

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]