Coverage for /usr/lib/python3/dist-packages/matplotlib/legend.py: 17%

434 statements  

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

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 manually. 

8 Most users would normally create a legend via the `~.Axes.legend` 

9 function. For more details on legends there is also a :doc:`legend guide 

10 </tutorials/intermediate/legend_guide>`. 

11 

12The `Legend` class is a container of legend handles and legend texts. 

13 

14The legend handler map specifies how to create legend handles from artists 

15(lines, patches, etc.) in the axes or figures. Default legend handlers are 

16defined in the :mod:`~matplotlib.legend_handler` module. While not all artist 

17types are covered by the default legend handlers, custom legend handlers can be 

18defined to support arbitrary objects. 

19 

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

21information. 

22""" 

23 

24import itertools 

25import logging 

26import time 

27 

28import numpy as np 

29 

30import matplotlib as mpl 

31from matplotlib import _api, _docstring, colors, offsetbox 

32from matplotlib.artist import Artist, allow_rasterization 

33from matplotlib.cbook import silent_list 

34from matplotlib.font_manager import FontProperties 

35from matplotlib.lines import Line2D 

36from matplotlib.patches import (Patch, Rectangle, Shadow, FancyBboxPatch, 

37 StepPatch) 

38from matplotlib.collections import ( 

39 Collection, CircleCollection, LineCollection, PathCollection, 

40 PolyCollection, RegularPolyCollection) 

41from matplotlib.text import Text 

42from matplotlib.transforms import Bbox, BboxBase, TransformedBbox 

43from matplotlib.transforms import BboxTransformTo, BboxTransformFrom 

44from matplotlib.offsetbox import ( 

45 AnchoredOffsetbox, DraggableOffsetBox, 

46 HPacker, VPacker, 

47 DrawingArea, TextArea, 

48) 

49from matplotlib.container import ErrorbarContainer, BarContainer, StemContainer 

50from . import legend_handler 

51 

52 

53class DraggableLegend(DraggableOffsetBox): 

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

55 """ 

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

57 

58 Parameters 

59 ---------- 

60 legend : `.Legend` 

61 The `.Legend` instance to wrap. 

62 use_blit : bool, optional 

63 Use blitting for faster image composition. For details see 

64 :ref:`func-animation`. 

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

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

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

68 """ 

69 self.legend = legend 

70 

71 _api.check_in_list(["loc", "bbox"], update=update) 

72 self._update = update 

73 

74 super().__init__(legend, legend._legend_box, use_blit=use_blit) 

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_docstring.interpd.update(_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 

165ncols : int, default: 1 

166 The number of columns that the legend has. 

167 

168 For backward compatibility, the spelling *ncol* is also supported 

169 but it is discouraged. If both are given, *ncols* takes precedence. 

170 

171prop : None or `matplotlib.font_manager.FontProperties` or dict 

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

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

174 

175fontsize : int or {'xx-small', 'x-small', 'small', 'medium', 'large', \ 

176'x-large', 'xx-large'} 

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

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

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

180 

181labelcolor : str or list, default: :rc:`legend.labelcolor` 

182 The color of the text in the legend. Either a valid color string 

183 (for example, 'red'), or a list of color strings. The labelcolor can 

184 also be made to match the color of the line or marker using 'linecolor', 

185 'markerfacecolor' (or 'mfc'), or 'markeredgecolor' (or 'mec'). 

186 

187 Labelcolor can be set globally using :rc:`legend.labelcolor`. If None, 

188 use :rc:`text.color`. 

189 

190numpoints : int, default: :rc:`legend.numpoints` 

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

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

193 

194scatterpoints : int, default: :rc:`legend.scatterpoints` 

195 The number of marker points in the legend when creating 

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

197 

198scatteryoffsets : iterable of floats, default: ``[0.375, 0.5, 0.3125]`` 

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

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

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

202 same height, set to ``[0.5]``. 

203 

204markerscale : float, default: :rc:`legend.markerscale` 

205 The relative size of legend markers compared with the originally 

206 drawn ones. 

207 

208markerfirst : bool, default: True 

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

210 If *False*, legend marker is placed to the right of the legend label. 

211 

212frameon : bool, default: :rc:`legend.frameon` 

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

214 

215fancybox : bool, default: :rc:`legend.fancybox` 

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

217 makes up the legend's background. 

218 

219shadow : bool, default: :rc:`legend.shadow` 

220 Whether to draw a shadow behind the legend. 

221 

222framealpha : float, default: :rc:`legend.framealpha` 

223 The alpha transparency of the legend's background. 

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

225 ignored. 

226 

227facecolor : "inherit" or color, default: :rc:`legend.facecolor` 

228 The legend's background color. 

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

230 

231edgecolor : "inherit" or color, default: :rc:`legend.edgecolor` 

232 The legend's background patch edge color. 

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

234 

235mode : {"expand", None} 

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

237 expanded to fill the axes area (or *bbox_to_anchor* if defines 

238 the legend's size). 

239 

240bbox_transform : None or `matplotlib.transforms.Transform` 

241 The transform for the bounding box (*bbox_to_anchor*). For a value 

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

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

244 

245title : str or None 

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

247 

248title_fontproperties : None or `matplotlib.font_manager.FontProperties` or dict 

249 The font properties of the legend's title. If None (default), the 

250 *title_fontsize* argument will be used if present; if *title_fontsize* is 

251 also None, the current :rc:`legend.title_fontsize` will be used. 

252 

253title_fontsize : int or {'xx-small', 'x-small', 'small', 'medium', 'large', \ 

254'x-large', 'xx-large'}, default: :rc:`legend.title_fontsize` 

255 The font size of the legend's title. 

256 Note: This cannot be combined with *title_fontproperties*. If you want 

257 to set the fontsize alongside other font properties, use the *size* 

258 parameter in *title_fontproperties*. 

259 

260alignment : {'center', 'left', 'right'}, default: 'center' 

261 The alignment of the legend title and the box of entries. The entries 

262 are aligned as a single block, so that markers always lined up. 

263 

264borderpad : float, default: :rc:`legend.borderpad` 

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

266 

267labelspacing : float, default: :rc:`legend.labelspacing` 

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

269 

270handlelength : float, default: :rc:`legend.handlelength` 

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

272 

273handleheight : float, default: :rc:`legend.handleheight` 

274 The height of the legend handles, in font-size units. 

275 

276handletextpad : float, default: :rc:`legend.handletextpad` 

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

278 

279borderaxespad : float, default: :rc:`legend.borderaxespad` 

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

281 

282columnspacing : float, default: :rc:`legend.columnspacing` 

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

284 

285handler_map : dict or None 

286 The custom dictionary mapping instances or types to a legend 

287 handler. This *handler_map* updates the default handler map 

288 found at `matplotlib.legend.Legend.get_legend_handler_map`. 

289""") 

