Coverage for /usr/lib/python3/dist-packages/matplotlib/artist.py: 33%

652 statements  

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

1from collections import namedtuple 

2import contextlib 

3from functools import lru_cache, wraps 

4import inspect 

5from inspect import Signature, Parameter 

6import logging 

7from numbers import Number 

8import re 

9import warnings 

10 

11import numpy as np 

12 

13import matplotlib as mpl 

14from . import _api, cbook 

15from .colors import BoundaryNorm 

16from .cm import ScalarMappable 

17from .path import Path 

18from .transforms import (Bbox, IdentityTransform, Transform, TransformedBbox, 

19 TransformedPatchPath, TransformedPath) 

20 

21_log = logging.getLogger(__name__) 

22 

23 

24def allow_rasterization(draw): 

25 """ 

26 Decorator for Artist.draw method. Provides routines 

27 that run before and after the draw call. The before and after functions 

28 are useful for changing artist-dependent renderer attributes or making 

29 other setup function calls, such as starting and flushing a mixed-mode 

30 renderer. 

31 """ 

32 

33 @wraps(draw) 

34 def draw_wrapper(artist, renderer): 

35 try: 

36 if artist.get_rasterized(): 

37 if renderer._raster_depth == 0 and not renderer._rasterizing: 

38 renderer.start_rasterizing() 

39 renderer._rasterizing = True 

40 renderer._raster_depth += 1 

41 else: 

42 if renderer._raster_depth == 0 and renderer._rasterizing: 

43 # Only stop when we are not in a rasterized parent 

44 # and something has be rasterized since last stop 

45 renderer.stop_rasterizing() 

46 renderer._rasterizing = False 

47 

48 if artist.get_agg_filter() is not None: 

49 renderer.start_filter() 

50 

51 return draw(artist, renderer) 

52 finally: 

53 if artist.get_agg_filter() is not None: 

54 renderer.stop_filter(artist.get_agg_filter()) 

55 if artist.get_rasterized(): 

56 renderer._raster_depth -= 1 

57 if (renderer._rasterizing and artist.figure and 

58 artist.figure.suppressComposite): 

59 # restart rasterizing to prevent merging 

60 renderer.stop_rasterizing() 

61 renderer.start_rasterizing() 

62 

63 draw_wrapper._supports_rasterization = True 

64 return draw_wrapper 

65 

66 

67def _finalize_rasterization(draw): 

68 """ 

69 Decorator for Artist.draw method. Needed on the outermost artist, i.e. 

70 Figure, to finish up if the render is still in rasterized mode. 

71 """ 

72 @wraps(draw) 

73 def draw_wrapper(artist, renderer, *args, **kwargs): 

74 result = draw(artist, renderer, *args, **kwargs) 

75 if renderer._rasterizing: 

76 renderer.stop_rasterizing() 

77 renderer._rasterizing = False 

78 return result 

79 return draw_wrapper 

80 

81 

82def _stale_axes_callback(self, val): 

83 if self.axes: 

84 self.axes.stale = val 

85 

86 

87_XYPair = namedtuple("_XYPair", "x y") 

88 

89 

90class _Unset: 

91 def __repr__(self): 

92 return "<UNSET>" 

93_UNSET = _Unset() 

94 

95 

96class Artist: 

97 """ 

98 Abstract base class for objects that render into a FigureCanvas. 

99 

100 Typically, all visible elements in a figure are subclasses of Artist. 

101 """ 

102 

103 zorder = 0 

104 

105 def __init_subclass__(cls): 

106 # Inject custom set() methods into the subclass with signature and 

107 # docstring based on the subclasses' properties. 

108 

109 if not hasattr(cls.set, '_autogenerated_signature'): 

110 # Don't overwrite cls.set if the subclass or one of its parents 

111 # has defined a set method set itself. 

112 # If there was no explicit definition, cls.set is inherited from 

113 # the hierarchy of auto-generated set methods, which hold the 

114 # flag _autogenerated_signature. 

115 return 

116 

117 cls.set = lambda self, **kwargs: Artist.set(self, **kwargs) 

118 cls.set.__name__ = "set" 

119 cls.set.__qualname__ = f"{cls.__qualname__}.set" 

120 cls._update_set_signature_and_docstring() 

121 

122 _PROPERTIES_EXCLUDED_FROM_SET = [ 

123 'navigate_mode', # not a user-facing function 

124 'figure', # changing the figure is such a profound operation 

125 # that we don't want this in set() 

126 '3d_properties', # cannot be used as a keyword due to leading digit 

127 ] 

128 

129 @classmethod 

130 def _update_set_signature_and_docstring(cls): 

131 """ 

132 Update the signature of the set function to list all properties 

133 as keyword arguments. 

134 

135 Property aliases are not listed in the signature for brevity, but 

136 are still accepted as keyword arguments. 

137 """ 

138 cls.set.__signature__ = Signature( 

139 [Parameter("self", Parameter.POSITIONAL_OR_KEYWORD), 

140 *[Parameter(prop, Parameter.KEYWORD_ONLY, default=_UNSET) 

141 for prop in ArtistInspector(cls).get_setters() 

142 if prop not in Artist._PROPERTIES_EXCLUDED_FROM_SET]]) 

143 cls.set._autogenerated_signature = True 

144 

145 cls.set.__doc__ = ( 

146 "Set multiple properties at once.\n\n" 

147 "Supported properties are\n\n" 

148 + kwdoc(cls)) 

149 

150 def __init__(self): 

151 self._stale = True 

152 self.stale_callback = None 

153 self._axes = None 

154 self.figure = None 

155 

156 self._transform = None 

157 self._transformSet = False 

158 self._visible = True 

159 self._animated = False 

160 self._alpha = None 

161 self.clipbox = None 

162 self._clippath = None 

163 self._clipon = True 

164 self._label = '' 

165 self._picker = None 

166 self._rasterized = False 

167 self._agg_filter = None 

168 # Normally, artist classes need to be queried for mouseover info if and 

169 # only if they override get_cursor_data. 

170 self._mouseover = type(self).get_cursor_data != Artist.get_cursor_data 

171 self._callbacks = cbook.CallbackRegistry(signals=["pchanged"]) 

172 try: 

173 self.axes = None 

174 except AttributeError: 

175 # Handle self.axes as a read-only property, as in Figure. 

176 pass 

177 self._remove_method = None 

178 self._url = None 

179 self._gid = None 

180 self._snap = None 

181 self._sketch = mpl.rcParams['path.sketch'] 

182 self._path_effects = mpl.rcParams['path.effects'] 

183 self._sticky_edges = _XYPair([], []) 

184 self._in_layout = True 

185 

186 def __getstate__(self): 

187 d = self.__dict__.copy() 

188 # remove the unpicklable remove method, this will get re-added on load 

189 # (by the Axes) if the artist lives on an Axes. 

190 d['stale_callback'] = None 

191 return d 

192 

193 def remove(self): 

194 """ 

195 Remove the artist from the figure if possible. 

196 

197 The effect will not be visible until the figure is redrawn, e.g., 

198 with `.FigureCanvasBase.draw_idle`. Call `~.axes.Axes.relim` to 

199 update the axes limits if desired. 

200 

201 Note: `~.axes.Axes.relim` will not see collections even if the 

202 collection was added to the axes with *autolim* = True. 

203 

204 Note: there is no support for removing the artist's legend entry. 

205 """ 

206 

207 # There is no method to set the callback. Instead, the parent should 

208 # set the _remove_method attribute directly. This would be a 

209 # protected attribute if Python supported that sort of thing. The 

210 # callback has one parameter, which is the child to be removed. 

211 if self._remove_method is not None: 

212 self._remove_method(self) 

213 # clear stale callback 

214 self.stale_callback = None 

215 _ax_flag = False 

216 if hasattr(self, 'axes') and self.axes: 

217 # remove from the mouse hit list 

218 self.axes._mouseover_set.discard(self) 

219 self.axes.stale = True 

