Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2The legend module defines the Legend class, which is responsible for 

3drawing legends associated with axes and/or figures. 

4 

5.. important:: 

6 

7 It is unlikely that you would ever create a Legend instance 

8 manually. Most users would normally create a legend via the 

9 :meth:`~matplotlib.axes.Axes.legend` function. For more details on legends 

10 there is also a :doc:`legend guide </tutorials/intermediate/legend_guide>`. 

11 

12The Legend class can be considered as a container of legend handles and 

13legend texts. Creation of corresponding legend handles from the plot elements 

14in the axes or figures (e.g., lines, patches, etc.) are specified by the 

15handler map, which defines the mapping between the plot elements and the 

16legend handlers to be used (the default legend handlers are defined in the 

17:mod:`~matplotlib.legend_handler` module). Note that not all kinds of 

18artist are supported by the legend yet by default but it is possible to 

19extend the legend handler's capabilities to support arbitrary objects. See 

20the :doc:`legend guide </tutorials/intermediate/legend_guide>` for more 

21information. 

22""" 

23 

24import logging 

25import time 

26 

27import numpy as np 

28 

29from matplotlib import rcParams 

30from matplotlib import cbook, docstring 

31from matplotlib.artist import Artist, allow_rasterization 

32from matplotlib.cbook import silent_list 

33from matplotlib.font_manager import FontProperties 

34from matplotlib.lines import Line2D 

35from matplotlib.patches import Patch, Rectangle, Shadow, FancyBboxPatch 

36from matplotlib.collections import (LineCollection, RegularPolyCollection, 

37 CircleCollection, PathCollection, 

38 PolyCollection) 

39from matplotlib.transforms import Bbox, BboxBase, TransformedBbox 

40from matplotlib.transforms import BboxTransformTo, BboxTransformFrom 

41 

42from matplotlib.offsetbox import HPacker, VPacker, TextArea, DrawingArea 

43from matplotlib.offsetbox import DraggableOffsetBox 

44 

45from matplotlib.container import ErrorbarContainer, BarContainer, StemContainer 

46from . import legend_handler 

47 

48 

49class DraggableLegend(DraggableOffsetBox): 

50 def __init__(self, legend, use_blit=False, update="loc"): 

51 """ 

52 Wrapper around a `.Legend` to support mouse dragging. 

53 

54 Parameters 

55 ---------- 

56 legend : `.Legend` 

57 The `.Legend` instance to wrap. 

58 use_blit : bool, optional 

59 Use blitting for faster image composition. For details see 

60 :ref:`func-animation`. 

61 update : {'loc', 'bbox'}, optional 

62 If "loc", update the *loc* parameter of the legend upon finalizing. 

63 If "bbox", update the *bbox_to_anchor* parameter. 