290 

291 

292class Legend(Artist): 

293 """ 

294 Place a legend on the axes at location loc. 

295 """ 

296 

297 # 'best' is only implemented for axes legends 

298 codes = {'best': 0, **AnchoredOffsetbox.codes} 

299 zorder = 5 

300 

301 def __str__(self): 

302 return "Legend" 

303 

304 @_api.make_keyword_only("3.6", "loc") 

305 @_docstring.dedent_interpd 

306 def __init__( 

307 self, parent, handles, labels, 

308 loc=None, 

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

310 markerscale=None, # relative size of legend markers vs. original 

311 markerfirst=True, # left/right ordering of legend marker and label 

312 scatterpoints=None, # number of scatter points 

313 scatteryoffsets=None, 

314 prop=None, # properties for the legend texts 

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

316 labelcolor=None, # keyword to set the text color 

317 

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

319 borderpad=None, # whitespace inside the legend border 

320 labelspacing=None, # vertical space between the legend entries 

321 handlelength=None, # length of the legend handles 

322 handleheight=None, # height of the legend handles 

323 handletextpad=None, # pad between the legend handle and text 

324 borderaxespad=None, # pad between the axes and legend border 

325 columnspacing=None, # spacing between columns 

326 

327 ncols=1, # number of columns 

328 mode=None, # horizontal distribution of columns: None or "expand" 

329 

330 fancybox=None, # True: fancy box, False: rounded box, None: rcParam 

331 shadow=None, 

332 title=None, # legend title 

333 title_fontsize=None, # legend title font size 

334 framealpha=None, # set frame alpha 

335 edgecolor=None, # frame patch edgecolor 

336 facecolor=None, # frame patch facecolor 

337 

338 bbox_to_anchor=None, # bbox to which the legend will be anchored 

339 bbox_transform=None, # transform for the bbox 

340 frameon=None, # draw frame 

341 handler_map=None, 

342 title_fontproperties=None, # properties for the legend title 

343 alignment="center", # control the alignment within the legend box 

344 *, 

345 ncol=1 # synonym for ncols (backward compatibility) 

346 ): 

347 """ 

348 Parameters 

349 ---------- 

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

351 The artist that contains the legend. 

352 

353 handles : list of `.Artist` 

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

355 

356 labels : list of str 

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

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

359 to the smaller of both lengths. 

360 

361 Other Parameters 

362 ---------------- 

363 %(_legend_kw_doc)s 

364 

365 Notes 

366 ----- 

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

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

369 `.BboxBase` (or derived there from) or a tuple of 2 or 4 floats. 

370 See `set_bbox_to_anchor` for more detail. 

371 

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

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

374 in the normalized axes coordinate. 

375 """ 