220 self.axes = None # decouple the artist from the Axes 

221 _ax_flag = True 

222 

223 if self.figure: 

224 self.figure = None 

225 if not _ax_flag: 

226 self.figure = True 

227 

228 else: 

229 raise NotImplementedError('cannot remove artist') 

230 # TODO: the fix for the collections relim problem is to move the 

231 # limits calculation into the artist itself, including the property of 

232 # whether or not the artist should affect the limits. Then there will 

233 # be no distinction between axes.add_line, axes.add_patch, etc. 

234 # TODO: add legend support 

235 

236 def have_units(self): 

237 """Return whether units are set on any axis.""" 

238 ax = self.axes 

239 return ax and any(axis.have_units() for axis in ax._axis_map.values()) 

240 

241 def convert_xunits(self, x): 

242 """ 

243 Convert *x* using the unit type of the xaxis. 

244 

245 If the artist is not contained in an Axes or if the xaxis does not 

246 have units, *x* itself is returned. 

247 """ 

248 ax = getattr(self, 'axes', None) 

249 if ax is None or ax.xaxis is None: 

250 return x 

251 return ax.xaxis.convert_units(x) 

252 

253 def convert_yunits(self, y): 

254 """ 

255 Convert *y* using the unit type of the yaxis. 

256 

257 If the artist is not contained in an Axes or if the yaxis does not 

258 have units, *y* itself is returned. 

259 """ 

260 ax = getattr(self, 'axes', None) 

261 if ax is None or ax.yaxis is None: 

262 return y 

263 return ax.yaxis.convert_units(y) 

264 

265 @property 

266 def axes(self): 

267 """The `~.axes.Axes` instance the artist resides in, or *None*.""" 

268 return self._axes 

269 

270 @axes.setter 

271 def axes(self, new_axes): 

272 if (new_axes is not None and self._axes is not None 

273 and new_axes != self._axes): 

274 raise ValueError("Can not reset the axes. You are probably " 

275 "trying to re-use an artist in more than one " 

276 "Axes which is not supported") 

277 self._axes = new_axes 

278 if new_axes is not None and new_axes is not self: 

279 self.stale_callback = _stale_axes_callback 

280 

281 @property 

282 def stale(self): 

283 """ 

284 Whether the artist is 'stale' and needs to be re-drawn for the output 

285 to match the internal state of the artist. 

286 """ 

287 return self._stale 

288 

289 @stale.setter 

290 def stale(self, val): 

291 self._stale = val 

292 

293 # if the artist is animated it does not take normal part in the 

294 # draw stack and is not expected to be drawn as part of the normal 

295 # draw loop (when not saving) so do not propagate this change 

296 if self.get_animated(): 

297 return 

298 

299 if val and self.stale_callback is not None: 

300 self.stale_callback(self, val) 

301 

302 def get_window_extent(self, renderer=None): 

303 """ 

304 Get the artist's bounding box in display space. 

305 

306 The bounding box' width and height are nonnegative. 

307 

308 Subclasses should override for inclusion in the bounding box 

309 "tight" calculation. Default is to return an empty bounding 

310 box at 0, 0. 

311 

312 Be careful when using this function, the results will not update 

313 if the artist window extent of the artist changes. The extent 

314 can change due to any changes in the transform stack, such as 

315 changing the axes limits, the figure size, or the canvas used 

316 (as is done when saving a figure). This can lead to unexpected 

317 behavior where interactive figures will look fine on the screen, 

318 but will save incorrectly. 

319 """ 

320 return Bbox([[0, 0], [0, 0]]) 

321 

322 def get_tightbbox(self, renderer=None): 

323 """ 

324 Like `.Artist.get_window_extent`, but includes any clipping. 

325 

326 Parameters 

327 ---------- 

328 renderer : `.RendererBase` subclass 

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

330 ``fig.canvas.get_renderer()``) 

331 

332 Returns 

333 ------- 

334 `.Bbox` 

335 The enclosing bounding box (in figure pixel coordinates). 

336 """ 

337 bbox = self.get_window_extent(renderer) 

338 if self.get_clip_on(): 

339 clip_box = self.get_clip_box() 

340 if clip_box is not None: 

341 bbox = Bbox.intersection(bbox, clip_box) 

342 clip_path = self.get_clip_path() 

343 if clip_path is not None: 

344 clip_path = clip_path.get_fully_transformed_path() 

345 bbox = Bbox.intersection(bbox, clip_path.get_extents()) 

346 return bbox 

347 

348 def add_callback(self, func): 

349 """ 

350 Add a callback function that will be called whenever one of the 

351 `.Artist`'s properties changes. 

352 

353 Parameters 

354 ---------- 

355 func : callable 

356 The callback function. It must have the signature:: 

357 

358 def func(artist: Artist) -> Any 

359 

360 where *artist* is the calling `.Artist`. Return values may exist 

361 but are ignored. 

362 

363 Returns 

364 ------- 

365 int 

366 The observer id associated with the callback. This id can be 

367 used for removing the callback with `.remove_callback` later. 

368 

369 See Also 

370 -------- 

371 remove_callback 

372 """ 

373 # Wrapping func in a lambda ensures it can be connected multiple times 

374 # and never gets weakref-gc'ed. 

375 return self._callbacks.connect("pchanged", lambda: func(self)) 

376 

377 def remove_callback(self, oid): 

378 """ 

379 Remove a callback based on its observer id. 

380 

381 See Also 

382 -------- 

383 add_callback 

384 """ 

385 self._callbacks.disconnect(oid) 

386 

387 def pchanged(self): 

388 """ 

389 Call all of the registered callbacks. 

390 

391 This function is triggered internally when a property is changed. 

392 

393 See Also 

394 -------- 

395 add_callback 

396 remove_callback 

397 """ 

398 self._callbacks.process("pchanged") 

399 

400 def is_transform_set(self): 

401 """ 

402 Return whether the Artist has an explicitly set transform. 

403 

404 This is *True* after `.set_transform` has been called. 

405 """ 

406 return self._transformSet 

407 

408 def set_transform(self, t): 

409 """ 

410 Set the artist transform. 

411 

412 Parameters 

413 ---------- 

414 t : `.Transform` 

415 """ 

416 self._transform = t 

417 self._transformSet = True 

418 self.pchanged() 

419 self.stale = True 

420 

421 def get_transform(self): 

422 """Return the `.Transform` instance used by this artist.""" 

423 if self._transform is None: 

424 self._transform = IdentityTransform() 

425 elif (not isinstance(self._transform, Transform) 

426 and hasattr(self._transform, '_as_mpl_transform')): 

427 self._transform = self._transform._as_mpl_transform(self.axes) 

428 return self._transform 

429 

430 def get_children(self): 

431 r"""Return a list of the child `.Artist`\s of this `.Artist`.""" 

432 return [] 

433 

434 def _default_contains(self, mouseevent, figure=None): 

435 """ 

436 Base impl. for checking whether a mouseevent happened in an artist. 

437 

438 1. If the artist figure is known and the event did not occur in that 

439 figure (by checking its ``canvas`` attribute), reject it. 

440 2. Otherwise, return `None, {}`, indicating that the subclass' 

441 implementation should be used. 

442 

443 Subclasses should start their definition of `contains` as follows: 

444 

445 inside, info = self._default_contains(mouseevent) 

446 if inside is not None: 

447 return inside, info 

448 # subclass-specific implementation follows 

449 

450 The *figure* kwarg is provided for the implementation of 

451 `.Figure.contains`. 

452 """ 

453 if figure is not None and mouseevent.canvas is not figure.canvas: 

454 return False, {} 

455 return None, {} 

456 

457 def contains(self, mouseevent): 

458 """ 

459 Test whether the artist contains the mouse event. 

460 

461 Parameters 

462 ---------- 

463 mouseevent : `matplotlib.backend_bases.MouseEvent` 

464 

465 Returns 

466 ------- 

467 contains : bool 

468 Whether any values are within the radius. 

469 details : dict 

470 An artist-specific dictionary of details of the event context, 

471 such as which points are contained in the pick radius. See the 

472 individual Artist subclasses for details. 

473 """ 