64 """ 

65 self.legend = legend 

66 

67 cbook._check_in_list(["loc", "bbox"], update=update) 

68 self._update = update 

69 

70 DraggableOffsetBox.__init__(self, legend, legend._legend_box, 

71 use_blit=use_blit) 

72 

73 def artist_picker(self, legend, evt): 

74 return self.legend.contains(evt) 

75 

76 def finalize_offset(self): 

77 if self._update == "loc": 

78 self._update_loc(self.get_loc_in_canvas()) 

79 elif self._update == "bbox": 

80 self._bbox_to_anchor(self.get_loc_in_canvas()) 

81 

82 def _update_loc(self, loc_in_canvas): 

83 bbox = self.legend.get_bbox_to_anchor() 

84 # if bbox has zero width or height, the transformation is 

85 # ill-defined. Fall back to the default bbox_to_anchor. 

86 if bbox.width == 0 or bbox.height == 0: 

87 self.legend.set_bbox_to_anchor(None) 

88 bbox = self.legend.get_bbox_to_anchor() 

89 _bbox_transform = BboxTransformFrom(bbox) 

90 self.legend._loc = tuple(_bbox_transform.transform(loc_in_canvas)) 

91 

92 def _update_bbox_to_anchor(self, loc_in_canvas): 

93 loc_in_bbox = self.legend.axes.transAxes.transform(loc_in_canvas) 

94 self.legend.set_bbox_to_anchor(loc_in_bbox) 

95 

96 

97_legend_kw_doc = ''' 

98loc : str or pair of floats, default: :rc:`legend.loc` ('best' for axes, \ 

99'upper right' for figures) 

100 The location of the legend. 

101 

102 The strings 

103 ``'upper left', 'upper right', 'lower left', 'lower right'`` 

104 place the legend at the corresponding corner of the axes/figure. 

105 

106 The strings 

107 ``'upper center', 'lower center', 'center left', 'center right'`` 

108 place the legend at the center of the corresponding edge of the 

109 axes/figure. 

110 

111 The string ``'center'`` places the legend at the center of the axes/figure. 

112 

113 The string ``'best'`` places the legend at the location, among the nine 

114 locations defined so far, with the minimum overlap with other drawn 

115 artists. This option can be quite slow for plots with large amounts of 

116 data; your plotting speed may benefit from providing a specific location. 

117 

118 The location can also be a 2-tuple giving the coordinates of the lower-left 

119 corner of the legend in axes coordinates (in which case *bbox_to_anchor* 

120 will be ignored). 

121 

122 For back-compatibility, ``'center right'`` (but no other location) can also 

123 be spelled ``'right'``, and each "string" locations can also be given as a 

124 numeric value: 

125 

126 =============== ============= 

127 Location String Location Code 

128 =============== ============= 

129 'best' 0 

130 'upper right' 1 

131 'upper left' 2 

132 'lower left' 3 

133 'lower right' 4 

134 'right' 5 

135 'center left' 6 

136 'center right' 7 

137 'lower center' 8 

138 'upper center' 9 

139 'center' 10 

140 =============== ============= 

141 

142bbox_to_anchor : `.BboxBase`, 2-tuple, or 4-tuple of floats 

143 Box that is used to position the legend in conjunction with *loc*. 

144 Defaults to `axes.bbox` (if called as a method to `.Axes.legend`) or 

145 `figure.bbox` (if `.Figure.legend`). This argument allows arbitrary 

146 placement of the legend. 

147 

148 Bbox coordinates are interpreted in the coordinate system given by 

149 `bbox_transform`, with the default transform 

150 Axes or Figure coordinates, depending on which ``legend`` is called. 

151 

152 If a 4-tuple or `.BboxBase` is given, then it specifies the bbox 

153 ``(x, y, width, height)`` that the legend is placed in. 

154 To put the legend in the best location in the bottom right 

155 quadrant of the axes (or figure):: 

156 

157 loc='best', bbox_to_anchor=(0.5, 0., 0.5, 0.5) 

158 

159 A 2-tuple ``(x, y)`` places the corner of the legend specified by *loc* at 

160 x, y. For example, to put the legend's upper right-hand corner in the 

161 center of the axes (or figure) the following keywords can be used:: 

162 

163 loc='upper right', bbox_to_anchor=(0.5, 0.5) 

164 

165ncol : integer 

166 The number of columns that the legend has. Default is 1. 

167 

168prop : None or :class:`matplotlib.font_manager.FontProperties` or dict 

169 The font properties of the legend. If None (default), the current 

170 :data:`matplotlib.rcParams` will be used. 

171 

172fontsize : int or float or {'xx-small', 'x-small', 'small', 'medium', \ 

173'large', 'x-large', 'xx-large'} 

174 The font size of the legend. If the value is numeric the size will be the 

175 absolute font size in points. String values are relative to the current 

176 default font size. This argument is only used if *prop* is not specified. 

177 

178numpoints : None or int 

179 The number of marker points in the legend when creating a legend 

180 entry for a `.Line2D` (line). 

181 Default is ``None``, which means using :rc:`legend.numpoints`. 

182 

183scatterpoints : None or int 

184 The number of marker points in the legend when creating 

185 a legend entry for a `.PathCollection` (scatter plot). 

186 Default is ``None``, which means using :rc:`legend.scatterpoints`. 

187 

188scatteryoffsets : iterable of floats 

189 The vertical offset (relative to the font size) for the markers 

190 created for a scatter plot legend entry. 0.0 is at the base the 

191 legend text, and 1.0 is at the top. To draw all markers at the 

192 same height, set to ``[0.5]``. Default is ``[0.375, 0.5, 0.3125]``. 

193 

194markerscale : None or int or float 

195 The relative size of legend markers compared with the originally 

196 drawn ones. 

197 Default is ``None``, which means using :rc:`legend.markerscale`. 

198 

199markerfirst : bool 

200 If *True*, legend marker is placed to the left of the legend label. 

201 If *False*, legend marker is placed to the right of the legend 

202 label. 

203 Default is *True*. 

204 

205frameon : None or bool 

206 Whether the legend should be drawn on a patch (frame). 

207 Default is ``None``, which means using :rc:`legend.frameon`. 

208 

209fancybox : None or bool 

210 Whether round edges should be enabled around the `~.FancyBboxPatch` which 

211 makes up the legend's background. 

212 Default is ``None``, which means using :rc:`legend.fancybox`. 

213 

214shadow : None or bool 

215 Whether to draw a shadow behind the legend. 

216 Default is ``None``, which means using :rc:`legend.shadow`. 

217 

218framealpha : None or float 

219 The alpha transparency of the legend's background. 

220 Default is ``None``, which means using :rc:`legend.framealpha`. 

221 If *shadow* is activated and *framealpha* is ``None``, the default value is 

222 ignored. 

223 

224facecolor : None or "inherit" or color 

225 The legend's background color. 

226 Default is ``None``, which means using :rc:`legend.facecolor`. 

227 If ``"inherit"``, use :rc:`axes.facecolor`. 

228 

229edgecolor : None or "inherit" or color 

230 The legend's background patch edge color. 

231 Default is ``None``, which means using :rc:`legend.edgecolor`. 

232 If ``"inherit"``, use take :rc:`axes.edgecolor`. 

233 

234mode : {"expand", None} 

235 If *mode* is set to ``"expand"`` the legend will be horizontally 

236 expanded to fill the axes area (or `bbox_to_anchor` if defines 

237 the legend's size). 

238 

239bbox_transform : None or :class:`matplotlib.transforms.Transform` 

240 The transform for the bounding box (`bbox_to_anchor`). For a value 

241 of ``None`` (default) the Axes' 

242 :data:`~matplotlib.axes.Axes.transAxes` transform will be used. 

243 

244title : str or None 

245 The legend's title. Default is no title (``None``). 

246 

247title_fontsize: str or None 

248 The fontsize of the legend's title. Default is the default fontsize. 

249 

250borderpad : float or None 

251 The fractional whitespace inside the legend border, in font-size units. 

252 Default is ``None``, which means using :rc:`legend.borderpad`. 

253 

254labelspacing : float or None 

255 The vertical space between the legend entries, in font-size units. 

256 Default is ``None``, which means using :rc:`legend.labelspacing`. 

257 

258handlelength : float or None 

259 The length of the legend handles, in font-size units. 

260 Default is ``None``, which means using :rc:`legend.handlelength`. 

261 

262handletextpad : float or None 

263 The pad between the legend handle and text, in font-size units. 

264 Default is ``None``, which means using :rc:`legend.handletextpad`. 

265 

266borderaxespad : float or None 

267 The pad between the axes and legend border, in font-size units. 

268 Default is ``None``, which means using :rc:`legend.borderaxespad`. 

269 

270columnspacing : float or None 

271 The spacing between columns, in font-size units. 

272 Default is ``None``, which means using :rc:`legend.columnspacing`. 

273 

274handler_map : dict or None 

275 The custom dictionary mapping instances or types to a legend 

276 handler. This `handler_map` updates the default handler map 

277 found at :func:`matplotlib.legend.Legend.get_legend_handler_map`. 

278 

279''' 

280docstring.interpd.update(_legend_kw_doc=_legend_kw_doc) 

281 

282 

283class Legend(Artist): 

284 """ 

285 Place a legend on the axes at location loc. 

286 