376 # local import only to avoid circularity 

377 from matplotlib.axes import Axes 

378 from matplotlib.figure import FigureBase 

379 

380 super().__init__() 

381 

382 if prop is None: 

383 if fontsize is not None: 

384 self.prop = FontProperties(size=fontsize) 

385 else: 

386 self.prop = FontProperties( 

387 size=mpl.rcParams["legend.fontsize"]) 

388 else: 

389 self.prop = FontProperties._from_any(prop) 

390 if isinstance(prop, dict) and "size" not in prop: 

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

392 

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

394 

395 self.texts = [] 

396 self.legendHandles = [] 

397 self._legend_title_box = None 

398 

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

400 #: instance. 

401 self._custom_handler_map = handler_map 

402 

403 def val_or_rc(val, rc_name): 

404 return val if val is not None else mpl.rcParams[rc_name] 

405 

406 self.numpoints = val_or_rc(numpoints, 'legend.numpoints') 

407 self.markerscale = val_or_rc(markerscale, 'legend.markerscale') 

408 self.scatterpoints = val_or_rc(scatterpoints, 'legend.scatterpoints') 

409 self.borderpad = val_or_rc(borderpad, 'legend.borderpad') 

410 self.labelspacing = val_or_rc(labelspacing, 'legend.labelspacing') 

411 self.handlelength = val_or_rc(handlelength, 'legend.handlelength') 

412 self.handleheight = val_or_rc(handleheight, 'legend.handleheight') 

413 self.handletextpad = val_or_rc(handletextpad, 'legend.handletextpad') 

414 self.borderaxespad = val_or_rc(borderaxespad, 'legend.borderaxespad') 

415 self.columnspacing = val_or_rc(columnspacing, 'legend.columnspacing') 

416 self.shadow = val_or_rc(shadow, 'legend.shadow') 

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 _api.warn_external(f"The label {label!r} of {handle!r} starts " 

422 "with '_'. It is thus excluded from the " 

423 "legend.") 

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 ncols = 1 

432 self._ncols = ncols if ncols != 1 else 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 a VPacker 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, FigureBase): 

456 self.isaxes = False 

457 self.set_figure(parent) 

458 else: 

459 raise TypeError( 

460 "Legend needs either Axes or FigureBase as parent" 

461 ) 

462 self.parent = parent 

463 

464 self._loc_used_default = loc is None 

465 if loc is None: 

466 loc = mpl.rcParams["legend.loc"] 

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

468 loc = 'upper right' 

469 if isinstance(loc, str): 

470 loc = _api.check_getitem(self.codes, loc=loc) 

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

472 raise ValueError( 

473 "Automatic legend placement (loc='best') not implemented for " 

474 "figure legend") 

475 

476 self._mode = mode 

477 self.set_bbox_to_anchor(bbox_to_anchor, bbox_transform) 

478 

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

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

481 

482 if facecolor is None: 

483 facecolor = mpl.rcParams["legend.facecolor"] 

484 if facecolor == 'inherit': 

485 facecolor = mpl.rcParams["axes.facecolor"] 

486 

487 if edgecolor is None: 

488 edgecolor = mpl.rcParams["legend.edgecolor"] 

489 if edgecolor == 'inherit': 

490 edgecolor = mpl.rcParams["axes.edgecolor"] 

491 

492 if fancybox is None: 

493 fancybox = mpl.rcParams["legend.fancybox"] 

494 

495 self.legendPatch = FancyBboxPatch( 

496 xy=(0, 0), width=1, height=1, 

497 facecolor=facecolor, edgecolor=edgecolor, 

498 # If shadow is used, default to alpha=1 (#8943). 

499 alpha=(framealpha if framealpha is not None 

500 else 1 if shadow 

501 else mpl.rcParams["legend.framealpha"]), 

502 # The width and height of the legendPatch will be set (in draw()) 

503 # to the length that includes the padding. Thus we set pad=0 here. 

504 boxstyle=("round,pad=0,rounding_size=0.2" if fancybox 

505 else "square,pad=0"), 

506 mutation_scale=self._fontsize, 

507 snap=True, 

508 visible=(frameon if frameon is not None 

509 else mpl.rcParams["legend.frameon"]) 

510 ) 

511 self._set_artist_props(self.legendPatch) 

512 

513 _api.check_in_list(["center", "left", "right"], alignment=alignment) 

514 self._alignment = alignment 

515 

516 # init with null renderer 

517 self._init_legend_box(handles, labels, markerfirst) 

518 

519 tmp = self._loc_used_default 

520 self._set_loc(loc) 

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

522 

523 # figure out title font properties: 

524 if title_fontsize is not None and title_fontproperties is not None: 

525 raise ValueError( 

526 "title_fontsize and title_fontproperties can't be specified " 

527 "at the same time. Only use one of them. ") 