474 inside, info = self._default_contains(mouseevent) 

475 if inside is not None: 

476 return inside, info 

477 _log.warning("%r needs 'contains' method", self.__class__.__name__) 

478 return False, {} 

479 

480 def pickable(self): 

481 """ 

482 Return whether the artist is pickable. 

483 

484 See Also 

485 -------- 

486 set_picker, get_picker, pick 

487 """ 

488 return self.figure is not None and self._picker is not None 

489 

490 def pick(self, mouseevent): 

491 """ 

492 Process a pick event. 

493 

494 Each child artist will fire a pick event if *mouseevent* is over 

495 the artist and the artist has picker set. 

496 

497 See Also 

498 -------- 

499 set_picker, get_picker, pickable 

500 """ 

501 from .backend_bases import PickEvent # Circular import. 

502 # Pick self 

503 if self.pickable(): 

504 picker = self.get_picker() 

505 if callable(picker): 

506 inside, prop = picker(self, mouseevent) 

507 else: 

508 inside, prop = self.contains(mouseevent) 

509 if inside: 

510 PickEvent("pick_event", self.figure.canvas, 

511 mouseevent, self, **prop)._process() 

512 

513 # Pick children 

514 for a in self.get_children(): 

515 # make sure the event happened in the same Axes 

516 ax = getattr(a, 'axes', None) 

517 if (mouseevent.inaxes is None or ax is None 

518 or mouseevent.inaxes == ax): 

519 # we need to check if mouseevent.inaxes is None 

520 # because some objects associated with an Axes (e.g., a 

521 # tick label) can be outside the bounding box of the 

522 # Axes and inaxes will be None 

523 # also check that ax is None so that it traverse objects 

524 # which do not have an axes property but children might 

525 a.pick(mouseevent) 

526 

527 def set_picker(self, picker): 

528 """ 

529 Define the picking behavior of the artist. 

530 

531 Parameters 

532 ---------- 

533 picker : None or bool or float or callable 

534 This can be one of the following: 

535 

536 - *None*: Picking is disabled for this artist (default). 

537 

538 - A boolean: If *True* then picking will be enabled and the 

539 artist will fire a pick event if the mouse event is over 

540 the artist. 

541 

542 - A float: If picker is a number it is interpreted as an 

543 epsilon tolerance in points and the artist will fire 

544 off an event if its data is within epsilon of the mouse 

545 event. For some artists like lines and patch collections, 

546 the artist may provide additional data to the pick event 

547 that is generated, e.g., the indices of the data within 

548 epsilon of the pick event 

549 

550 - A function: If picker is callable, it is a user supplied 

551 function which determines whether the artist is hit by the 

552 mouse event:: 

553 

554 hit, props = picker(artist, mouseevent) 

555 

556 to determine the hit test. if the mouse event is over the 

557 artist, return *hit=True* and props is a dictionary of 

558 properties you want added to the PickEvent attributes. 

559 """ 

560 self._picker = picker 

561 

562 def get_picker(self): 

563 """ 

564 Return the picking behavior of the artist. 

565 

566 The possible values are described in `.set_picker`. 

567 

568 See Also 

569 -------- 

570 set_picker, pickable, pick 

571 """ 

572 return self._picker 

573 

574 def get_url(self): 

575 """Return the url.""" 

576 return self._url 

577 

578 def set_url(self, url): 

579 """ 

580 Set the url for the artist. 

581 

582 Parameters 

583 ---------- 

584 url : str 

585 """ 

586 self._url = url 

587 

588 def get_gid(self): 

589 """Return the group id.""" 

590 return self._gid 

591 

592 def set_gid(self, gid): 

593 """ 

594 Set the (group) id for the artist. 

595 

596 Parameters 

597 ---------- 

598 gid : str 

599 """ 

600 self._gid = gid 

601 

602 def get_snap(self): 

603 """ 

604 Return the snap setting. 

605 

606 See `.set_snap` for details. 

607 """ 

608 if mpl.rcParams['path.snap']: 

609 return self._snap 

610 else: 

611 return False 

612 

613 def set_snap(self, snap): 

614 """ 

615 Set the snapping behavior. 

616 

617 Snapping aligns positions with the pixel grid, which results in 

618 clearer images. For example, if a black line of 1px width was 

619 defined at a position in between two pixels, the resulting image 

620 would contain the interpolated value of that line in the pixel grid, 

621 which would be a grey value on both adjacent pixel positions. In 

622 contrast, snapping will move the line to the nearest integer pixel 

623 value, so that the resulting image will really contain a 1px wide 

624 black line. 

625 

626 Snapping is currently only supported by the Agg and MacOSX backends. 

627 

628 Parameters 

629 ---------- 

630 snap : bool or None 

631 Possible values: 

632 

633 - *True*: Snap vertices to the nearest pixel center. 

634 - *False*: Do not modify vertex positions. 

635 - *None*: (auto) If the path contains only rectilinear line 

636 segments, round to the nearest pixel center. 

637 """ 

638 self._snap = snap 

639 self.stale = True 

640 

641 def get_sketch_params(self): 

642 """ 

643 Return the sketch parameters for the artist. 

644 

645 Returns 

646 ------- 

647 tuple or None 

648 

649 A 3-tuple with the following elements: 

650 

651 - *scale*: The amplitude of the wiggle perpendicular to the 

652 source line. 

653 - *length*: The length of the wiggle along the line. 

654 - *randomness*: The scale factor by which the length is 

655 shrunken or expanded. 

656 

657 Returns *None* if no sketch parameters were set. 

658 """ 

659 return self._sketch 

660 

661 def set_sketch_params(self, scale=None, length=None, randomness=None): 

662 """ 

663 Set the sketch parameters. 

664 

665 Parameters 

666 ---------- 

667 scale : float, optional 

668 The amplitude of the wiggle perpendicular to the source 

669 line, in pixels. If scale is `None`, or not provided, no 

670 sketch filter will be provided. 

671 length : float, optional 

672 The length of the wiggle along the line, in pixels 

673 (default 128.0) 

674 randomness : float, optional 

675 The scale factor by which the length is shrunken or 

676 expanded (default 16.0) 

677 

678 The PGF backend uses this argument as an RNG seed and not as 

679 described above. Using the same seed yields the same random shape. 

680 

681 .. ACCEPTS: (scale: float, length: float, randomness: float) 

682 """ 

683 if scale is None: 

684 self._sketch = None 

685 else: 

686 self._sketch = (scale, length or 128.0, randomness or 16.0) 

687 self.stale = True 

688 

689 def set_path_effects(self, path_effects): 

690 """ 

691 Set the path effects. 

692 

693 Parameters 

694 ---------- 

695 path_effects : `.AbstractPathEffect` 

696 """ 

697 self._path_effects = path_effects 

698 self.stale = True 

699 

700 def get_path_effects(self): 

701 return self._path_effects 

702 

703 def get_figure(self): 

704 """Return the `.Figure` instance the artist belongs to.""" 

705 return self.figure 

706 

707 def set_figure(self, fig): 

708 """ 

709 Set the `.Figure` instance the artist belongs to. 

710 

711 Parameters 

712 ---------- 

713 fig : `.Figure` 

714 """ 

715 # if this is a no-op just return 

716 if self.figure is fig: 

717 return 

718 # if we currently have a figure (the case of both `self.figure` 

719 # and *fig* being none is taken care of above) we then user is 

720 # trying to change the figure an artist is associated with which 

721 # is not allowed for the same reason as adding the same instance 

722 # to more than one Axes 

723 if self.figure is not None: 

724 raise RuntimeError("Can not put single artist in " 

725 "more than one figure") 

726 self.figure = fig 

727 if self.figure and self.figure is not self: 

728 self.pchanged() 

729 self.stale = True 

730 

731 def set_clip_box(self, clipbox): 