287 """ 

288 codes = {'best': 0, # only implemented for axes legends 

289 'upper right': 1, 

290 'upper left': 2, 

291 'lower left': 3, 

292 'lower right': 4, 

293 'right': 5, 

294 'center left': 6, 

295 'center right': 7, 

296 'lower center': 8, 

297 'upper center': 9, 

298 'center': 10, 

299 } 

300 

301 zorder = 5 

302 

303 def __str__(self): 

304 return "Legend" 

305 

306 @docstring.dedent_interpd 

307 def __init__(self, parent, handles, labels, 

308 loc=None, 

309 numpoints=None, # the number of points in the legend line 

310 markerscale=None, # the relative size of legend markers 

311 # vs. original 

312 markerfirst=True, # controls ordering (left-to-right) of 

313 # legend marker and label 

314 scatterpoints=None, # number of scatter points 

315 scatteryoffsets=None, 

316 prop=None, # properties for the legend texts 

317 fontsize=None, # keyword to set font size directly 

318 

319 # spacing & pad defined as a fraction of the font-size 

320 borderpad=None, # the whitespace inside the legend border 

321 labelspacing=None, # the vertical space between the legend 

322 # entries 

323 handlelength=None, # the length of the legend handles 

324 handleheight=None, # the height of the legend handles 

325 handletextpad=None, # the pad between the legend handle 

326 # and text 

327 borderaxespad=None, # the pad between the axes and legend 

328 # border 

329 columnspacing=None, # spacing between columns 

330 

331 ncol=1, # number of columns 

332 mode=None, # mode for horizontal distribution of columns. 

333 # None, "expand" 

334 

335 fancybox=None, # True use a fancy box, false use a rounded 

336 # box, none use rc 

337 shadow=None, 

338 title=None, # set a title for the legend 

339 title_fontsize=None, # set to ax.fontsize if None 

340 framealpha=None, # set frame alpha 

341 edgecolor=None, # frame patch edgecolor 

342 facecolor=None, # frame patch facecolor 

343 

344 bbox_to_anchor=None, # bbox that the legend will be anchored. 

345 bbox_transform=None, # transform for the bbox 

346 frameon=None, # draw frame 

347 handler_map=None, 

348 ): 

349 """ 

350 Parameters 

351 ---------- 

352 parent : `~matplotlib.axes.Axes` or `.Figure` 

353 The artist that contains the legend. 

354 

355 handles : list of `.Artist` 

356 A list of Artists (lines, patches) to be added to the legend. 

357 

358 labels : list of str 

359 A list of labels to show next to the artists. The length of handles 

360 and labels should be the same. If they are not, they are truncated 

361 to the smaller of both lengths. 

362 

363 Other Parameters 

364 ---------------- 

365 %(_legend_kw_doc)s 

366 

367 Notes 

368 ----- 

369 Users can specify any arbitrary location for the legend using the 

370 *bbox_to_anchor* keyword argument. *bbox_to_anchor* can be a 

371 `.BboxBase` (or derived therefrom) or a tuple of 2 or 4 floats. 

372 See :meth:`set_bbox_to_anchor` for more detail. 

373 

374 The legend location can be specified by setting *loc* with a tuple of 

375 2 floats, which is interpreted as the lower-left corner of the legend 

376 in the normalized axes coordinate. 