528 title_prop_fp = FontProperties._from_any(title_fontproperties) 

529 if isinstance(title_fontproperties, dict): 

530 if "size" not in title_fontproperties: 

531 title_fontsize = mpl.rcParams["legend.title_fontsize"] 

532 title_prop_fp.set_size(title_fontsize) 

533 elif title_fontsize is not None: 

534 title_prop_fp.set_size(title_fontsize) 

535 elif not isinstance(title_fontproperties, FontProperties): 

536 title_fontsize = mpl.rcParams["legend.title_fontsize"] 

537 title_prop_fp.set_size(title_fontsize) 

538 

539 self.set_title(title, prop=title_prop_fp) 

540 self._draggable = None 

541 

542 # set the text color 

543 

544 color_getters = { # getter function depends on line or patch 

545 'linecolor': ['get_color', 'get_facecolor'], 

546 'markerfacecolor': ['get_markerfacecolor', 'get_facecolor'], 

547 'mfc': ['get_markerfacecolor', 'get_facecolor'], 

548 'markeredgecolor': ['get_markeredgecolor', 'get_edgecolor'], 

549 'mec': ['get_markeredgecolor', 'get_edgecolor'], 

550 } 

551 if labelcolor is None: 

552 if mpl.rcParams['legend.labelcolor'] is not None: 

553 labelcolor = mpl.rcParams['legend.labelcolor'] 

554 else: 

555 labelcolor = mpl.rcParams['text.color'] 

556 if isinstance(labelcolor, str) and labelcolor in color_getters: 

557 getter_names = color_getters[labelcolor] 

558 for handle, text in zip(self.legendHandles, self.texts): 

559 for getter_name in getter_names: 

560 try: 

561 color = getattr(handle, getter_name)() 

562 text.set_color(color) 

563 break 

564 except AttributeError: 

565 pass 

566 elif isinstance(labelcolor, str) and labelcolor == 'none': 

567 for text in self.texts: 

568 text.set_color(labelcolor) 

569 elif np.iterable(labelcolor): 

570 for text, color in zip(self.texts, 

571 itertools.cycle( 

572 colors.to_rgba_array(labelcolor))): 

573 text.set_color(color) 

574 else: 

575 raise ValueError(f"Invalid labelcolor: {labelcolor!r}") 

576 

577 def _set_artist_props(self, a): 

578 """ 

579 Set the boilerplate props for artists added to axes. 

580 """ 

581 a.set_figure(self.figure) 

582 if self.isaxes: 

583 # a.set_axes(self.axes) 

584 a.axes = self.axes 

585 

586 a.set_transform(self.get_transform()) 

587 

588 def _set_loc(self, loc): 

589 # find_offset function will be provided to _legend_box and 

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

591 # value of the find_offset. 

592 self._loc_used_default = False 

593 self._loc_real = loc 

594 self.stale = True 

595 self._legend_box.set_offset(self._findoffset) 

596 

597 def set_ncols(self, ncols): 

598 """Set the number of columns.""" 

599 self._ncols = ncols 

600 

601 def _get_loc(self): 

602 return self._loc_real 

603 

604 _loc = property(_get_loc, _set_loc) 

605 

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

607 """Helper function to locate the legend.""" 

608 

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

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

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

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

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

614 self.get_bbox_to_anchor(), 

615 renderer) 

616 else: # Axes or figure coordinates. 

617 fx, fy = self._loc 

618 bbox = self.get_bbox_to_anchor() 

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

620 

621 return x + xdescent, y + ydescent 

622 

623 @allow_rasterization 

624 def draw(self, renderer): 

625 # docstring inherited 

626 if not self.get_visible(): 

627 return 

628 

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

630 

631 fontsize = renderer.points_to_pixels(self._fontsize) 

632 

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

634 # width of the parent (minus pads) 

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

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

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

638 

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

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

641 bbox = self._legend_box.get_window_extent(renderer) 

642 self.legendPatch.set_bounds(bbox.bounds) 

643 self.legendPatch.set_mutation_scale(fontsize) 

644 

645 if self.shadow: 

646 Shadow(self.legendPatch, 2, -2).draw(renderer) 

647 

648 self.legendPatch.draw(renderer) 

649 self._legend_box.draw(renderer) 

650 

651 renderer.close_group('legend') 

652 self.stale = False 

653 

654 # _default_handler_map defines the default mapping between plot 

655 # elements and the legend handlers. 

656 