732 """ 

733 Set the artist's clip `.Bbox`. 

734 

735 Parameters 

736 ---------- 

737 clipbox : `.Bbox` 

738 """ 

739 self.clipbox = clipbox 

740 self.pchanged() 

741 self.stale = True 

742 

743 def set_clip_path(self, path, transform=None): 

744 """ 

745 Set the artist's clip path. 

746 

747 Parameters 

748 ---------- 

749 path : `.Patch` or `.Path` or `.TransformedPath` or None 

750 The clip path. If given a `.Path`, *transform* must be provided as 

751 well. If *None*, a previously set clip path is removed. 

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

753 Only used if *path* is a `.Path`, in which case the given `.Path` 

754 is converted to a `.TransformedPath` using *transform*. 

755 

756 Notes 

757 ----- 

758 For efficiency, if *path* is a `.Rectangle` this method will set the 

759 clipping box to the corresponding rectangle and set the clipping path 

760 to ``None``. 

761 

762 For technical reasons (support of `~.Artist.set`), a tuple 

763 (*path*, *transform*) is also accepted as a single positional 

764 parameter. 

765 

766 .. ACCEPTS: Patch or (Path, Transform) or None 

767 """ 

768 from matplotlib.patches import Patch, Rectangle 

769 

770 success = False 

771 if transform is None: 

772 if isinstance(path, Rectangle): 

773 self.clipbox = TransformedBbox(Bbox.unit(), 

774 path.get_transform()) 

775 self._clippath = None 

776 success = True 

777 elif isinstance(path, Patch): 

778 self._clippath = TransformedPatchPath(path) 

779 success = True 

780 elif isinstance(path, tuple): 

781 path, transform = path 

782 

783 if path is None: 

784 self._clippath = None 

785 success = True 

786 elif isinstance(path, Path): 

787 self._clippath = TransformedPath(path, transform) 

788 success = True 

789 elif isinstance(path, TransformedPatchPath): 

790 self._clippath = path 

791 success = True 

792 elif isinstance(path, TransformedPath): 

793 self._clippath = path 

794 success = True 

795 

796 if not success: 

797 raise TypeError( 

798 "Invalid arguments to set_clip_path, of type {} and {}" 

799 .format(type(path).__name__, type(transform).__name__)) 

800 # This may result in the callbacks being hit twice, but guarantees they 

801 # will be hit at least once. 

802 self.pchanged() 

803 self.stale = True 

804 

805 def get_alpha(self): 

806 """ 

807 Return the alpha value used for blending - not supported on all 

808 backends. 

809 """ 

810 return self._alpha 

811 

812 def get_visible(self): 

813 """Return the visibility.""" 

814 return self._visible 

815 

816 def get_animated(self): 

817 """Return whether the artist is animated.""" 

818 return self._animated 

819 

820 def get_in_layout(self): 

821 """ 

822 Return boolean flag, ``True`` if artist is included in layout 

823 calculations. 

824 

825 E.g. :doc:`/tutorials/intermediate/constrainedlayout_guide`, 

826 `.Figure.tight_layout()`, and 

827 ``fig.savefig(fname, bbox_inches='tight')``. 

828 """ 

829 return self._in_layout 

830 

831 def _fully_clipped_to_axes(self): 

832 """ 

833 Return a boolean flag, ``True`` if the artist is clipped to the Axes 

834 and can thus be skipped in layout calculations. Requires `get_clip_on` 

835 is True, one of `clip_box` or `clip_path` is set, ``clip_box.extents`` 

836 is equivalent to ``ax.bbox.extents`` (if set), and ``clip_path._patch`` 

837 is equivalent to ``ax.patch`` (if set). 

838 """ 

839 # Note that ``clip_path.get_fully_transformed_path().get_extents()`` 

840 # cannot be directly compared to ``axes.bbox.extents`` because the 

841 # extents may be undefined (i.e. equivalent to ``Bbox.null()``) 

842 # before the associated artist is drawn, and this method is meant 

843 # to determine whether ``axes.get_tightbbox()`` may bypass drawing 

844 clip_box = self.get_clip_box() 

845 clip_path = self.get_clip_path() 

846 return (self.axes is not None 

847 and self.get_clip_on() 

848 and (clip_box is not None or clip_path is not None) 

849 and (clip_box is None 

850 or np.all(clip_box.extents == self.axes.bbox.extents)) 

851 and (clip_path is None 

852 or isinstance(clip_path, TransformedPatchPath) 

853 and clip_path._patch is self.axes.patch)) 

854 

855 def get_clip_on(self): 

856 """Return whether the artist uses clipping.""" 

857 return self._clipon 

858 

859 def get_clip_box(self): 

860 """Return the clipbox.""" 

861 return self.clipbox 

862 

863 def get_clip_path(self): 

864 """Return the clip path.""" 

865 return self._clippath 

866 

867 def get_transformed_clip_path_and_affine(self): 

868 """ 

869 Return the clip path with the non-affine part of its 

870 transformation applied, and the remaining affine part of its 

871 transformation. 

872 """ 

873 if self._clippath is not None: 

874 return self._clippath.get_transformed_path_and_affine() 

875 return None, None 

876 

877 def set_clip_on(self, b): 

878 """ 

879 Set whether the artist uses clipping. 

880 

881 When False, artists will be visible outside the Axes which 

882 can lead to unexpected results. 

883 

884 Parameters 

885 ---------- 

886 b : bool 

887 """ 

888 self._clipon = b 

889 # This may result in the callbacks being hit twice, but ensures they 

890 # are hit at least once 

891 self.pchanged() 

892 self.stale = True 

893 

894 def _set_gc_clip(self, gc): 

895 """Set the clip properly for the gc.""" 

896 if self._clipon: 

897 if self.clipbox is not None: 

898 gc.set_clip_rectangle(self.clipbox) 

899 gc.set_clip_path(self._clippath) 

900 else: 

901 gc.set_clip_rectangle(None) 

902 gc.set_clip_path(None) 

903 

904 def get_rasterized(self): 

905 """Return whether the artist is to be rasterized.""" 

906 return self._rasterized 

907 

908 def set_rasterized(self, rasterized): 

909 """ 

910 Force rasterized (bitmap) drawing for vector graphics output. 

911 

912 Rasterized drawing is not supported by all artists. If you try to 

913 enable this on an artist that does not support it, the command has no 

914 effect and a warning will be issued. 

915 

916 This setting is ignored for pixel-based output. 

917 

918 See also :doc:`/gallery/misc/rasterization_demo`. 

919 

920 Parameters 

921 ---------- 

922 rasterized : bool 

923 """ 

924 if rasterized and not hasattr(self.draw, "_supports_rasterization"): 

925 _api.warn_external(f"Rasterization of '{self}' will be ignored") 

926 

927 self._rasterized = rasterized 

928 

929 def get_agg_filter(self): 

930 """Return filter function to be used for agg filter.""" 

931 return self._agg_filter 

932 

933 def set_agg_filter(self, filter_func): 

934 """ 

935 Set the agg filter. 

936 

937 Parameters 

938 ---------- 

939 filter_func : callable 

940 A filter function, which takes a (m, n, depth) float array 

941 and a dpi value, and returns a (m, n, depth) array and two 

942 offsets from the bottom left corner of the image 

943 

944 .. ACCEPTS: a filter function, which takes a (m, n, 3) float array 

945 and a dpi value, and returns a (m, n, 3) array and two offsets 

946 from the bottom left corner of the image 

947 """ 

948 self._agg_filter = filter_func 

949 self.stale = True 

950 

951 def draw(self, renderer): 

952 """ 

953 Draw the Artist (and its children) using the given renderer. 

954 

955 This has no effect if the artist is not visible (`.Artist.get_visible` 

956 returns False). 

957 

958 Parameters 

959 ---------- 

960 renderer : `.RendererBase` subclass. 

961 

962 Notes 

963 ----- 

964 This method is overridden in the Artist subclasses. 

965 """ 