377 """ 

378 # local import only to avoid circularity 

379 from matplotlib.axes import Axes 

380 from matplotlib.figure import Figure 

381 

382 Artist.__init__(self) 

383 

384 if prop is None: 

385 if fontsize is not None: 

386 self.prop = FontProperties(size=fontsize) 

387 else: 

388 self.prop = FontProperties(size=rcParams["legend.fontsize"]) 

389 elif isinstance(prop, dict): 

390 self.prop = FontProperties(**prop) 

391 if "size" not in prop: 

392 self.prop.set_size(rcParams["legend.fontsize"]) 

393 else: 

394 self.prop = prop 

395 

396 self._fontsize = self.prop.get_size_in_points() 

397 

398 self.texts = [] 

399 self.legendHandles = [] 

400 self._legend_title_box = None 

401 

402 #: A dictionary with the extra handler mappings for this Legend 

403 #: instance. 

404 self._custom_handler_map = handler_map 

405 

406 locals_view = locals() 

407 for name in ["numpoints", "markerscale", "shadow", "columnspacing", 

408 "scatterpoints", "handleheight", 'borderpad', 

409 'labelspacing', 'handlelength', 'handletextpad', 

410 'borderaxespad']: 

411 if locals_view[name] is None: 

412 value = rcParams["legend." + name] 

413 else: 

414 value = locals_view[name] 

415 setattr(self, name, value) 

416 del locals_view 

417 # trim handles and labels if illegal label... 

418 _lab, _hand = [], [] 

419 for label, handle in zip(labels, handles): 

420 if isinstance(label, str) and label.startswith('_'): 

421 cbook._warn_external('The handle {!r} has a label of {!r} ' 

422 'which cannot be automatically added to' 

423 ' the legend.'.format(handle, label)) 

424 else: 

425 _lab.append(label) 

426 _hand.append(handle) 

427 labels, handles = _lab, _hand 

428 

429 handles = list(handles) 

430 if len(handles) < 2: 

431 ncol = 1 

432 self._ncol = ncol 

433 

434 if self.numpoints <= 0: 

435 raise ValueError("numpoints must be > 0; it was %d" % numpoints) 

436 

437 # introduce y-offset for handles of the scatter plot 

438 if scatteryoffsets is None: 

439 self._scatteryoffsets = np.array([3. / 8., 4. / 8., 2.5 / 8.]) 

440 else: 

441 self._scatteryoffsets = np.asarray(scatteryoffsets) 

442 reps = self.scatterpoints // len(self._scatteryoffsets) + 1 

443 self._scatteryoffsets = np.tile(self._scatteryoffsets, 

444 reps)[:self.scatterpoints] 

445 

446 # _legend_box is an OffsetBox instance that contains all 

447 # legend items and will be initialized from _init_legend_box() 

448 # method. 

449 self._legend_box = None 

450 

451 if isinstance(parent, Axes): 

452 self.isaxes = True 

453 self.axes = parent 

454 self.set_figure(parent.figure) 

455 elif isinstance(parent, Figure): 

456 self.isaxes = False 

457 self.set_figure(parent) 

458 else: 

459 raise TypeError("Legend needs either Axes or Figure as parent") 

460 self.parent = parent 

461 

462 self._loc_used_default = loc is None 

463 if loc is None: 

464 loc = rcParams["legend.loc"] 

465 if not self.isaxes and loc in [0, 'best']: 

466 loc = 'upper right' 

467 if isinstance(loc, str): 

468 if loc not in self.codes: 

469 if self.isaxes: 

470 cbook.warn_deprecated( 

471 "3.1", message="Unrecognized location {!r}. Falling " 

472 "back on 'best'; valid locations are\n\t{}\n" 

473 "This will raise an exception %(removal)s." 

474 .format(loc, '\n\t'.join(self.codes))) 

475 loc = 0 

476 else: 

477 cbook.warn_deprecated( 

478 "3.1", message="Unrecognized location {!r}. Falling " 

479 "back on 'upper right'; valid locations are\n\t{}\n'" 

480 "This will raise an exception %(removal)s." 

481 .format(loc, '\n\t'.join(self.codes))) 

482 loc = 1 

483 else: 

484 loc = self.codes[loc] 

485 if not self.isaxes and loc == 0: 

486 cbook.warn_deprecated( 

487 "3.1", message="Automatic legend placement (loc='best') not " 

488 "implemented for figure legend. Falling back on 'upper " 

489 "right'. This will raise an exception %(removal)s.") 

490 loc = 1 

491 

492 self._mode = mode 

493 self.set_bbox_to_anchor(bbox_to_anchor, bbox_transform) 

494 

495 # We use FancyBboxPatch to draw a legend frame. The location 

496 # and size of the box will be updated during the drawing time. 

497 

498 if facecolor is None: 

499 facecolor = rcParams["legend.facecolor"] 

500 if facecolor == 'inherit': 

501 facecolor = rcParams["axes.facecolor"] 

502 

503 if edgecolor is None: 

504 edgecolor = rcParams["legend.edgecolor"] 

505 if edgecolor == 'inherit': 

506 edgecolor = rcParams["axes.edgecolor"] 

507 

508 self.legendPatch = FancyBboxPatch( 

509 xy=(0.0, 0.0), width=1., height=1., 

510 facecolor=facecolor, 

511 edgecolor=edgecolor, 

512 mutation_scale=self._fontsize, 

513 snap=True 

514 ) 

515 

516 # The width and height of the legendPatch will be set (in the 

517 # draw()) to the length that includes the padding. Thus we set 

518 # pad=0 here. 

519 if fancybox is None: 

520 fancybox = rcParams["legend.fancybox"] 

521 

522 if fancybox: 

523 self.legendPatch.set_boxstyle("round", pad=0, 

524 rounding_size=0.2) 

525 else: 

526 self.legendPatch.set_boxstyle("square", pad=0) 

527 

528 self._set_artist_props(self.legendPatch) 

529 

530 self._drawFrame = frameon 

531 if frameon is None: 

532 self._drawFrame = rcParams["legend.frameon"] 

533 

534 # init with null renderer 

535 self._init_legend_box(handles, labels, markerfirst) 

536 

537 # If shadow is activated use framealpha if not 

538 # explicitly passed. See Issue 8943 

539 if framealpha is None: 

540 if shadow: 

541 self.get_frame().set_alpha(1) 

542 else: 

543 self.get_frame().set_alpha(rcParams["legend.framealpha"]) 

544 else: 

545 self.get_frame().set_alpha(framealpha) 

546 

547 tmp = self._loc_used_default 

548 self._set_loc(loc) 

549 self._loc_used_default = tmp # ignore changes done by _set_loc 

550 

551 # figure out title fontsize: 

552 if title_fontsize is None: 

553 title_fontsize = rcParams['legend.title_fontsize'] 

554 tprop = FontProperties(size=title_fontsize) 

555 self.set_title(title, prop=tprop) 

556 self._draggable = None 

557 

558 def _set_artist_props(self, a): 

559 """ 

560 Set the boilerplate props for artists added to axes. 

561 """ 

562 a.set_figure(self.figure) 

563 if self.isaxes: 

564 # a.set_axes(self.axes) 

565 a.axes = self.axes 

566 

567 a.set_transform(self.get_transform()) 

568 

569 def _set_loc(self, loc): 

570 # find_offset function will be provided to _legend_box and 

571 # _legend_box will draw itself at the location of the return 

572 # value of the find_offset. 

573 self._loc_used_default = False 

574 self._loc_real = loc 

575 self.stale = True 

576 self._legend_box.set_offset(self._findoffset) 

577 

578 def _get_loc(self): 

579 return self._loc_real 

580 

581 _loc = property(_get_loc, _set_loc) 

582 

583 def _findoffset(self, width, height, xdescent, ydescent, renderer): 

584 "Helper function to locate the legend." 

585 

586 if self._loc == 0: # "best". 

587 x, y = self._find_best_position(width, height, renderer) 

588 elif self._loc in Legend.codes.values(): # Fixed location. 

589 bbox = Bbox.from_bounds(0, 0, width, height) 

590 x, y = self._get_anchored_bbox(self._loc, bbox, 

591 self.get_bbox_to_anchor(), 

592 renderer) 

593 else: # Axes or figure coordinates. 

594 fx, fy = self._loc 

595 bbox = self.get_bbox_to_anchor() 

596 x, y = bbox.x0 + bbox.width * fx, bbox.y0 + bbox.height * fy 

597 

598 return x + xdescent, y + ydescent 

599 

600 @allow_rasterization 

601 def draw(self, renderer): 

602 "Draw everything that belongs to the legend." 

603 if not self.get_visible(): 

604 return 

605 

606 renderer.open_group('legend', gid=self.get_gid()) 

607 

608 fontsize = renderer.points_to_pixels(self._fontsize) 

609 

610 # if mode == fill, set the width of the legend_box to the 

611 # width of the parent (minus pads) 

612 if self._mode in ["expand"]: 

613 pad = 2 * (self.borderaxespad + self.borderpad) * fontsize 

614 self._legend_box.set_width(self.get_bbox_to_anchor().width - pad) 

615 

616 # update the location and size of the legend. This needs to 

617 # be done in any case to clip the figure right. 

618 bbox = self._legend_box.get_window_extent(renderer) 

619 self.legendPatch.set_bounds(bbox.x0, bbox.y0, 

620 bbox.width, bbox.height) 

621 self.legendPatch.set_mutation_scale(fontsize) 

622 

623 if self._drawFrame: 

624 if self.shadow: 

625 shadow = Shadow(self.legendPatch, 2, -2) 

626 shadow.draw(renderer) 

627 

628 self.legendPatch.draw(renderer) 

629 

630 self._legend_box.draw(renderer) 

631 

632 renderer.close_group('legend') 

633 self.stale = False 

634 

635 def _approx_text_height(self, renderer=None): 

636 """ 

