Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/legend_handler.py: 25%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

340 statements  

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]