966 if not self.get_visible(): 

967 return 

968 self.stale = False 

969 

970 def set_alpha(self, alpha): 

971 """ 

972 Set the alpha value used for blending - not supported on all backends. 

973 

974 Parameters 

975 ---------- 

976 alpha : scalar or None 

977 *alpha* must be within the 0-1 range, inclusive. 

978 """ 

979 if alpha is not None and not isinstance(alpha, Number): 

980 raise TypeError( 

981 f'alpha must be numeric or None, not {type(alpha)}') 

982 if alpha is not None and not (0 <= alpha <= 1): 

983 raise ValueError(f'alpha ({alpha}) is outside 0-1 range') 

984 self._alpha = alpha 

985 self.pchanged() 

986 self.stale = True 

987 

988 def _set_alpha_for_array(self, alpha): 

989 """ 

990 Set the alpha value used for blending - not supported on all backends. 

991 

992 Parameters 

993 ---------- 

994 alpha : array-like or scalar or None 

995 All values must be within the 0-1 range, inclusive. 

996 Masked values and nans are not supported. 

997 """ 

998 if isinstance(alpha, str): 

999 raise TypeError("alpha must be numeric or None, not a string") 

1000 if not np.iterable(alpha): 

1001 Artist.set_alpha(self, alpha) 

1002 return 

1003 alpha = np.asarray(alpha) 

1004 if not (0 <= alpha.min() and alpha.max() <= 1): 

1005 raise ValueError('alpha must be between 0 and 1, inclusive, ' 

1006 f'but min is {alpha.min()}, max is {alpha.max()}') 

1007 self._alpha = alpha 

1008 self.pchanged() 

1009 self.stale = True 

1010 

1011 def set_visible(self, b): 

1012 """ 

1013 Set the artist's visibility. 

1014 

1015 Parameters 

1016 ---------- 

1017 b : bool 

1018 """ 

1019 self._visible = b 

1020 self.pchanged() 

1021 self.stale = True 

1022 

1023 def set_animated(self, b): 

1024 """ 

1025 Set whether the artist is intended to be used in an animation. 

1026 

1027 If True, the artist is excluded from regular drawing of the figure. 

1028 You have to call `.Figure.draw_artist` / `.Axes.draw_artist` 

1029 explicitly on the artist. This approach is used to speed up animations 

1030 using blitting. 

1031 

1032 See also `matplotlib.animation` and 

1033 :doc:`/tutorials/advanced/blitting`. 

1034 

1035 Parameters 

1036 ---------- 

1037 b : bool 

1038 """ 

1039 if self._animated != b: 

1040 self._animated = b 

1041 self.pchanged() 

1042 

1043 def set_in_layout(self, in_layout): 

1044 """ 

1045 Set if artist is to be included in layout calculations, 

1046 E.g. :doc:`/tutorials/intermediate/constrainedlayout_guide`, 

1047 `.Figure.tight_layout()`, and 

1048 ``fig.savefig(fname, bbox_inches='tight')``. 

1049 

1050 Parameters 

1051 ---------- 

1052 in_layout : bool 

1053 """ 

1054 self._in_layout = in_layout 

1055 

1056 def get_label(self): 

1057 """Return the label used for this artist in the legend.""" 

1058 return self._label 

1059 

1060 def set_label(self, s): 

1061 """ 

1062 Set a label that will be displayed in the legend. 

1063 

1064 Parameters 

1065 ---------- 

1066 s : object 

1067 *s* will be converted to a string by calling `str`. 

1068 """ 

1069 if s is not None: 

1070 self._label = str(s) 

1071 else: 

1072 self._label = None 

1073 self.pchanged() 

1074 self.stale = True 

1075 

1076 def get_zorder(self): 

1077 """Return the artist's zorder.""" 

1078 return self.zorder 

1079 

1080 def set_zorder(self, level): 

1081 """ 

1082 Set the zorder for the artist. Artists with lower zorder 

1083 values are drawn first. 

1084 

1085 Parameters 

1086 ---------- 

1087 level : float 

1088 """ 

1089 if level is None: 

1090 level = self.__class__.zorder 

1091 self.zorder = level 

1092 self.pchanged() 

1093 self.stale = True 

1094 

1095 @property 

1096 def sticky_edges(self): 

1097 """ 

1098 ``x`` and ``y`` sticky edge lists for autoscaling. 

1099 

1100 When performing autoscaling, if a data limit coincides with a value in 

1101 the corresponding sticky_edges list, then no margin will be added--the 

1102 view limit "sticks" to the edge. A typical use case is histograms, 

1103 where one usually expects no margin on the bottom edge (0) of the 

1104 histogram. 

1105 

1106 Moreover, margin expansion "bumps" against sticky edges and cannot 

1107 cross them. For example, if the upper data limit is 1.0, the upper 

1108 view limit computed by simple margin application is 1.2, but there is a 

1109 sticky edge at 1.1, then the actual upper view limit will be 1.1. 

1110 

1111 This attribute cannot be assigned to; however, the ``x`` and ``y`` 

1112 lists can be modified in place as needed. 

1113 

1114 Examples 

1115 -------- 

1116 >>> artist.sticky_edges.x[:] = (xmin, xmax) 

1117 >>> artist.sticky_edges.y[:] = (ymin, ymax) 

1118 

1119 """ 

1120 return self._sticky_edges 

1121 

1122 def update_from(self, other): 

1123 """Copy properties from *other* to *self*.""" 

1124 self._transform = other._transform 

1125 self._transformSet = other._transformSet 

1126 self._visible = other._visible 

1127 self._alpha = other._alpha 

1128 self.clipbox = other.clipbox 

1129 self._clipon = other._clipon 

1130 self._clippath = other._clippath 

1131 self._label = other._label 

1132 self._sketch = other._sketch 

1133 self._path_effects = other._path_effects 

1134 self.sticky_edges.x[:] = other.sticky_edges.x.copy() 

1135 self.sticky_edges.y[:] = other.sticky_edges.y.copy() 

1136 self.pchanged() 

1137 self.stale = True 

1138 

1139 def properties(self): 

1140 """Return a dictionary of all the properties of the artist.""" 

1141 return ArtistInspector(self).properties() 

1142 

1143 def _update_props(self, props, errfmt): 

1144 """ 

1145 Helper for `.Artist.set` and `.Artist.update`. 

1146 

1147 *errfmt* is used to generate error messages for invalid property 

1148 names; it gets formatted with ``type(self)`` and the property name. 

1149 """ 

1150 ret = [] 

1151 with cbook._setattr_cm(self, eventson=False): 

1152 for k, v in props.items(): 

1153 # Allow attributes we want to be able to update through 

1154 # art.update, art.set, setp. 

1155 if k == "axes": 

1156 ret.append(setattr(self, k, v)) 

1157 else: 

1158 func = getattr(self, f"set_{k}", None) 

1159 if not callable(func): 

1160 raise AttributeError( 

1161 errfmt.format(cls=type(self), prop_name=k)) 

1162 ret.append(func(v)) 

1163 if ret: 

1164 self.pchanged() 

1165 self.stale = True 

1166 return ret 

1167 

1168 def update(self, props): 

1169 """ 

1170 Update this artist's properties from the dict *props*. 

1171 

1172 Parameters 

1173 ---------- 

1174 props : dict 

1175 """ 

1176 return self._update_props( 

1177 props, "{cls.__name__!r} object has no property {prop_name!r}") 

1178 

1179 def _internal_update(self, kwargs): 

1180 """ 

1181 Update artist properties without prenormalizing them, but generating 

1182 errors as if calling `set`. 

1183 

1184 The lack of prenormalization is to maintain backcompatibility. 

1185 """ 

1186 return self._update_props( 

1187 kwargs, "{cls.__name__}.set() got an unexpected keyword argument " 

1188 "{prop_name!r}") 

1189 

1190 def set(self, **kwargs): 

1191 # docstring and signature are auto-generated via 