637 Return the approximate height of the text. This is used to place 

638 the legend handle. 

639 """ 

640 if renderer is None: 

641 return self._fontsize 

642 else: 

643 return renderer.points_to_pixels(self._fontsize) 

644 

645 # _default_handler_map defines the default mapping between plot 

646 # elements and the legend handlers. 

647 

648 _default_handler_map = { 

649 StemContainer: legend_handler.HandlerStem(), 

650 ErrorbarContainer: legend_handler.HandlerErrorbar(), 

651 Line2D: legend_handler.HandlerLine2D(), 

652 Patch: legend_handler.HandlerPatch(), 

653 LineCollection: legend_handler.HandlerLineCollection(), 

654 RegularPolyCollection: legend_handler.HandlerRegularPolyCollection(), 

655 CircleCollection: legend_handler.HandlerCircleCollection(), 

656 BarContainer: legend_handler.HandlerPatch( 

657 update_func=legend_handler.update_from_first_child), 

658 tuple: legend_handler.HandlerTuple(), 

659 PathCollection: legend_handler.HandlerPathCollection(), 

660 PolyCollection: legend_handler.HandlerPolyCollection() 

661 } 

662 

663 # (get|set|update)_default_handler_maps are public interfaces to 

664 # modify the default handler map. 

665 

666 @classmethod 

667 def get_default_handler_map(cls): 

668 """ 

669 A class method that returns the default handler map. 

670 """ 

671 return cls._default_handler_map 

672 

673 @classmethod 

674 def set_default_handler_map(cls, handler_map): 

675 """ 

676 A class method to set the default handler map. 

677 """ 

678 cls._default_handler_map = handler_map 

679 

680 @classmethod 

681 def update_default_handler_map(cls, handler_map): 

682 """ 

683 A class method to update the default handler map. 

684 """ 

685 cls._default_handler_map.update(handler_map) 

686 

687 def get_legend_handler_map(self): 

688 """ 

689 Return the handler map. 

690 """ 

691 

692 default_handler_map = self.get_default_handler_map() 

693 

694 if self._custom_handler_map: 

695 hm = default_handler_map.copy() 

696 hm.update(self._custom_handler_map) 

697 return hm 

698 else: 

699 return default_handler_map 

700 

701 @staticmethod 

702 def get_legend_handler(legend_handler_map, orig_handle): 

703 """ 

704 Return a legend handler from *legend_handler_map* that 

705 corresponds to *orig_handler*. 

706 

707 *legend_handler_map* should be a dictionary object (that is 

708 returned by the get_legend_handler_map method). 

709 

710 It first checks if the *orig_handle* itself is a key in the 

711 *legend_handler_map* and return the associated value. 

712 Otherwise, it checks for each of the classes in its 

713 method-resolution-order. If no matching key is found, it 

714 returns ``None``. 

715 """ 

716 try: 

717 return legend_handler_map[orig_handle] 

718 except (TypeError, KeyError): # TypeError if unhashable. 

719 pass 

720 for handle_type in type(orig_handle).mro(): 

721 try: 

722 return legend_handler_map[handle_type] 

723 except KeyError: 

724 pass 

725 return None 

726 

727 def _init_legend_box(self, handles, labels, markerfirst=True): 

728 """ 

729 Initialize the legend_box. The legend_box is an instance of 

730 the OffsetBox, which is packed with legend handles and 

731 texts. Once packed, their location is calculated during the 

732 drawing time. 