657 _default_handler_map = { 

658 StemContainer: legend_handler.HandlerStem(), 

659 ErrorbarContainer: legend_handler.HandlerErrorbar(), 

660 Line2D: legend_handler.HandlerLine2D(), 

661 Patch: legend_handler.HandlerPatch(), 

662 StepPatch: legend_handler.HandlerStepPatch(), 

663 LineCollection: legend_handler.HandlerLineCollection(), 

664 RegularPolyCollection: legend_handler.HandlerRegularPolyCollection(), 

665 CircleCollection: legend_handler.HandlerCircleCollection(), 

666 BarContainer: legend_handler.HandlerPatch( 

667 update_func=legend_handler.update_from_first_child), 

668 tuple: legend_handler.HandlerTuple(), 

669 PathCollection: legend_handler.HandlerPathCollection(), 

670 PolyCollection: legend_handler.HandlerPolyCollection() 

671 } 

672 

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

674 # modify the default handler map. 

675 

676 @classmethod 

677 def get_default_handler_map(cls): 

678 """Return the global default handler map, shared by all legends.""" 

679 return cls._default_handler_map 

680 

681 @classmethod 

682 def set_default_handler_map(cls, handler_map): 

683 """Set the global default handler map, shared by all legends.""" 

684 cls._default_handler_map = handler_map 

685 

686 @classmethod 

687 def update_default_handler_map(cls, handler_map): 

688 """Update the global default handler map, shared by all legends.""" 

689 cls._default_handler_map.update(handler_map) 

690 

691 def get_legend_handler_map(self): 

692 """Return this legend instance's handler map.""" 

693 default_handler_map = self.get_default_handler_map() 

694 return ({**default_handler_map, **self._custom_handler_map} 

695 if self._custom_handler_map else default_handler_map) 

696 

697 @staticmethod 

698 def get_legend_handler(legend_handler_map, orig_handle): 

699 """ 

700 Return a legend handler from *legend_handler_map* that 

701 corresponds to *orig_handler*. 

702 

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

704 returned by the get_legend_handler_map method). 

705 

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

707 *legend_handler_map* and return the associated value. 

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

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

710 returns ``None``. 

711 """ 

712 try: 

713 return legend_handler_map[orig_handle] 

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

715 pass 

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

717 try: 

718 return legend_handler_map[handle_type] 

719 except KeyError: 

720 pass 

721 return None 

722 

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

724 """ 

725 Initialize the legend_box. The legend_box is an instance of 

726 the OffsetBox, which is packed with legend handles and 

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

728 drawing time. 

729 """ 

730 

731 fontsize = self._fontsize 

732 

733 # legend_box is a HPacker, horizontally packed with columns. 

734 # Each column is a VPacker, vertically packed with legend items. 

735 # Each legend item is a HPacker packed with: 

736 # - handlebox: a DrawingArea which contains the legend handle. 

737 # - labelbox: a TextArea which contains the legend text. 

738 

739 text_list = [] # the list of text instances 

740 handle_list = [] # the list of handle instances 

741 handles_and_labels = [] 

742 

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

744 # only used for plotting the legend handle. 

745 descent = 0.35 * fontsize * (self.handleheight - 0.7) # heuristic. 

746 height = fontsize * self.handleheight - descent 

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

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

749 # be given in the display coordinates. 

750 

751 # The transformation of each handle will be automatically set 

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

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

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

755 legend_handler_map = self.get_legend_handler_map() 

756 

757 for orig_handle, label in zip(handles, labels): 

758 handler = self.get_legend_handler(legend_handler_map, orig_handle) 

759 if handler is None: 

760 _api.warn_external( 

761 "Legend does not support handles for {0} " 

762 "instances.\nA proxy artist may be used " 

763 "instead.\nSee: https://matplotlib.org/" 

764 "stable/tutorials/intermediate/legend_guide.html" 

765 "#controlling-the-legend-entries".format( 

766 type(orig_handle).__name__)) 

767 # No handle for this artist, so we just defer to None. 

768 handle_list.append(None) 

769 else: 

770 textbox = TextArea(label, multilinebaseline=True, 

771 textprops=dict( 

772 verticalalignment='baseline', 

773 horizontalalignment='left', 

774 fontproperties=self.prop)) 

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

776 height=height, 

777 xdescent=0., ydescent=descent) 

778 

779 text_list.append(textbox._text) 

780 # Create the artist for the legend which represents the 

781 # original artist/handle. 

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

783 fontsize, handlebox)) 

784 handles_and_labels.append((handlebox, textbox)) 

785 

786 columnbox = [] 

787 # array_split splits n handles_and_labels into ncols columns, with the 

788 # first n%ncols columns having an extra entry. filter(len, ...) 

789 # handles the case where n < ncols: the last ncols-n columns are empty 

790 # and get filtered out. 

791 for handles_and_labels_column in filter( 

792 len, np.array_split(handles_and_labels, self._ncols)): 

793 # pack handlebox and labelbox into itembox 

794 itemboxes = [HPacker(pad=0, 

795 sep=self.handletextpad * fontsize, 

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

797 align="baseline") 

798 for h, t in handles_and_labels_column] 