1192 # Artist._update_set_signature_and_docstring() at the end of the 

1193 # module. 

1194 return self._internal_update(cbook.normalize_kwargs(kwargs, self)) 

1195 

1196 @contextlib.contextmanager 

1197 def _cm_set(self, **kwargs): 

1198 """ 

1199 `.Artist.set` context-manager that restores original values at exit. 

1200 """ 

1201 orig_vals = {k: getattr(self, f"get_{k}")() for k in kwargs} 

1202 try: 

1203 self.set(**kwargs) 

1204 yield 

1205 finally: 

1206 self.set(**orig_vals) 

1207 

1208 def findobj(self, match=None, include_self=True): 

1209 """ 

1210 Find artist objects. 

1211 

1212 Recursively find all `.Artist` instances contained in the artist. 

1213 

1214 Parameters 

1215 ---------- 

1216 match 

1217 A filter criterion for the matches. This can be 

1218 

1219 - *None*: Return all objects contained in artist. 

1220 - A function with signature ``def match(artist: Artist) -> bool``. 

1221 The result will only contain artists for which the function 

1222 returns *True*. 

1223 - A class instance: e.g., `.Line2D`. The result will only contain 

1224 artists of this class or its subclasses (``isinstance`` check). 

1225 

1226 include_self : bool 

1227 Include *self* in the list to be checked for a match. 

1228 

1229 Returns 

1230 ------- 

1231 list of `.Artist` 

1232 

1233 """ 

1234 if match is None: # always return True 

1235 def matchfunc(x): 

1236 return True 

1237 elif isinstance(match, type) and issubclass(match, Artist): 

1238 def matchfunc(x): 

1239 return isinstance(x, match) 

1240 elif callable(match): 

1241 matchfunc = match 

1242 else: 

1243 raise ValueError('match must be None, a matplotlib.artist.Artist ' 

1244 'subclass, or a callable') 

1245 

1246 artists = sum([c.findobj(matchfunc) for c in self.get_children()], []) 

1247 if include_self and matchfunc(self): 

1248 artists.append(self) 

1249 return artists 

1250 

1251 def get_cursor_data(self, event): 

1252 """ 

1253 Return the cursor data for a given event. 

1254 

1255 .. note:: 

1256 This method is intended to be overridden by artist subclasses. 

1257 As an end-user of Matplotlib you will most likely not call this 

1258 method yourself. 

1259 

1260 Cursor data can be used by Artists to provide additional context 

1261 information for a given event. The default implementation just returns 

1262 *None*. 

1263 

1264 Subclasses can override the method and return arbitrary data. However, 

1265 when doing so, they must ensure that `.format_cursor_data` can convert 

1266 the data to a string representation. 

1267 

1268 The only current use case is displaying the z-value of an `.AxesImage` 

1269 in the status bar of a plot window, while moving the mouse. 

1270 

1271 Parameters 

1272 ---------- 

1273 event : `matplotlib.backend_bases.MouseEvent` 

1274 

1275 See Also 

1276 -------- 

1277 format_cursor_data 

1278 

1279 """ 

1280 return None 

1281 

1282 def format_cursor_data(self, data): 

1283 """ 

1284 Return a string representation of *data*. 

1285 

1286 .. note:: 

1287 This method is intended to be overridden by artist subclasses. 

1288 As an end-user of Matplotlib you will most likely not call this 

1289 method yourself. 

1290 

1291 The default implementation converts ints and floats and arrays of ints 

1292 and floats into a comma-separated string enclosed in square brackets, 

1293 unless the artist has an associated colorbar, in which case scalar 

1294 values are formatted using the colorbar's formatter. 

1295 

1296 See Also 

1297 -------- 

1298 get_cursor_data 

1299 """ 

1300 if np.ndim(data) == 0 and isinstance(self, ScalarMappable): 

1301 # This block logically belongs to ScalarMappable, but can't be 

1302 # implemented in it because most ScalarMappable subclasses inherit 

1303 # from Artist first and from ScalarMappable second, so 

1304 # Artist.format_cursor_data would always have precedence over 

1305 # ScalarMappable.format_cursor_data. 

1306 n = self.cmap.N 

1307 if np.ma.getmask(data): 

1308 return "[]" 

1309 normed = self.norm(data) 

1310 if np.isfinite(normed): 

1311 if isinstance(self.norm, BoundaryNorm): 

1312 # not an invertible normalization mapping 

1313 cur_idx = np.argmin(np.abs(self.norm.boundaries - data)) 

1314 neigh_idx = max(0, cur_idx - 1) 

1315 # use max diff to prevent delta == 0 

1316 delta = np.diff( 

1317 self.norm.boundaries[neigh_idx:cur_idx + 2] 

1318 ).max() 

1319 

1320 else: 

1321 # Midpoints of neighboring color intervals. 

1322 neighbors = self.norm.inverse( 

1323 (int(normed * n) + np.array([0, 1])) / n) 

1324 delta = abs(neighbors - data).max() 

1325 g_sig_digits = cbook._g_sig_digits(data, delta) 

1326 else: 

1327 g_sig_digits = 3 # Consistent with default below. 

1328 return "[{:-#.{}g}]".format(data, g_sig_digits) 

1329 else: 

1330 try: 

1331 data[0] 

1332 except (TypeError, IndexError): 

1333 data = [data] 

1334 data_str = ', '.join('{:0.3g}'.format(item) for item in data 

1335 if isinstance(item, Number)) 

1336 return "[" + data_str + "]" 

1337 

1338 def get_mouseover(self): 

1339 """ 

1340 Return whether this artist is queried for custom context information 

1341 when the mouse cursor moves over it. 

1342 """ 

1343 return self._mouseover 

1344 

1345 def set_mouseover(self, mouseover): 

1346 """ 

1347 Set whether this artist is queried for custom context information when 

1348 the mouse cursor moves over it. 

1349 

1350 Parameters 

1351 ---------- 

1352 mouseover : bool 

1353 

1354 See Also 

1355 -------- 

1356 get_cursor_data 

1357 .ToolCursorPosition 

1358 .NavigationToolbar2 

1359 """ 

1360 self._mouseover = bool(mouseover) 

1361 ax = self.axes 

1362 if ax: 

1363 if self._mouseover: 

1364 ax._mouseover_set.add(self) 

1365 else: 

1366 ax._mouseover_set.discard(self) 

1367 

1368 mouseover = property(get_mouseover, set_mouseover) # backcompat. 

1369 

1370 

1371def _get_tightbbox_for_layout_only(obj, *args, **kwargs): 

1372 """ 

1373 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a 

1374 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it 

1375 when encountering third-party subclasses that do not support it. 

1376 """ 

1377 try: 

1378 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True}) 

1379 except TypeError: 

1380 return obj.get_tightbbox(*args, **kwargs) 

1381 

1382 

1383class ArtistInspector: 

1384 """ 

1385 A helper class to inspect an `~matplotlib.artist.Artist` and return 

1386 information about its settable properties and their current values. 

1387 """ 

1388 

1389 def __init__(self, o): 

1390 r""" 

1391 Initialize the artist inspector with an `Artist` or an iterable of 

1392 `Artist`\s. If an iterable is used, we assume it is a homogeneous 

1393 sequence (all `Artist`\s are of the same type) and it is your 

1394 responsibility to make sure this is so. 

1395 """ 

1396 if not isinstance(o, Artist): 

1397 if np.iterable(o): 

1398 o = list(o) 

1399 if len(o): 

1400 o = o[0] 

1401 

1402 self.oorig = o 

1403 if not isinstance(o, type): 

1404 o = type(o) 

1405 self.o = o 

1406 

1407 self.aliasd = self.get_aliases() 

1408 

1409 def get_aliases(self): 

1410 """ 

1411 Get a dict mapping property fullnames to sets of aliases for each alias 

1412 in the :class:`~matplotlib.artist.ArtistInspector`. 

1413 

1414 e.g., for lines:: 

1415 

1416 {'markerfacecolor': {'mfc'}, 

1417 'linewidth' : {'lw'}, 

1418 } 

1419 """ 