733 """ 

734 

735 fontsize = self._fontsize 

736 

737 # legend_box is a HPacker, horizontally packed with 

738 # columns. Each column is a VPacker, vertically packed with 

739 # legend items. Each legend item is HPacker packed with 

740 # legend handleBox and labelBox. handleBox is an instance of 

741 # offsetbox.DrawingArea which contains legend handle. labelBox 

742 # is an instance of offsetbox.TextArea which contains legend 

743 # text. 

744 

745 text_list = [] # the list of text instances 

746 handle_list = [] # the list of text instances 

747 handles_and_labels = [] 

748 

749 label_prop = dict(verticalalignment='baseline', 

750 horizontalalignment='left', 

751 fontproperties=self.prop, 

752 ) 

753 

754 # The approximate height and descent of text. These values are 

755 # only used for plotting the legend handle. 

756 descent = 0.35 * self._approx_text_height() * (self.handleheight - 0.7) 

757 # 0.35 and 0.7 are just heuristic numbers and may need to be improved. 

758 height = self._approx_text_height() * self.handleheight - descent 

759 # each handle needs to be drawn inside a box of (x, y, w, h) = 

760 # (0, -descent, width, height). And their coordinates should 

761 # be given in the display coordinates. 

762 

763 # The transformation of each handle will be automatically set 

764 # to self.get_transform(). If the artist does not use its 

765 # default transform (e.g., Collections), you need to 

766 # manually set their transform to the self.get_transform(). 

767 legend_handler_map = self.get_legend_handler_map() 

768 

769 for orig_handle, lab in zip(handles, labels): 

770 handler = self.get_legend_handler(legend_handler_map, orig_handle) 

771 if handler is None: 

772 cbook._warn_external( 

773 "Legend does not support {!r} instances.\nA proxy artist " 

774 "may be used instead.\nSee: " 

775 "http://matplotlib.org/users/legend_guide.html" 

776 "#creating-artists-specifically-for-adding-to-the-legend-" 

777 "aka-proxy-artists".format(orig_handle)) 

778 # We don't have a handle for this artist, so we just defer 

779 # to None. 

780 handle_list.append(None) 

781 else: 

782 textbox = TextArea(lab, textprops=label_prop, 

783 multilinebaseline=True, 

784 minimumdescent=True) 

785 handlebox = DrawingArea(width=self.handlelength * fontsize, 

786 height=height, 

787 xdescent=0., ydescent=descent) 

788 

789 text_list.append(textbox._text) 

790 # Create the artist for the legend which represents the 

791 # original artist/handle. 

792 handle_list.append(handler.legend_artist(self, orig_handle, 

793 fontsize, handlebox)) 

794 handles_and_labels.append((handlebox, textbox)) 

795 

796 if handles_and_labels: 

797 # We calculate number of rows in each column. The first 

798 # (num_largecol) columns will have (nrows+1) rows, and remaining 

799 # (num_smallcol) columns will have (nrows) rows. 

800 ncol = min(self._ncol, len(handles_and_labels)) 

801 nrows, num_largecol = divmod(len(handles_and_labels), ncol) 

802 num_smallcol = ncol - num_largecol 

803 # starting index of each column and number of rows in it. 

804 rows_per_col = [nrows + 1] * num_largecol + [nrows] * num_smallcol 

805 start_idxs = np.concatenate([[0], np.cumsum(rows_per_col)[:-1]]) 

806 cols = zip(start_idxs, rows_per_col) 

807 else: 

808 cols = [] 

809 

810 columnbox = [] 

811 for i0, di in cols: 

812 # pack handleBox and labelBox into itemBox 

813 itemBoxes = [HPacker(pad=0, 

814 sep=self.handletextpad * fontsize, 

815 children=[h, t] if markerfirst else [t, h], 

816 align="baseline") 

817 for h, t in handles_and_labels[i0:i0 + di]] 

818 # minimumdescent=False for the text of the last row of the column 

819 if markerfirst: 

820 itemBoxes[-1].get_children()[1].set_minimumdescent(False) 

821 else: 

822 itemBoxes[-1].get_children()[0].set_minimumdescent(False) 

823 

824 # pack columnBox 

825 alignment = "baseline" if markerfirst else "right" 

826 columnbox.append(VPacker(pad=0, 

827 sep=self.labelspacing * fontsize, 

828 align=alignment, 

829 children=itemBoxes)) 

830 

831 mode = "expand" if self._mode == "expand" else "fixed" 

832 sep = self.columnspacing * fontsize 

833 self._legend_handle_box = HPacker(pad=0, 

834 sep=sep, align="baseline", 

835 mode=mode, 

836 children=columnbox) 

837 self._legend_title_box = TextArea("") 

838 self._legend_box = VPacker(pad=self.borderpad * fontsize, 

839 sep=self.labelspacing * fontsize, 

840 align="center", 

841 children=[self._legend_title_box, 

842 self._legend_handle_box]) 

843 self._legend_box.set_figure(self.figure) 

844 self.texts = text_list 

845 self.legendHandles = handle_list 

846 

847 def _auto_legend_data(self): 

848 """ 

849 Returns list of vertices and extents covered by the plot. 

850 

851 Returns a two long list. 

852 

853 First element is a list of (x, y) vertices (in 

854 display-coordinates) covered by all the lines and line 

855 collections, in the legend's handles. 

856 

857 Second element is a list of bounding boxes for all the patches in 

858 the legend's handles. 

859 """ 

860 # should always hold because function is only called internally 

861 assert self.isaxes 

862 

863 ax = self.parent 

864 bboxes = [] 

865 lines = [] 

866 offsets = [] 

867 

868 for handle in ax.lines: 

869 assert isinstance(handle, Line2D) 

870 path = handle.get_path() 

871 trans = handle.get_transform() 

872 tpath = trans.transform_path(path) 

873 lines.append(tpath) 

874 

875 for handle in ax.patches: 

876 assert isinstance(handle, Patch) 

877 

878 if isinstance(handle, Rectangle): 

879 transform = handle.get_data_transform() 

880 bboxes.append(handle.get_bbox().transformed(transform)) 

881 else: 

882 transform = handle.get_transform() 

883 bboxes.append(handle.get_path().get_extents(transform)) 

884 

885 for handle in ax.collections: 

886 transform, transOffset, hoffsets, paths = handle._prepare_points() 

887 

888 if len(hoffsets): 

889 for offset in transOffset.transform(hoffsets): 

890 offsets.append(offset) 

891 

892 try: 

893 vertices = np.concatenate([l.vertices for l in lines]) 

894 except ValueError: 

895 vertices = np.array([]) 

896 

897 return [vertices, bboxes, lines, offsets] 

898 

899 def draw_frame(self, b): 

900 ''' 

901 Set draw frame to b. 

902 

903 Parameters 

904 ---------- 

905 b : bool 

906 ''' 

907 self.set_frame_on(b) 

908 

909 def get_children(self): 

910 'Return a list of child artists.' 

911 children = [] 

912 if self._legend_box: 

913 children.append(self._legend_box) 

914 children.append(self.get_frame()) 

915 

916 return children 

917 

918 def get_frame(self): 

919 ''' 

920 Return the `~.patches.Rectangle` instances used to frame the legend. 

921 ''' 

922 return self.legendPatch 

923 

924 def get_lines(self): 

925 'Return a list of `~.lines.Line2D` instances in the legend.' 

926 return [h for h in self.legendHandles if isinstance(h, Line2D)] 

927 

928 def get_patches(self): 

929 'Return a list of `~.patches.Patch` instances in the legend.' 

930 return silent_list('Patch', 

931 [h for h in self.legendHandles 

932 if isinstance(h, Patch)]) 

933 

934 def get_texts(self): 

935 'Return a list of `~.text.Text` instances in the legend.' 

936 return silent_list('Text', self.texts) 

937 

938 def set_title(self, title, prop=None): 

939 """ 