799 # pack columnbox 

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

801 columnbox.append(VPacker(pad=0, 

802 sep=self.labelspacing * fontsize, 

803 align=alignment, 

804 children=itemboxes)) 

805 

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

807 sep = self.columnspacing * fontsize 

808 self._legend_handle_box = HPacker(pad=0, 

809 sep=sep, align="baseline", 

810 mode=mode, 

811 children=columnbox) 

812 self._legend_title_box = TextArea("") 

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

814 sep=self.labelspacing * fontsize, 

815 align=self._alignment, 

816 children=[self._legend_title_box, 

817 self._legend_handle_box]) 

818 self._legend_box.set_figure(self.figure) 

819 self._legend_box.axes = self.axes 

820 self.texts = text_list 

821 self.legendHandles = handle_list 

822 

823 def _auto_legend_data(self): 

824 """ 

825 Return display coordinates for hit testing for "best" positioning. 

826 

827 Returns 

828 ------- 

829 bboxes 

830 List of bounding boxes of all patches. 

831 lines 

832 List of `.Path` corresponding to each line. 

833 offsets 

834 List of (x, y) offsets of all collection. 

835 """ 

836 assert self.isaxes # always holds, as this is only called internally 

837 bboxes = [] 

838 lines = [] 

839 offsets = [] 

840 for artist in self.parent._children: 

841 if isinstance(artist, Line2D): 

842 lines.append( 

843 artist.get_transform().transform_path(artist.get_path())) 

844 elif isinstance(artist, Rectangle): 

845 bboxes.append( 

846 artist.get_bbox().transformed(artist.get_data_transform())) 

847 elif isinstance(artist, Patch): 

848 bboxes.append( 

849 artist.get_path().get_extents(artist.get_transform())) 

850 elif isinstance(artist, Collection): 

851 _, offset_trf, hoffsets, _ = artist._prepare_points() 

852 for offset in offset_trf.transform(hoffsets): 

853 offsets.append(offset) 

854 return bboxes, lines, offsets 

855 

856 def get_children(self): 

857 # docstring inherited 

858 return [self._legend_box, self.get_frame()] 

859 

860 def get_frame(self): 

861 """Return the `~.patches.Rectangle` used to frame the legend.""" 

862 return self.legendPatch 

863 

864 def get_lines(self): 

865 r"""Return the list of `~.lines.Line2D`\s in the legend.""" 

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

867 

868 def get_patches(self): 

869 r"""Return the list of `~.patches.Patch`\s in the legend.""" 

870 return silent_list('Patch', 

871 [h for h in self.legendHandles 

872 if isinstance(h, Patch)]) 

873 

874 def get_texts(self): 

875 r"""Return the list of `~.text.Text`\s in the legend.""" 

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

877 

878 def set_alignment(self, alignment): 

879 """ 

880 Set the alignment of the legend title and the box of entries. 

881 

882 The entries are aligned as a single block, so that markers always 

883 lined up. 

884 

885 Parameters 

886 ---------- 

887 alignment : {'center', 'left', 'right'}. 

888 

889 """ 

890 _api.check_in_list(["center", "left", "right"], alignment=alignment) 

891 self._alignment = alignment 

892 self._legend_box.align = alignment 

893 

894 def get_alignment(self): 

895 """Get the alignment value of the legend box""" 

896 return self._legend_box.align 

897 

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

899 """ 

900 Set legend title and title style. 

901 

902 Parameters 

903 ---------- 

904 title : str 

905 The legend title. 

906 

907 prop : `.font_manager.FontProperties` or `str` or `pathlib.Path` 

908 The font properties of the legend title. 

909 If a `str`, it is interpreted as a fontconfig pattern parsed by 

910 `.FontProperties`. If a `pathlib.Path`, it is interpreted as the 

911 absolute path to a font file. 

912 

913 """ 

914 self._legend_title_box._text.set_text(title) 

915 if title: 

916 self._legend_title_box._text.set_visible(True) 

917 self._legend_title_box.set_visible(True) 

918 else: 

919 self._legend_title_box._text.set_visible(False) 

920 self._legend_title_box.set_visible(False) 

921 

922 if prop is not None: 

923 self._legend_title_box._text.set_fontproperties(prop) 

924 

925 self.stale = True 

926 

927 def get_title(self): 

928 """Return the `.Text` instance for the legend title.""" 

929 return self._legend_title_box._text 

930 

931 def get_window_extent(self, renderer=None): 

932 # docstring inherited 

933 if renderer is None: 

934 renderer = self.figure._get_renderer() 

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

936 

937 def get_tightbbox(self, renderer=None): 

938 # docstring inherited 

939 return self._legend_box.get_window_extent(renderer) 

940 

941 def get_frame_on(self): 

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

943 return self.legendPatch.get_visible() 

944 