1420 names = [name for name in dir(self.o) 

1421 if name.startswith(('set_', 'get_')) 

1422 and callable(getattr(self.o, name))] 

1423 aliases = {} 

1424 for name in names: 

1425 func = getattr(self.o, name) 

1426 if not self.is_alias(func): 

1427 continue 

1428 propname = re.search("`({}.*)`".format(name[:4]), # get_.*/set_.* 

1429 inspect.getdoc(func)).group(1) 

1430 aliases.setdefault(propname[4:], set()).add(name[4:]) 

1431 return aliases 

1432 

1433 _get_valid_values_regex = re.compile( 

1434 r"\n\s*(?:\.\.\s+)?ACCEPTS:\s*((?:.|\n)*?)(?:$|(?:\n\n))" 

1435 ) 

1436 

1437 def get_valid_values(self, attr): 

1438 """ 

1439 Get the legal arguments for the setter associated with *attr*. 

1440 

1441 This is done by querying the docstring of the setter for a line that 

1442 begins with "ACCEPTS:" or ".. ACCEPTS:", and then by looking for a 

1443 numpydoc-style documentation for the setter's first argument. 

1444 """ 

1445 

1446 name = 'set_%s' % attr 

1447 if not hasattr(self.o, name): 

1448 raise AttributeError('%s has no function %s' % (self.o, name)) 

1449 func = getattr(self.o, name) 

1450 

1451 docstring = inspect.getdoc(func) 

1452 if docstring is None: 

1453 return 'unknown' 

1454 

1455 if docstring.startswith('Alias for '): 

1456 return None 

1457 

1458 match = self._get_valid_values_regex.search(docstring) 

1459 if match is not None: 

1460 return re.sub("\n *", " ", match.group(1)) 

1461 

1462 # Much faster than list(inspect.signature(func).parameters)[1], 

1463 # although barely relevant wrt. matplotlib's total import time. 

1464 param_name = func.__code__.co_varnames[1] 

1465 # We could set the presence * based on whether the parameter is a 

1466 # varargs (it can't be a varkwargs) but it's not really worth it. 

1467 match = re.search(r"(?m)^ *\*?{} : (.+)".format(param_name), docstring) 

1468 if match: 

1469 return match.group(1) 

1470 

1471 return 'unknown' 

1472 

1473 def _replace_path(self, source_class): 

1474 """ 

1475 Changes the full path to the public API path that is used 

1476 in sphinx. This is needed for links to work. 

1477 """ 

1478 replace_dict = {'_base._AxesBase': 'Axes', 

1479 '_axes.Axes': 'Axes'} 

1480 for key, value in replace_dict.items(): 

1481 source_class = source_class.replace(key, value) 

1482 return source_class 

1483 

1484 def get_setters(self): 

1485 """ 

1486 Get the attribute strings with setters for object. 

1487 

1488 For example, for a line, return ``['markerfacecolor', 'linewidth', 

1489 ....]``. 

1490 """ 

1491 setters = [] 

1492 for name in dir(self.o): 

1493 if not name.startswith('set_'): 

1494 continue 

1495 func = getattr(self.o, name) 

1496 if (not callable(func) 

1497 or self.number_of_parameters(func) < 2 

1498 or self.is_alias(func)): 

1499 continue 

1500 setters.append(name[4:]) 

1501 return setters 

1502 

1503 @staticmethod 

1504 @lru_cache(maxsize=None) 

1505 def number_of_parameters(func): 

1506 """Return number of parameters of the callable *func*.""" 

1507 return len(inspect.signature(func).parameters) 

1508 

1509 @staticmethod 

1510 @lru_cache(maxsize=None) 

1511 def is_alias(method): 

1512 """ 

1513 Return whether the object *method* is an alias for another method. 

1514 """ 

1515 

1516 ds = inspect.getdoc(method) 

1517 if ds is None: 

1518 return False 

1519 

1520 return ds.startswith('Alias for ') 

1521 

1522 def aliased_name(self, s): 

1523 """ 

1524 Return 'PROPNAME or alias' if *s* has an alias, else return 'PROPNAME'. 

1525 

1526 For example, for the line markerfacecolor property, which has an 

1527 alias, return 'markerfacecolor or mfc' and for the transform 

1528 property, which does not, return 'transform'. 

1529 """ 

1530 aliases = ''.join(' or %s' % x for x in sorted(self.aliasd.get(s, []))) 

1531 return s + aliases 

1532 

1533 _NOT_LINKABLE = { 

1534 # A set of property setter methods that are not available in our 

1535 # current docs. This is a workaround used to prevent trying to link 

1536 # these setters which would lead to "target reference not found" 

1537 # warnings during doc build. 

1538 'matplotlib.image._ImageBase.set_alpha', 

1539 'matplotlib.image._ImageBase.set_array', 

1540 'matplotlib.image._ImageBase.set_data', 

1541 'matplotlib.image._ImageBase.set_filternorm', 

1542 'matplotlib.image._ImageBase.set_filterrad', 

1543 'matplotlib.image._ImageBase.set_interpolation', 

1544 'matplotlib.image._ImageBase.set_interpolation_stage', 

1545 'matplotlib.image._ImageBase.set_resample', 

1546 'matplotlib.text._AnnotationBase.set_annotation_clip', 

1547 } 

1548 

1549 def aliased_name_rest(self, s, target): 

1550 """ 

1551 Return 'PROPNAME or alias' if *s* has an alias, else return 'PROPNAME', 

1552 formatted for reST. 

1553 

1554 For example, for the line markerfacecolor property, which has an 

1555 alias, return 'markerfacecolor or mfc' and for the transform 

1556 property, which does not, return 'transform'. 

1557 """ 

1558 # workaround to prevent "reference target not found" 

1559 if target in self._NOT_LINKABLE: 

1560 return f'``{s}``' 

1561 

1562 aliases = ''.join(' or %s' % x for x in sorted(self.aliasd.get(s, []))) 

1563 return ':meth:`%s <%s>`%s' % (s, target, aliases) 

1564 

1565 def pprint_setters(self, prop=None, leadingspace=2): 

1566 """ 

1567 If *prop* is *None*, return a list of strings of all settable 

1568 properties and their valid values. 

1569 

1570 If *prop* is not *None*, it is a valid property name and that 

1571 property will be returned as a string of property : valid 

1572 values. 

1573 """ 

1574 if leadingspace: 

1575 pad = ' ' * leadingspace 

1576 else: 

1577 pad = '' 

1578 if prop is not None: 

1579 accepts = self.get_valid_values(prop) 

1580 return '%s%s: %s' % (pad, prop, accepts) 

1581 

1582 lines = [] 

1583 for prop in sorted(self.get_setters()): 

1584 accepts = self.get_valid_values(prop) 

1585 name = self.aliased_name(prop) 

1586 lines.append('%s%s: %s' % (pad, name, accepts)) 

1587 return lines 

1588 

1589 def pprint_setters_rest(self, prop=None, leadingspace=4): 

1590 """ 

1591 If *prop* is *None*, return a list of reST-formatted strings of all 

1592 settable properties and their valid values. 

1593 

1594 If *prop* is not *None*, it is a valid property name and that 

1595 property will be returned as a string of "property : valid" 

1596 values. 

1597 """ 

1598 if leadingspace: 

1599 pad = ' ' * leadingspace 

1600 else: 

1601 pad = '' 

1602 if prop is not None: 

1603 accepts = self.get_valid_values(prop) 

1604 return '%s%s: %s' % (pad, prop, accepts) 

1605 

1606 prop_and_qualnames = [] 

1607 for prop in sorted(self.get_setters()): 

1608 # Find the parent method which actually provides the docstring. 

1609 for cls in self.o.__mro__: 

1610 method = getattr(cls, f"set_{prop}", None) 

1611 if method and method.__doc__ is not None: 

1612 break 