940 Set the legend title. Fontproperties can be optionally set 

941 with *prop* parameter. 

942 """ 

943 self._legend_title_box._text.set_text(title) 

944 if title: 

945 self._legend_title_box._text.set_visible(True) 

946 self._legend_title_box.set_visible(True) 

947 else: 

948 self._legend_title_box._text.set_visible(False) 

949 self._legend_title_box.set_visible(False) 

950 

951 if prop is not None: 

952 if isinstance(prop, dict): 

953 prop = FontProperties(**prop) 

954 self._legend_title_box._text.set_fontproperties(prop) 

955 

956 self.stale = True 

957 

958 def get_title(self): 

959 'Return the `.Text` instance for the legend title.' 

960 return self._legend_title_box._text 

961 

962 def get_window_extent(self, renderer=None): 

963 'Return extent of the legend.' 

964 if renderer is None: 

965 renderer = self.figure._cachedRenderer 

966 return self._legend_box.get_window_extent(renderer=renderer) 

967 

968 def get_tightbbox(self, renderer): 

969 """ 

970 Like `.Legend.get_window_extent`, but uses the box for the legend. 

971 

972 Parameters 

973 ---------- 

974 renderer : `.RendererBase` instance 

975 renderer that will be used to draw the figures (i.e. 

976 ``fig.canvas.get_renderer()``) 

977 

978 Returns 

979 ------- 

980 `.BboxBase` : containing the bounding box in figure pixel co-ordinates. 

981 """ 

982 return self._legend_box.get_window_extent(renderer) 

983 

984 def get_frame_on(self): 

985 """Get whether the legend box patch is drawn.""" 

986 return self._drawFrame 

987 

988 def set_frame_on(self, b): 

989 """ 

990 Set whether the legend box patch is drawn. 

991 

992 Parameters 

993 ---------- 

994 b : bool 

995 """ 

996 self._drawFrame = b 

997 self.stale = True 

998 

999 def get_bbox_to_anchor(self): 

1000 """Return the bbox that the legend will be anchored to.""" 

1001 if self._bbox_to_anchor is None: 

1002 return self.parent.bbox 

1003 else: 

1004 return self._bbox_to_anchor 

1005 

1006 def set_bbox_to_anchor(self, bbox, transform=None): 

1007 """ 

1008 Set the bbox that the legend will be anchored to. 

1009 

1010 *bbox* can be 

1011 

1012 - A `.BboxBase` instance 

1013 - A tuple of ``(left, bottom, width, height)`` in the given transform 

1014 (normalized axes coordinate if None) 

1015 - A tuple of ``(left, bottom)`` where the width and height will be 

1016 assumed to be zero. 

1017 """ 

1018 if bbox is None: 

1019 self._bbox_to_anchor = None 

1020 return 

1021 elif isinstance(bbox, BboxBase): 

1022 self._bbox_to_anchor = bbox 

1023 else: 

1024 try: 

1025 l = len(bbox) 

1026 except TypeError: 

1027 raise ValueError("Invalid argument for bbox : %s" % str(bbox)) 

1028 

1029 if l == 2: 

1030 bbox = [bbox[0], bbox[1], 0, 0] 

1031 

1032 self._bbox_to_anchor = Bbox.from_bounds(*bbox) 

1033 

1034 if transform is None: 

1035 transform = BboxTransformTo(self.parent.bbox) 

1036 

1037 self._bbox_to_anchor = TransformedBbox(self._bbox_to_anchor, 

1038 transform) 

1039 self.stale = True 

1040 

1041 def _get_anchored_bbox(self, loc, bbox, parentbbox, renderer): 

1042 """ 

1043 Place the *bbox* inside the *parentbbox* according to a given 

1044 location code. Return the (x, y) coordinate of the bbox. 

1045 

1046 - loc: a location code in range(1, 11). 

1047 This corresponds to the possible values for self._loc, excluding 

1048 "best". 

1049 

1050 - bbox: bbox to be placed, display coordinate units. 

1051 - parentbbox: a parent box which will contain the bbox. In 

1052 display coordinates. 

1053 """ 

1054 assert loc in range(1, 11) # called only internally 

1055 

1056 BEST, UR, UL, LL, LR, R, CL, CR, LC, UC, C = range(11) 

1057 

1058 anchor_coefs = {UR: "NE", 

1059 UL: "NW", 

1060 LL: "SW", 

1061 LR: "SE", 

1062 R: "E", 

1063 CL: "W", 

1064 CR: "E", 

1065 LC: "S", 

1066 UC: "N", 

1067 C: "C"} 

1068 

1069 c = anchor_coefs[loc] 

1070 

1071 fontsize = renderer.points_to_pixels(self._fontsize) 

1072 container = parentbbox.padded(-(self.borderaxespad) * fontsize) 

1073 anchored_box = bbox.anchored(c, container=container) 

1074 return anchored_box.x0, anchored_box.y0 

1075 

1076 def _find_best_position(self, width, height, renderer, consider=None): 

1077 """ 

1078 Determine the best location to place the legend. 

1079 

1080 *consider* is a list of ``(x, y)`` pairs to consider as a potential 

1081 lower-left corner of the legend. All are display coords. 