945 def set_frame_on(self, b): 

946 """ 

947 Set whether the legend box patch is drawn. 

948 

949 Parameters 

950 ---------- 

951 b : bool 

952 """ 

953 self.legendPatch.set_visible(b) 

954 self.stale = True 

955 

956 draw_frame = set_frame_on # Backcompat alias. 

957 

958 def get_bbox_to_anchor(self): 

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

960 if self._bbox_to_anchor is None: 

961 return self.parent.bbox 

962 else: 

963 return self._bbox_to_anchor 

964 

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

966 """ 

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

968 

969 Parameters 

970 ---------- 

971 bbox : `~matplotlib.transforms.BboxBase` or tuple 

972 The bounding box can be specified in the following ways: 

973 

974 - A `.BboxBase` instance 

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

976 transform (normalized axes coordinate if None) 

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

978 assumed to be zero. 

979 - *None*, to remove the bbox anchoring, and use the parent bbox. 

980 

981 transform : `~matplotlib.transforms.Transform`, optional 

982 A transform to apply to the bounding box. If not specified, this 

983 will use a transform to the bounding box of the parent. 

984 """ 

985 if bbox is None: 

986 self._bbox_to_anchor = None 

987 return 

988 elif isinstance(bbox, BboxBase): 

989 self._bbox_to_anchor = bbox 

990 else: 

991 try: 

992 l = len(bbox) 

993 except TypeError as err: 

994 raise ValueError(f"Invalid bbox: {bbox}") from err 

995 

996 if l == 2: 

997 bbox = [bbox[0], bbox[1], 0, 0] 

998 

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

1000 

1001 if transform is None: 

1002 transform = BboxTransformTo(self.parent.bbox) 

1003 

1004 self._bbox_to_anchor = TransformedBbox(self._bbox_to_anchor, 

1005 transform) 

1006 self.stale = True 

1007 

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

1009 """ 

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

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

1012 

1013 Parameters 

1014 ---------- 

1015 loc : int 

1016 A location code in range(1, 11). This corresponds to the possible 

1017 values for ``self._loc``, excluding "best". 

1018 bbox : `~matplotlib.transforms.Bbox` 

1019 bbox to be placed, in display coordinates. 

1020 parentbbox : `~matplotlib.transforms.Bbox` 

1021 A parent box which will contain the bbox, in display coordinates. 

1022 """ 

1023 return offsetbox._get_anchored_bbox( 

1024 loc, bbox, parentbbox, 

1025 self.borderaxespad * renderer.points_to_pixels(self._fontsize)) 

1026 

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

1028 """ 

1029 Determine the best location to place the legend. 

1030 

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

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

1033 """ 

1034 assert self.isaxes # always holds, as this is only called internally 

1035 

1036 start_time = time.perf_counter() 

1037 

1038 bboxes, lines, offsets = self._auto_legend_data() 

1039 

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

1041 if consider is None: 

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

1043 self.get_bbox_to_anchor(), 

1044 renderer) 

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

1046 

1047 candidates = [] 

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

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

1050 badness = 0 

1051 # XXX TODO: If markers are present, it would be good to take them 

1052 # into account when checking vertex overlaps in the next line. 

1053 badness = (sum(legendBox.count_contains(line.vertices) 

1054 for line in lines) 

1055 + legendBox.count_contains(offsets) 

1056 + legendBox.count_overlaps(bboxes) 

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

1058 for line in lines)) 

1059 if badness == 0: 

1060 return l, b 

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

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

1063 

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

1065 

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

1067 _api.warn_external( 

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

1069 'amounts of data.') 

1070 

1071 return l, b 

1072 

1073 def contains(self, event): 

1074 inside, info = self._default_contains(event) 

1075 if inside is not None: 

1076 return inside, info 

1077 return self.legendPatch.contains(event) 

1078 

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

1080 """ 

1081 Enable or disable mouse dragging support of the legend. 

1082 

1083 Parameters 

1084 ---------- 

1085 state : bool 

1086 Whether mouse dragging is enabled. 

1087 use_blit : bool, optional 

1088 Use blitting for faster image composition. For details see 

1089 :ref:`func-animation`. 

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

1091 The legend parameter to be changed when dragged: 

1092 

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

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

1095 

1096 Returns 

1097 ------- 

1098 `.DraggableLegend` or *None* 

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

1100 instance. Otherwise this returns *None*. 

1101 """ 

1102 if state: 

1103 if self._draggable is None: 

1104 self._draggable = DraggableLegend(self, 

1105 use_blit, 

1106 update=update) 

1107 else: 

1108 if self._draggable is not None: 

1109 self._draggable.disconnect() 

1110 self._draggable = None 

1111 return self._draggable 

1112 

1113 def get_draggable(self): 

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

1115 return self._draggable is not None 

1116 

1117 

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