1613 else: # No docstring available. 

1614 method = getattr(self.o, f"set_{prop}") 

1615 prop_and_qualnames.append( 

1616 (prop, f"{method.__module__}.{method.__qualname__}")) 

1617 

1618 names = [self.aliased_name_rest(prop, target) 

1619 .replace('_base._AxesBase', 'Axes') 

1620 .replace('_axes.Axes', 'Axes') 

1621 for prop, target in prop_and_qualnames] 

1622 accepts = [self.get_valid_values(prop) 

1623 for prop, _ in prop_and_qualnames] 

1624 

1625 col0_len = max(len(n) for n in names) 

1626 col1_len = max(len(a) for a in accepts) 

1627 table_formatstr = pad + ' ' + '=' * col0_len + ' ' + '=' * col1_len 

1628 

1629 return [ 

1630 '', 

1631 pad + '.. table::', 

1632 pad + ' :class: property-table', 

1633 '', 

1634 table_formatstr, 

1635 pad + ' ' + 'Property'.ljust(col0_len) 

1636 + ' ' + 'Description'.ljust(col1_len), 

1637 table_formatstr, 

1638 *[pad + ' ' + n.ljust(col0_len) + ' ' + a.ljust(col1_len) 

1639 for n, a in zip(names, accepts)], 

1640 table_formatstr, 

1641 '', 

1642 ] 

1643 

1644 def properties(self): 

1645 """Return a dictionary mapping property name -> value.""" 

1646 o = self.oorig 

1647 getters = [name for name in dir(o) 

1648 if name.startswith('get_') and callable(getattr(o, name))] 

1649 getters.sort() 

1650 d = {} 

1651 for name in getters: 

1652 func = getattr(o, name) 

1653 if self.is_alias(func): 

1654 continue 

1655 try: 

1656 with warnings.catch_warnings(): 

1657 warnings.simplefilter('ignore') 

1658 val = func() 

1659 except Exception: 

1660 continue 

1661 else: 

1662 d[name[4:]] = val 

1663 return d 

1664 

1665 def pprint_getters(self): 

1666 """Return the getters and actual values as list of strings.""" 

1667 lines = [] 

1668 for name, val in sorted(self.properties().items()): 

1669 if getattr(val, 'shape', ()) != () and len(val) > 6: 

1670 s = str(val[:6]) + '...' 

1671 else: 

1672 s = str(val) 

1673 s = s.replace('\n', ' ') 

1674 if len(s) > 50: 

1675 s = s[:50] + '...' 

1676 name = self.aliased_name(name) 

1677 lines.append(' %s = %s' % (name, s)) 

1678 return lines 

1679 

1680 

1681def getp(obj, property=None): 

1682 """ 

1683 Return the value of an `.Artist`'s *property*, or print all of them. 

1684 

1685 Parameters 

1686 ---------- 

1687 obj : `.Artist` 

1688 The queried artist; e.g., a `.Line2D`, a `.Text`, or an `~.axes.Axes`. 

1689 

1690 property : str or None, default: None 

1691 If *property* is 'somename', this function returns 

1692 ``obj.get_somename()``. 

1693 

1694 If it's None (or unset), it *prints* all gettable properties from 

1695 *obj*. Many properties have aliases for shorter typing, e.g. 'lw' is 

1696 an alias for 'linewidth'. In the output, aliases and full property 

1697 names will be listed as: 

1698 

1699 property or alias = value 

1700 

1701 e.g.: 

1702 

1703 linewidth or lw = 2 

1704 

1705 See Also 

1706 -------- 

1707 setp 

1708 """ 

1709 if property is None: 

1710 insp = ArtistInspector(obj) 

1711 ret = insp.pprint_getters() 

1712 print('\n'.join(ret)) 

1713 return 

1714 return getattr(obj, 'get_' + property)() 

1715 

1716# alias 

1717get = getp 

1718 

1719 

1720def setp(obj, *args, file=None, **kwargs): 

1721 """ 

1722 Set one or more properties on an `.Artist`, or list allowed values. 

1723 

1724 Parameters 

1725 ---------- 

1726 obj : `.Artist` or list of `.Artist` 

1727 The artist(s) whose properties are being set or queried. When setting 

1728 properties, all artists are affected; when querying the allowed values, 

1729 only the first instance in the sequence is queried. 

1730 

1731 For example, two lines can be made thicker and red with a single call: 

1732 

1733 >>> x = arange(0, 1, 0.01) 

1734 >>> lines = plot(x, sin(2*pi*x), x, sin(4*pi*x)) 

1735 >>> setp(lines, linewidth=2, color='r') 

1736 

1737 file : file-like, default: `sys.stdout` 

1738 Where `setp` writes its output when asked to list allowed values. 

1739 

1740 >>> with open('output.log') as file: 

1741 ... setp(line, file=file) 

1742 

1743 The default, ``None``, means `sys.stdout`. 

1744 

1745 *args, **kwargs 

1746 The properties to set. The following combinations are supported: 

1747 

1748 - Set the linestyle of a line to be dashed: 

1749 

1750 >>> line, = plot([1, 2, 3]) 

1751 >>> setp(line, linestyle='--') 

1752 

1753 - Set multiple properties at once: 

1754 

1755 >>> setp(line, linewidth=2, color='r') 

1756 

1757 - List allowed values for a line's linestyle: 

1758 

1759 >>> setp(line, 'linestyle') 

1760 linestyle: {'-', '--', '-.', ':', '', (offset, on-off-seq), ...} 

1761 

1762 - List all properties that can be set, and their allowed values: 

1763 

1764 >>> setp(line) 

1765 agg_filter: a filter function, ... 

1766 [long output listing omitted] 

1767 

1768 `setp` also supports MATLAB style string/value pairs. For example, the 

1769 following are equivalent: 

1770 

1771 >>> setp(lines, 'linewidth', 2, 'color', 'r') # MATLAB style 

1772 >>> setp(lines, linewidth=2, color='r') # Python style 

1773 

1774 See Also 

1775 -------- 

1776 getp 

1777 """ 

1778 

1779 if isinstance(obj, Artist): 

1780 objs = [obj] 

1781 else: 

1782 objs = list(cbook.flatten(obj)) 

1783 

1784 if not objs: 

1785 return 

1786 

1787 insp = ArtistInspector(objs[0]) 

1788 

1789 if not kwargs and len(args) < 2: 

1790 if args: 

1791 print(insp.pprint_setters(prop=args[0]), file=file) 

1792 else: 

1793 print('\n'.join(insp.pprint_setters()), file=file) 

1794 return 

1795 

1796 if len(args) % 2: 

1797 raise ValueError('The set args must be string, value pairs') 

1798 

1799 funcvals = dict(zip(args[::2], args[1::2])) 

1800 ret = [o.update(funcvals) for o in objs] + [o.set(**kwargs) for o in objs] 

1801 return list(cbook.flatten(ret)) 

1802 

1803 

1804def kwdoc(artist): 

1805 r""" 

1806 Inspect an `~matplotlib.artist.Artist` class (using `.ArtistInspector`) and 

1807 return information about its settable properties and their current values. 

1808 

1809 Parameters 

1810 ---------- 

1811 artist : `~matplotlib.artist.Artist` or an iterable of `Artist`\s 

1812 

1813 Returns 

1814 ------- 

1815 str 

1816 The settable properties of *artist*, as plain text if 

1817 :rc:`docstring.hardcopy` is False and as a rst table (intended for 

1818 use in Sphinx) if it is True. 

1819 """ 

1820 ai = ArtistInspector(artist) 

1821 return ('\n'.join(ai.pprint_setters_rest(leadingspace=4)) 

1822 if mpl.rcParams['docstring.hardcopy'] else 

1823 'Properties:\n' + '\n'.join(ai.pprint_setters(leadingspace=4))) 

1824 

1825# We defer this to the end of them module, because it needs ArtistInspector 

1826# to be defined. 

1827Artist._update_set_signature_and_docstring()