1082 """ 

1083 # should always hold because function is only called internally 

1084 assert self.isaxes 

1085 

1086 start_time = time.perf_counter() 

1087 

1088 verts, bboxes, lines, offsets = self._auto_legend_data() 

1089 

1090 bbox = Bbox.from_bounds(0, 0, width, height) 

1091 if consider is None: 

1092 consider = [self._get_anchored_bbox(x, bbox, 

1093 self.get_bbox_to_anchor(), 

1094 renderer) 

1095 for x in range(1, len(self.codes))] 

1096 

1097 candidates = [] 

1098 for idx, (l, b) in enumerate(consider): 

1099 legendBox = Bbox.from_bounds(l, b, width, height) 

1100 badness = 0 

1101 # XXX TODO: If markers are present, it would be good to 

1102 # take them into account when checking vertex overlaps in 

1103 # the next line. 

1104 badness = (legendBox.count_contains(verts) 

1105 + legendBox.count_contains(offsets) 

1106 + legendBox.count_overlaps(bboxes) 

1107 + sum(line.intersects_bbox(legendBox, filled=False) 

1108 for line in lines)) 

1109 if badness == 0: 

1110 return l, b 

1111 # Include the index to favor lower codes in case of a tie. 

1112 candidates.append((badness, idx, (l, b))) 

1113 

1114 _, _, (l, b) = min(candidates) 

1115 

1116 if self._loc_used_default and time.perf_counter() - start_time > 1: 

1117 cbook._warn_external( 

1118 'Creating legend with loc="best" can be slow with large ' 

1119 'amounts of data.') 

1120 

1121 return l, b 

1122 

1123 def contains(self, event): 

1124 inside, info = self._default_contains(event) 

1125 if inside is not None: 

1126 return inside, info 

1127 return self.legendPatch.contains(event) 

1128 

1129 def set_draggable(self, state, use_blit=False, update='loc'): 

1130 """ 

1131 Enable or disable mouse dragging support of the legend. 

1132 

1133 Parameters 

1134 ---------- 

1135 state : bool 

1136 Whether mouse dragging is enabled. 

1137 use_blit : bool, optional 

1138 Use blitting for faster image composition. For details see 

1139 :ref:`func-animation`. 

1140 update : {'loc', 'bbox'}, optional 

1141 The legend parameter to be changed when dragged: 

1142 

1143 - 'loc': update the *loc* parameter of the legend 

1144 - 'bbox': update the *bbox_to_anchor* parameter of the legend 

1145 

1146 Returns 

1147 ------- 

1148 If *state* is ``True`` this returns the `~.DraggableLegend` helper 

1149 instance. Otherwise this returns ``None``. 

1150 """ 

1151 if state: 

1152 if self._draggable is None: 

1153 self._draggable = DraggableLegend(self, 

1154 use_blit, 

1155 update=update) 

1156 else: 

1157 if self._draggable is not None: 

1158 self._draggable.disconnect() 

1159 self._draggable = None 

1160 return self._draggable 

1161 

1162 def get_draggable(self): 

1163 """Return ``True`` if the legend is draggable, ``False`` otherwise.""" 

1164 return self._draggable is not None 

1165 

1166 

1167# Helper functions to parse legend arguments for both `figure.legend` and 

1168# `axes.legend`: 

1169def _get_legend_handles(axs, legend_handler_map=None): 

1170 """ 

1171 Return a generator of artists that can be used as handles in 

1172 a legend. 

1173 

1174 """ 

1175 handles_original = [] 

1176 for ax in axs: 

1177 handles_original += (ax.lines + ax.patches + 

1178 ax.collections + ax.containers) 

1179 # support parasite axes: 

1180 if hasattr(ax, 'parasites'): 

1181 for axx in ax.parasites: 

1182 handles_original += (axx.lines + axx.patches + 

1183 axx.collections + axx.containers) 

1184 

1185 handler_map = Legend.get_default_handler_map() 

1186 

1187 if legend_handler_map is not None: 

1188 handler_map = handler_map.copy() 

1189 handler_map.update(legend_handler_map) 

1190 

1191 has_handler = Legend.get_legend_handler 

1192 

1193 for handle in handles_original: 

1194 label = handle.get_label() 

1195 if label != '_nolegend_' and has_handler(handler_map, handle): 

1196 yield handle 

1197 

1198 

1199def _get_legend_handles_labels(axs, legend_handler_map=None): 

1200 """ 

1201 Return handles and labels for legend, internal method. 

1202 

1203 """ 

1204 handles = [] 

1205 labels = [] 

1206 

1207 for handle in _get_legend_handles(axs, legend_handler_map): 

1208 label = handle.get_label() 

1209 if label and not label.startswith('_'): 

1210 handles.append(handle) 

1211 labels.append(label) 

1212 return handles, labels 

1213 

1214 

1215def _parse_legend_args(axs, *args, handles=None, labels=None, **kwargs): 

1216 """ 

1217 Get the handles and labels from the calls to either ``figure.legend`` 

1218 or ``axes.legend``. 

1219 

1220 ``axs`` is a list of axes (to get legend artists from) 

1221 """ 

1222 log = logging.getLogger(__name__) 

1223 

1224 handlers = kwargs.get('handler_map', {}) or {} 

1225 extra_args = () 

1226 

1227 if (handles is not None or labels is not None) and args: 

1228 cbook._warn_external("You have mixed positional and keyword " 

1229 "arguments, some input may be discarded.") 

1230 

1231 # if got both handles and labels as kwargs, make same length 

1232 if handles and labels: 

1233 handles, labels = zip(*zip(handles, labels)) 

1234 

1235 elif handles is not None and labels is None: 

1236 labels = [handle.get_label() for handle in handles] 

1237 

1238 elif labels is not None and handles is None: 

1239 # Get as many handles as there are labels. 

1240 handles = [handle for handle, label 

1241 in zip(_get_legend_handles(axs, handlers), labels)] 

1242 

1243 # No arguments - automatically detect labels and handles. 

1244 elif len(args) == 0: 

1245 handles, labels = _get_legend_handles_labels(axs, handlers) 

1246 if not handles: 

1247 log.warning('No handles with labels found to put in legend.') 

1248 

1249 # One argument. User defined labels - automatic handle detection. 

1250 elif len(args) == 1: 

1251 labels, = args 

1252 # Get as many handles as there are labels. 

1253 handles = [handle for handle, label 

1254 in zip(_get_legend_handles(axs, handlers), labels)] 

1255 

1256 # Two arguments: 

1257 # * user defined handles and labels 

1258 elif len(args) >= 2: 

1259 handles, labels = args[:2] 

1260 extra_args = args[2:] 

1261 

1262 else: 

1263 raise TypeError('Invalid arguments to legend.') 

1264 

1265 return handles, labels, extra_args, kwargs