1119# `axes.legend`: 

1120def _get_legend_handles(axs, legend_handler_map=None): 

1121 """Yield artists that can be used as handles in a legend.""" 

1122 handles_original = [] 

1123 for ax in axs: 

1124 handles_original += [ 

1125 *(a for a in ax._children 

1126 if isinstance(a, (Line2D, Patch, Collection, Text))), 

1127 *ax.containers] 

1128 # support parasite axes: 

1129 if hasattr(ax, 'parasites'): 

1130 for axx in ax.parasites: 

1131 handles_original += [ 

1132 *(a for a in axx._children 

1133 if isinstance(a, (Line2D, Patch, Collection, Text))), 

1134 *axx.containers] 

1135 

1136 handler_map = {**Legend.get_default_handler_map(), 

1137 **(legend_handler_map or {})} 

1138 has_handler = Legend.get_legend_handler 

1139 for handle in handles_original: 

1140 label = handle.get_label() 

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

1142 yield handle 

1143 elif (label and not label.startswith('_') and 

1144 not has_handler(handler_map, handle)): 

1145 _api.warn_external( 

1146 "Legend does not support handles for {0} " 

1147 "instances.\nSee: https://matplotlib.org/stable/" 

1148 "tutorials/intermediate/legend_guide.html" 

1149 "#implementing-a-custom-legend-handler".format( 

1150 type(handle).__name__)) 

1151 continue 

1152 

1153 

1154def _get_legend_handles_labels(axs, legend_handler_map=None): 

1155 """Return handles and labels for legend.""" 

1156 handles = [] 

1157 labels = [] 

1158 for handle in _get_legend_handles(axs, legend_handler_map): 

1159 label = handle.get_label() 

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

1161 handles.append(handle) 

1162 labels.append(label) 

1163 return handles, labels 

1164 

1165 

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

1167 """ 

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

1169 or ``axes.legend``. 

1170 

1171 The parser is a bit involved because we support:: 

1172 

1173 legend() 

1174 legend(labels) 

1175 legend(handles, labels) 

1176 legend(labels=labels) 

1177 legend(handles=handles) 

1178 legend(handles=handles, labels=labels) 

1179 

1180 The behavior for a mixture of positional and keyword handles and labels 

1181 is undefined and issues a warning. 

1182 

1183 Parameters 

1184 ---------- 

1185 axs : list of `.Axes` 

1186 If handles are not given explicitly, the artists in these Axes are 

1187 used as handles. 

1188 *args : tuple 

1189 Positional parameters passed to ``legend()``. 

1190 handles 

1191 The value of the keyword argument ``legend(handles=...)``, or *None* 

1192 if that keyword argument was not used. 

1193 labels 

1194 The value of the keyword argument ``legend(labels=...)``, or *None* 

1195 if that keyword argument was not used. 

1196 **kwargs 

1197 All other keyword arguments passed to ``legend()``. 

1198 

1199 Returns 

1200 ------- 

1201 handles : list of `.Artist` 

1202 The legend handles. 

1203 labels : list of str 

1204 The legend labels. 

1205 extra_args : tuple 

1206 *args* with positional handles and labels removed. 

1207 kwargs : dict 

1208 *kwargs* with keywords handles and labels removed. 

1209 

1210 """ 

1211 log = logging.getLogger(__name__) 

1212 

1213 handlers = kwargs.get('handler_map') 

1214 extra_args = () 

1215 

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

1217 _api.warn_external("You have mixed positional and keyword arguments, " 

1218 "some input may be discarded.") 

1219 

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

1221 if handles and labels: 

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

1223 

1224 elif handles is not None and labels is None: 

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

1226 

1227 elif labels is not None and handles is None: 

1228 # Get as many handles as there are labels. 

1229 handles = [handle for handle, label 

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

1231 

1232 # No arguments - automatically detect labels and handles. 

1233 elif len(args) == 0: 

1234 handles, labels = _get_legend_handles_labels(axs, handlers) 

1235 if not handles: 

1236 log.warning( 

1237 "No artists with labels found to put in legend. Note that " 

1238 "artists whose label start with an underscore are ignored " 

1239 "when legend() is called with no argument.") 

1240 

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

1242 elif len(args) == 1: 

1243 labels, = args 

1244 if any(isinstance(l, Artist) for l in labels): 

1245 raise TypeError("A single argument passed to legend() must be a " 

1246 "list of labels, but found an Artist in there.") 

1247 

1248 # Get as many handles as there are labels. 

1249 handles = [handle for handle, label 

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

1251 

1252 # Two arguments: 

1253 # * user defined handles and labels 

1254 elif len(args) >= 2: 

1255 handles, labels = args[:2] 

1256 extra_args = args[2:] 

1257 

1258 else: 

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

1260 

1261 return handles, labels, extra_args, kwargs