1from collections import OrderedDict, namedtuple 2from functools import wraps 3import inspect 4import logging 5from numbers import Number 6import re 7import warnings 8 9import numpy as np 10 11import matplotlib as mpl 12from . import _api, cbook, docstring 13from .path import Path 14from .transforms import (Bbox, IdentityTransform, Transform, TransformedBbox, 15 TransformedPatchPath, TransformedPath) 16 17_log = logging.getLogger(__name__) 18 19 20def allow_rasterization(draw): 21 """ 22 Decorator for Artist.draw method. Provides routines 23 that run before and after the draw call. The before and after functions 24 are useful for changing artist-dependent renderer attributes or making 25 other setup function calls, such as starting and flushing a mixed-mode 26 renderer. 27 """ 28 29 # Axes has a second (deprecated) argument inframe for its draw method. 30 # args and kwargs are deprecated, but we don't wrap this in 31 # _api.delete_parameter for performance; the relevant deprecation 32 # warning will be emitted by the inner draw() call. 33 @wraps(draw) 34 def draw_wrapper(artist, renderer, *args, **kwargs): 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, *args, **kwargs) 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 Artist: 91 """ 92 Abstract base class for objects that render into a FigureCanvas. 93 94 Typically, all visible elements in a figure are subclasses of Artist. 95 """ 96 97 zorder = 0 98 99 def __init__(self): 100 self._stale = True 101 self.stale_callback = None 102 self._axes = None 103 self.figure = None 104 105 self._transform = None 106 self._transformSet = False 107 self._visible = True 108 self._animated = False 109 self._alpha = None 110 self.clipbox = None 111 self._clippath = None 112 self._clipon = True 113 self._label = '' 114 self._picker = None 115 self._contains = None 116 self._rasterized = False 117 self._agg_filter = None 118 # Normally, artist classes need to be queried for mouseover info if and 119 # only if they override get_cursor_data. 120 self._mouseover = type(self).get_cursor_data != Artist.get_cursor_data 121 self._callbacks = cbook.CallbackRegistry() 122 try: 123 self.axes = None 124 except AttributeError: 125 # Handle self.axes as a read-only property, as in Figure. 126 pass 127 self._remove_method = None 128 self._url = None 129 self._gid = None 130 self._snap = None 131 self._sketch = mpl.rcParams['path.sketch'] 132 self._path_effects = mpl.rcParams['path.effects'] 133 self._sticky_edges = _XYPair([], []) 134 self._in_layout = True 135 136 def __getstate__(self): 137 d = self.__dict__.copy() 138 # remove the unpicklable remove method, this will get re-added on load 139 # (by the axes) if the artist lives on an axes. 140 d['stale_callback'] = None 141 return d 142 143 def remove(self): 144 """ 145 Remove the artist from the figure if possible. 146 147 The effect will not be visible until the figure is redrawn, e.g., 148 with `.FigureCanvasBase.draw_idle`. Call `~.axes.Axes.relim` to 149 update the axes limits if desired. 150 151 Note: `~.axes.Axes.relim` will not see collections even if the 152 collection was added to the axes with *autolim* = True. 153 154 Note: there is no support for removing the artist's legend entry. 155 """ 156 157 # There is no method to set the callback. Instead the parent should 158 # set the _remove_method attribute directly. This would be a 159 # protected attribute if Python supported that sort of thing. The 160 # callback has one parameter, which is the child to be removed. 161 if self._remove_method is not None: 162 self._remove_method(self) 163 # clear stale callback 164 self.stale_callback = None 165 _ax_flag = False 166 if hasattr(self, 'axes') and self.axes: 167 # remove from the mouse hit list 168 self.axes._mouseover_set.discard(self) 169 # mark the axes as stale 170 self.axes.stale = True 171 # decouple the artist from the axes 172 self.axes = None 173 _ax_flag = True 174 175 if self.figure: 176 self.figure = None 177 if not _ax_flag: 178 self.figure = True 179 180 else: 181 raise NotImplementedError('cannot remove artist') 182 # TODO: the fix for the collections relim problem is to move the 183 # limits calculation into the artist itself, including the property of 184 # whether or not the artist should affect the limits. Then there will 185 # be no distinction between axes.add_line, axes.add_patch, etc. 186 # TODO: add legend support 187 188 def have_units(self): 189 """Return whether units are set on any axis.""" 190 ax = self.axes 191 return ax and any(axis.have_units() for axis in ax._get_axis_list()) 192 193 def convert_xunits(self, x): 194 """ 195 Convert *x* using the unit type of the xaxis. 196 197 If the artist is not in contained in an Axes or if the xaxis does not 198 have units, *x* itself is returned. 199 """ 200 ax = getattr(self, 'axes', None) 201 if ax is None or ax.xaxis is None: 202 return x 203 return ax.xaxis.convert_units(x) 204 205 def convert_yunits(self, y): 206 """ 207 Convert *y* using the unit type of the yaxis. 208 209 If the artist is not in contained in an Axes or if the yaxis does not 210 have units, *y* itself is returned. 211 """ 212 ax = getattr(self, 'axes', None) 213 if ax is None or ax.yaxis is None: 214 return y 215 return ax.yaxis.convert_units(y) 216 217 @property 218 def axes(self): 219 """The `~.axes.Axes` instance the artist resides in, or *None*.""" 220 return self._axes 221 222 @axes.setter 223 def axes(self, new_axes): 224 if (new_axes is not None and self._axes is not None 225 and new_axes != self._axes): 226 raise ValueError("Can not reset the axes. You are probably " 227 "trying to re-use an artist in more than one " 228 "Axes which is not supported") 229 self._axes = new_axes 230 if new_axes is not None and new_axes is not self: 231 self.stale_callback = _stale_axes_callback 232 233 @property 234 def stale(self): 235 """ 236 Whether the artist is 'stale' and needs to be re-drawn for the output 237 to match the internal state of the artist. 238 """ 239 return self._stale 240 241 @stale.setter 242 def stale(self, val): 243 self._stale = val 244 245 # if the artist is animated it does not take normal part in the 246 # draw stack and is not expected to be drawn as part of the normal 247 # draw loop (when not saving) so do not propagate this change 248 if self.get_animated(): 249 return 250 251 if val and self.stale_callback is not None: 252 self.stale_callback(self, val) 253 254 def get_window_extent(self, renderer): 255 """ 256 Get the axes bounding box in display space. 257 258 The bounding box' width and height are nonnegative. 259 260 Subclasses should override for inclusion in the bounding box 261 "tight" calculation. Default is to return an empty bounding 262 box at 0, 0. 263 264 Be careful when using this function, the results will not update 265 if the artist window extent of the artist changes. The extent 266 can change due to any changes in the transform stack, such as 267 changing the axes limits, the figure size, or the canvas used 268 (as is done when saving a figure). This can lead to unexpected 269 behavior where interactive figures will look fine on the screen, 270 but will save incorrectly. 271 """ 272 return Bbox([[0, 0], [0, 0]]) 273 274 def _get_clipping_extent_bbox(self): 275 """ 276 Return a bbox with the extents of the intersection of the clip_path 277 and clip_box for this artist, or None if both of these are 278 None, or ``get_clip_on`` is False. 279 """ 280 bbox = None 281 if self.get_clip_on(): 282 clip_box = self.get_clip_box() 283 if clip_box is not None: 284 bbox = clip_box 285 clip_path = self.get_clip_path() 286 if clip_path is not None and bbox is not None: 287 clip_path = clip_path.get_fully_transformed_path() 288 bbox = Bbox.intersection(bbox, clip_path.get_extents()) 289 return bbox 290 291 def get_tightbbox(self, renderer): 292 """ 293 Like `.Artist.get_window_extent`, but includes any clipping. 294 295 Parameters 296 ---------- 297 renderer : `.RendererBase` subclass 298 renderer that will be used to draw the figures (i.e. 299 ``fig.canvas.get_renderer()``) 300 301 Returns 302 ------- 303 `.Bbox` 304 The enclosing bounding box (in figure pixel coordinates). 305 """ 306 bbox = self.get_window_extent(renderer) 307 if self.get_clip_on(): 308 clip_box = self.get_clip_box() 309 if clip_box is not None: 310 bbox = Bbox.intersection(bbox, clip_box) 311 clip_path = self.get_clip_path() 312 if clip_path is not None and bbox is not None: 313 clip_path = clip_path.get_fully_transformed_path() 314 bbox = Bbox.intersection(bbox, clip_path.get_extents()) 315 return bbox 316 317 def add_callback(self, func): 318 """ 319 Add a callback function that will be called whenever one of the 320 `.Artist`'s properties changes. 321 322 Parameters 323 ---------- 324 func : callable 325 The callback function. It must have the signature:: 326 327 def func(artist: Artist) -> Any 328 329 where *artist* is the calling `.Artist`. Return values may exist 330 but are ignored. 331 332 Returns 333 ------- 334 int 335 The observer id associated with the callback. This id can be 336 used for removing the callback with `.remove_callback` later. 337 338 See Also 339 -------- 340 remove_callback 341 """ 342 # Wrapping func in a lambda ensures it can be connected multiple times 343 # and never gets weakref-gc'ed. 344 return self._callbacks.connect("pchanged", lambda: func(self)) 345 346 def remove_callback(self, oid): 347 """ 348 Remove a callback based on its observer id. 349 350 See Also 351 -------- 352 add_callback 353 """ 354 self._callbacks.disconnect(oid) 355 356 def pchanged(self): 357 """ 358 Call all of the registered callbacks. 359 360 This function is triggered internally when a property is changed. 361 362 See Also 363 -------- 364 add_callback 365 remove_callback 366 """ 367 self._callbacks.process("pchanged") 368 369 def is_transform_set(self): 370 """ 371 Return whether the Artist has an explicitly set transform. 372 373 This is *True* after `.set_transform` has been called. 374 """ 375 return self._transformSet 376 377 def set_transform(self, t): 378 """ 379 Set the artist transform. 380 381 Parameters 382 ---------- 383 t : `.Transform` 384 """ 385 self._transform = t 386 self._transformSet = True 387 self.pchanged() 388 self.stale = True 389 390 def get_transform(self): 391 """Return the `.Transform` instance used by this artist.""" 392 if self._transform is None: 393 self._transform = IdentityTransform() 394 elif (not isinstance(self._transform, Transform) 395 and hasattr(self._transform, '_as_mpl_transform')): 396 self._transform = self._transform._as_mpl_transform(self.axes) 397 return self._transform 398 399 def get_children(self): 400 r"""Return a list of the child `.Artist`\s of this `.Artist`.""" 401 return [] 402 403 def _default_contains(self, mouseevent, figure=None): 404 """ 405 Base impl. for checking whether a mouseevent happened in an artist. 406 407 1. If the artist defines a custom checker, use it (deprecated). 408 2. If the artist figure is known and the event did not occur in that 409 figure (by checking its ``canvas`` attribute), reject it. 410 3. Otherwise, return `None, {}`, indicating that the subclass' 411 implementation should be used. 412 413 Subclasses should start their definition of `contains` as follows: 414 415 inside, info = self._default_contains(mouseevent) 416 if inside is not None: 417 return inside, info 418 # subclass-specific implementation follows 419 420 The *figure* kwarg is provided for the implementation of 421 `.Figure.contains`. 422 """ 423 if callable(self._contains): 424 return self._contains(self, mouseevent) 425 if figure is not None and mouseevent.canvas is not figure.canvas: 426 return False, {} 427 return None, {} 428 429 def contains(self, mouseevent): 430 """ 431 Test whether the artist contains the mouse event. 432 433 Parameters 434 ---------- 435 mouseevent : `matplotlib.backend_bases.MouseEvent` 436 437 Returns 438 ------- 439 contains : bool 440 Whether any values are within the radius. 441 details : dict 442 An artist-specific dictionary of details of the event context, 443 such as which points are contained in the pick radius. See the 444 individual Artist subclasses for details. 445 """ 446 inside, info = self._default_contains(mouseevent) 447 if inside is not None: 448 return inside, info 449 _log.warning("%r needs 'contains' method", self.__class__.__name__) 450 return False, {} 451 452 @_api.deprecated("3.3", alternative="set_picker") 453 def set_contains(self, picker): 454 """ 455 Define a custom contains test for the artist. 456 457 The provided callable replaces the default `.contains` method 458 of the artist. 459 460 Parameters 461 ---------- 462 picker : callable 463 A custom picker function to evaluate if an event is within the 464 artist. The function must have the signature:: 465 466 def contains(artist: Artist, event: MouseEvent) -> bool, dict 467 468 that returns: 469 470 - a bool indicating if the event is within the artist 471 - a dict of additional information. The dict should at least 472 return the same information as the default ``contains()`` 473 implementation of the respective artist, but may provide 474 additional information. 475 """ 476 if not callable(picker): 477 raise TypeError("picker is not a callable") 478 self._contains = picker 479 480 @_api.deprecated("3.3", alternative="get_picker") 481 def get_contains(self): 482 """ 483 Return the custom contains function of the artist if set, or *None*. 484 485 See Also 486 -------- 487 set_contains 488 """ 489 return self._contains 490 491 def pickable(self): 492 """ 493 Return whether the artist is pickable. 494 495 See Also 496 -------- 497 set_picker, get_picker, pick 498 """ 499 return self.figure is not None and self._picker is not None 500 501 def pick(self, mouseevent): 502 """ 503 Process a pick event. 504 505 Each child artist will fire a pick event if *mouseevent* is over 506 the artist and the artist has picker set. 507 508 See Also 509 -------- 510 set_picker, get_picker, pickable 511 """ 512 # Pick self 513 if self.pickable(): 514 picker = self.get_picker() 515 if callable(picker): 516 inside, prop = picker(self, mouseevent) 517 else: 518 inside, prop = self.contains(mouseevent) 519 if inside: 520 self.figure.canvas.pick_event(mouseevent, self, **prop) 521 522 # Pick children 523 for a in self.get_children(): 524 # make sure the event happened in the same axes 525 ax = getattr(a, 'axes', None) 526 if (mouseevent.inaxes is None or ax is None 527 or mouseevent.inaxes == ax): 528 # we need to check if mouseevent.inaxes is None 529 # because some objects associated with an axes (e.g., a 530 # tick label) can be outside the bounding box of the 531 # axes and inaxes will be None 532 # also check that ax is None so that it traverse objects 533 # which do no have an axes property but children might 534 a.pick(mouseevent) 535 536 def set_picker(self, picker): 537 """ 538 Define the picking behavior of the artist. 539 540 Parameters 541 ---------- 542 picker : None or bool or float or callable 543 This can be one of the following: 544 545 - *None*: Picking is disabled for this artist (default). 546 547 - A boolean: If *True* then picking will be enabled and the 548 artist will fire a pick event if the mouse event is over 549 the artist. 550 551 - A float: If picker is a number it is interpreted as an 552 epsilon tolerance in points and the artist will fire 553 off an event if its data is within epsilon of the mouse 554 event. For some artists like lines and patch collections, 555 the artist may provide additional data to the pick event 556 that is generated, e.g., the indices of the data within 557 epsilon of the pick event 558 559 - A function: If picker is callable, it is a user supplied 560 function which determines whether the artist is hit by the 561 mouse event:: 562 563 hit, props = picker(artist, mouseevent) 564 565 to determine the hit test. if the mouse event is over the 566 artist, return *hit=True* and props is a dictionary of 567 properties you want added to the PickEvent attributes. 568 """ 569 self._picker = picker 570 571 def get_picker(self): 572 """ 573 Return the picking behavior of the artist. 574 575 The possible values are described in `.set_picker`. 576 577 See Also 578 -------- 579 set_picker, pickable, pick 580 """ 581 return self._picker 582 583 def get_url(self): 584 """Return the url.""" 585 return self._url 586 587 def set_url(self, url): 588 """ 589 Set the url for the artist. 590 591 Parameters 592 ---------- 593 url : str 594 """ 595 self._url = url 596 597 def get_gid(self): 598 """Return the group id.""" 599 return self._gid 600 601 def set_gid(self, gid): 602 """ 603 Set the (group) id for the artist. 604 605 Parameters 606 ---------- 607 gid : str 608 """ 609 self._gid = gid 610 611 def get_snap(self): 612 """ 613 Return the snap setting. 614 615 See `.set_snap` for details. 616 """ 617 if mpl.rcParams['path.snap']: 618 return self._snap 619 else: 620 return False 621 622 def set_snap(self, snap): 623 """ 624 Set the snapping behavior. 625 626 Snapping aligns positions with the pixel grid, which results in 627 clearer images. For example, if a black line of 1px width was 628 defined at a position in between two pixels, the resulting image 629 would contain the interpolated value of that line in the pixel grid, 630 which would be a grey value on both adjacent pixel positions. In 631 contrast, snapping will move the line to the nearest integer pixel 632 value, so that the resulting image will really contain a 1px wide 633 black line. 634 635 Snapping is currently only supported by the Agg and MacOSX backends. 636 637 Parameters 638 ---------- 639 snap : bool or None 640 Possible values: 641 642 - *True*: Snap vertices to the nearest pixel center. 643 - *False*: Do not modify vertex positions. 644 - *None*: (auto) If the path contains only rectilinear line 645 segments, round to the nearest pixel center. 646 """ 647 self._snap = snap 648 self.stale = True 649 650 def get_sketch_params(self): 651 """ 652 Return the sketch parameters for the artist. 653 654 Returns 655 ------- 656 tuple or None 657 658 A 3-tuple with the following elements: 659 660 - *scale*: The amplitude of the wiggle perpendicular to the 661 source line. 662 - *length*: The length of the wiggle along the line. 663 - *randomness*: The scale factor by which the length is 664 shrunken or expanded. 665 666 Returns *None* if no sketch parameters were set. 667 """ 668 return self._sketch 669 670 def set_sketch_params(self, scale=None, length=None, randomness=None): 671 """ 672 Set the sketch parameters. 673 674 Parameters 675 ---------- 676 scale : float, optional 677 The amplitude of the wiggle perpendicular to the source 678 line, in pixels. If scale is `None`, or not provided, no 679 sketch filter will be provided. 680 length : float, optional 681 The length of the wiggle along the line, in pixels 682 (default 128.0) 683 randomness : float, optional 684 The scale factor by which the length is shrunken or 685 expanded (default 16.0) 686 687 .. ACCEPTS: (scale: float, length: float, randomness: float) 688 """ 689 if scale is None: 690 self._sketch = None 691 else: 692 self._sketch = (scale, length or 128.0, randomness or 16.0) 693 self.stale = True 694 695 def set_path_effects(self, path_effects): 696 """ 697 Set the path effects. 698 699 Parameters 700 ---------- 701 path_effects : `.AbstractPathEffect` 702 """ 703 self._path_effects = path_effects 704 self.stale = True 705 706 def get_path_effects(self): 707 return self._path_effects 708 709 def get_figure(self): 710 """Return the `.Figure` instance the artist belongs to.""" 711 return self.figure 712 713 def set_figure(self, fig): 714 """ 715 Set the `.Figure` instance the artist belongs to. 716 717 Parameters 718 ---------- 719 fig : `.Figure` 720 """ 721 # if this is a no-op just return 722 if self.figure is fig: 723 return 724 # if we currently have a figure (the case of both `self.figure` 725 # and *fig* being none is taken care of above) we then user is 726 # trying to change the figure an artist is associated with which 727 # is not allowed for the same reason as adding the same instance 728 # to more than one Axes 729 if self.figure is not None: 730 raise RuntimeError("Can not put single artist in " 731 "more than one figure") 732 self.figure = fig 733 if self.figure and self.figure is not self: 734 self.pchanged() 735 self.stale = True 736 737 def set_clip_box(self, clipbox): 738 """ 739 Set the artist's clip `.Bbox`. 740 741 Parameters 742 ---------- 743 clipbox : `.Bbox` 744 """ 745 self.clipbox = clipbox 746 self.pchanged() 747 self.stale = True 748 749 def set_clip_path(self, path, transform=None): 750 """ 751 Set the artist's clip path. 752 753 Parameters 754 ---------- 755 path : `.Patch` or `.Path` or `.TransformedPath` or None 756 The clip path. If given a `.Path`, *transform* must be provided as 757 well. If *None*, a previously set clip path is removed. 758 transform : `~matplotlib.transforms.Transform`, optional 759 Only used if *path* is a `.Path`, in which case the given `.Path` 760 is converted to a `.TransformedPath` using *transform*. 761 762 Notes 763 ----- 764 For efficiency, if *path* is a `.Rectangle` this method will set the 765 clipping box to the corresponding rectangle and set the clipping path 766 to ``None``. 767 768 For technical reasons (support of `~.Artist.set`), a tuple 769 (*path*, *transform*) is also accepted as a single positional 770 parameter. 771 772 .. ACCEPTS: Patch or (Path, Transform) or None 773 """ 774 from matplotlib.patches import Patch, Rectangle 775 776 success = False 777 if transform is None: 778 if isinstance(path, Rectangle): 779 self.clipbox = TransformedBbox(Bbox.unit(), 780 path.get_transform()) 781 self._clippath = None 782 success = True 783 elif isinstance(path, Patch): 784 self._clippath = TransformedPatchPath(path) 785 success = True 786 elif isinstance(path, tuple): 787 path, transform = path 788 789 if path is None: 790 self._clippath = None 791 success = True 792 elif isinstance(path, Path): 793 self._clippath = TransformedPath(path, transform) 794 success = True 795 elif isinstance(path, TransformedPatchPath): 796 self._clippath = path 797 success = True 798 elif isinstance(path, TransformedPath): 799 self._clippath = path 800 success = True 801 802 if not success: 803 raise TypeError( 804 "Invalid arguments to set_clip_path, of type {} and {}" 805 .format(type(path).__name__, type(transform).__name__)) 806 # This may result in the callbacks being hit twice, but guarantees they 807 # will be hit at least once. 808 self.pchanged() 809 self.stale = True 810 811 def get_alpha(self): 812 """ 813 Return the alpha value used for blending - not supported on all 814 backends. 815 """ 816 return self._alpha 817 818 def get_visible(self): 819 """Return the visibility.""" 820 return self._visible 821 822 def get_animated(self): 823 """Return whether the artist is animated.""" 824 return self._animated 825 826 def get_in_layout(self): 827 """ 828 Return boolean flag, ``True`` if artist is included in layout 829 calculations. 830 831 E.g. :doc:`/tutorials/intermediate/constrainedlayout_guide`, 832 `.Figure.tight_layout()`, and 833 ``fig.savefig(fname, bbox_inches='tight')``. 834 """ 835 return self._in_layout 836 837 def get_clip_on(self): 838 """Return whether the artist uses clipping.""" 839 return self._clipon 840 841 def get_clip_box(self): 842 """Return the clipbox.""" 843 return self.clipbox 844 845 def get_clip_path(self): 846 """Return the clip path.""" 847 return self._clippath 848 849 def get_transformed_clip_path_and_affine(self): 850 """ 851 Return the clip path with the non-affine part of its 852 transformation applied, and the remaining affine part of its 853 transformation. 854 """ 855 if self._clippath is not None: 856 return self._clippath.get_transformed_path_and_affine() 857 return None, None 858 859 def set_clip_on(self, b): 860 """ 861 Set whether the artist uses clipping. 862 863 When False artists will be visible outside of the axes which 864 can lead to unexpected results. 865 866 Parameters 867 ---------- 868 b : bool 869 """ 870 self._clipon = b 871 # This may result in the callbacks being hit twice, but ensures they 872 # are hit at least once 873 self.pchanged() 874 self.stale = True 875 876 def _set_gc_clip(self, gc): 877 """Set the clip properly for the gc.""" 878 if self._clipon: 879 if self.clipbox is not None: 880 gc.set_clip_rectangle(self.clipbox) 881 gc.set_clip_path(self._clippath) 882 else: 883 gc.set_clip_rectangle(None) 884 gc.set_clip_path(None) 885 886 def get_rasterized(self): 887 """Return whether the artist is to be rasterized.""" 888 return self._rasterized 889 890 def set_rasterized(self, rasterized): 891 """ 892 Force rasterized (bitmap) drawing for vector graphics output. 893 894 Rasterized drawing is not supported by all artists. If you try to 895 enable this on an artist that does not support it, the command has no 896 effect and a warning will be issued. 897 898 This setting is ignored for pixel-based output. 899 900 See also :doc:`/gallery/misc/rasterization_demo`. 901 902 Parameters 903 ---------- 904 rasterized : bool 905 """ 906 if rasterized and not hasattr(self.draw, "_supports_rasterization"): 907 _api.warn_external(f"Rasterization of '{self}' will be ignored") 908 909 self._rasterized = rasterized 910 911 def get_agg_filter(self): 912 """Return filter function to be used for agg filter.""" 913 return self._agg_filter 914 915 def set_agg_filter(self, filter_func): 916 """ 917 Set the agg filter. 918 919 Parameters 920 ---------- 921 filter_func : callable 922 A filter function, which takes a (m, n, 3) float array and a dpi 923 value, and returns a (m, n, 3) array. 924 925 .. ACCEPTS: a filter function, which takes a (m, n, 3) float array 926 and a dpi value, and returns a (m, n, 3) array 927 """ 928 self._agg_filter = filter_func 929 self.stale = True 930 931 @_api.delete_parameter("3.3", "args") 932 @_api.delete_parameter("3.3", "kwargs") 933 def draw(self, renderer, *args, **kwargs): 934 """ 935 Draw the Artist (and its children) using the given renderer. 936 937 This has no effect if the artist is not visible (`.Artist.get_visible` 938 returns False). 939 940 Parameters 941 ---------- 942 renderer : `.RendererBase` subclass. 943 944 Notes 945 ----- 946 This method is overridden in the Artist subclasses. 947 """ 948 if not self.get_visible(): 949 return 950 self.stale = False 951 952 def set_alpha(self, alpha): 953 """ 954 Set the alpha value used for blending - not supported on all backends. 955 956 Parameters 957 ---------- 958 alpha : scalar or None 959 *alpha* must be within the 0-1 range, inclusive. 960 """ 961 if alpha is not None and not isinstance(alpha, Number): 962 raise TypeError( 963 f'alpha must be numeric or None, not {type(alpha)}') 964 if alpha is not None and not (0 <= alpha <= 1): 965 raise ValueError(f'alpha ({alpha}) is outside 0-1 range') 966 self._alpha = alpha 967 self.pchanged() 968 self.stale = True 969 970 def _set_alpha_for_array(self, alpha): 971 """ 972 Set the alpha value used for blending - not supported on all backends. 973 974 Parameters 975 ---------- 976 alpha : array-like or scalar or None 977 All values must be within the 0-1 range, inclusive. 978 Masked values and nans are not supported. 979 """ 980 if isinstance(alpha, str): 981 raise TypeError("alpha must be numeric or None, not a string") 982 if not np.iterable(alpha): 983 Artist.set_alpha(self, alpha) 984 return 985 alpha = np.asarray(alpha) 986 if not (0 <= alpha.min() and alpha.max() <= 1): 987 raise ValueError('alpha must be between 0 and 1, inclusive, ' 988 f'but min is {alpha.min()}, max is {alpha.max()}') 989 self._alpha = alpha 990 self.pchanged() 991 self.stale = True 992 993 def set_visible(self, b): 994 """ 995 Set the artist's visibility. 996 997 Parameters 998 ---------- 999 b : bool 1000 """ 1001 self._visible = b 1002 self.pchanged() 1003 self.stale = True 1004 1005 def set_animated(self, b): 1006 """ 1007 Set whether the artist is intended to be used in an animation. 1008 1009 If True, the artist is excluded from regular drawing of the figure. 1010 You have to call `.Figure.draw_artist` / `.Axes.draw_artist` 1011 explicitly on the artist. This appoach is used to speed up animations 1012 using blitting. 1013 1014 See also `matplotlib.animation` and 1015 :doc:`/tutorials/advanced/blitting`. 1016 1017 Parameters 1018 ---------- 1019 b : bool 1020 """ 1021 if self._animated != b: 1022 self._animated = b 1023 self.pchanged() 1024 1025 def set_in_layout(self, in_layout): 1026 """ 1027 Set if artist is to be included in layout calculations, 1028 E.g. :doc:`/tutorials/intermediate/constrainedlayout_guide`, 1029 `.Figure.tight_layout()`, and 1030 ``fig.savefig(fname, bbox_inches='tight')``. 1031 1032 Parameters 1033 ---------- 1034 in_layout : bool 1035 """ 1036 self._in_layout = in_layout 1037 1038 def update(self, props): 1039 """ 1040 Update this artist's properties from the dict *props*. 1041 1042 Parameters 1043 ---------- 1044 props : dict 1045 """ 1046 ret = [] 1047 with cbook._setattr_cm(self, eventson=False): 1048 for k, v in props.items(): 1049 if k != k.lower(): 1050 _api.warn_deprecated( 1051 "3.3", message="Case-insensitive properties were " 1052 "deprecated in %(since)s and support will be removed " 1053 "%(removal)s") 1054 k = k.lower() 1055 # White list attributes we want to be able to update through 1056 # art.update, art.set, setp. 1057 if k == "axes": 1058 ret.append(setattr(self, k, v)) 1059 else: 1060 func = getattr(self, f"set_{k}", None) 1061 if not callable(func): 1062 raise AttributeError(f"{type(self).__name__!r} object " 1063 f"has no property {k!r}") 1064 ret.append(func(v)) 1065 if ret: 1066 self.pchanged() 1067 self.stale = True 1068 return ret 1069 1070 def get_label(self): 1071 """Return the label used for this artist in the legend.""" 1072 return self._label 1073 1074 def set_label(self, s): 1075 """ 1076 Set a label that will be displayed in the legend. 1077 1078 Parameters 1079 ---------- 1080 s : object 1081 *s* will be converted to a string by calling `str`. 1082 """ 1083 if s is not None: 1084 self._label = str(s) 1085 else: 1086 self._label = None 1087 self.pchanged() 1088 self.stale = True 1089 1090 def get_zorder(self): 1091 """Return the artist's zorder.""" 1092 return self.zorder 1093 1094 def set_zorder(self, level): 1095 """ 1096 Set the zorder for the artist. Artists with lower zorder 1097 values are drawn first. 1098 1099 Parameters 1100 ---------- 1101 level : float 1102 """ 1103 if level is None: 1104 level = self.__class__.zorder 1105 self.zorder = level 1106 self.pchanged() 1107 self.stale = True 1108 1109 @property 1110 def sticky_edges(self): 1111 """ 1112 ``x`` and ``y`` sticky edge lists for autoscaling. 1113 1114 When performing autoscaling, if a data limit coincides with a value in 1115 the corresponding sticky_edges list, then no margin will be added--the 1116 view limit "sticks" to the edge. A typical use case is histograms, 1117 where one usually expects no margin on the bottom edge (0) of the 1118 histogram. 1119 1120 This attribute cannot be assigned to; however, the ``x`` and ``y`` 1121 lists can be modified in place as needed. 1122 1123 Examples 1124 -------- 1125 >>> artist.sticky_edges.x[:] = (xmin, xmax) 1126 >>> artist.sticky_edges.y[:] = (ymin, ymax) 1127 1128 """ 1129 return self._sticky_edges 1130 1131 def update_from(self, other): 1132 """Copy properties from *other* to *self*.""" 1133 self._transform = other._transform 1134 self._transformSet = other._transformSet 1135 self._visible = other._visible 1136 self._alpha = other._alpha 1137 self.clipbox = other.clipbox 1138 self._clipon = other._clipon 1139 self._clippath = other._clippath 1140 self._label = other._label 1141 self._sketch = other._sketch 1142 self._path_effects = other._path_effects 1143 self.sticky_edges.x[:] = other.sticky_edges.x.copy() 1144 self.sticky_edges.y[:] = other.sticky_edges.y.copy() 1145 self.pchanged() 1146 self.stale = True 1147 1148 def properties(self): 1149 """Return a dictionary of all the properties of the artist.""" 1150 return ArtistInspector(self).properties() 1151 1152 def set(self, **kwargs): 1153 """A property batch setter. Pass *kwargs* to set properties.""" 1154 kwargs = cbook.normalize_kwargs(kwargs, self) 1155 move_color_to_start = False 1156 if "color" in kwargs: 1157 keys = [*kwargs] 1158 i_color = keys.index("color") 1159 props = ["edgecolor", "facecolor"] 1160 if any(tp.__module__ == "matplotlib.collections" 1161 and tp.__name__ == "Collection" 1162 for tp in type(self).__mro__): 1163 props.append("alpha") 1164 for other in props: 1165 if other not in keys: 1166 continue 1167 i_other = keys.index(other) 1168 if i_other < i_color: 1169 move_color_to_start = True 1170 _api.warn_deprecated( 1171 "3.3", message=f"You have passed the {other!r} kwarg " 1172 "before the 'color' kwarg. Artist.set() currently " 1173 "reorders the properties to apply 'color' first, but " 1174 "this is deprecated since %(since)s and will be " 1175 "removed %(removal)s; please pass 'color' first " 1176 "instead.") 1177 if move_color_to_start: 1178 kwargs = {"color": kwargs.pop("color"), **kwargs} 1179 return self.update(kwargs) 1180 1181 def findobj(self, match=None, include_self=True): 1182 """ 1183 Find artist objects. 1184 1185 Recursively find all `.Artist` instances contained in the artist. 1186 1187 Parameters 1188 ---------- 1189 match 1190 A filter criterion for the matches. This can be 1191 1192 - *None*: Return all objects contained in artist. 1193 - A function with signature ``def match(artist: Artist) -> bool``. 1194 The result will only contain artists for which the function 1195 returns *True*. 1196 - A class instance: e.g., `.Line2D`. The result will only contain 1197 artists of this class or its subclasses (``isinstance`` check). 1198 1199 include_self : bool 1200 Include *self* in the list to be checked for a match. 1201 1202 Returns 1203 ------- 1204 list of `.Artist` 1205 1206 """ 1207 if match is None: # always return True 1208 def matchfunc(x): 1209 return True 1210 elif isinstance(match, type) and issubclass(match, Artist): 1211 def matchfunc(x): 1212 return isinstance(x, match) 1213 elif callable(match): 1214 matchfunc = match 1215 else: 1216 raise ValueError('match must be None, a matplotlib.artist.Artist ' 1217 'subclass, or a callable') 1218 1219 artists = sum([c.findobj(matchfunc) for c in self.get_children()], []) 1220 if include_self and matchfunc(self): 1221 artists.append(self) 1222 return artists 1223 1224 def get_cursor_data(self, event): 1225 """ 1226 Return the cursor data for a given event. 1227 1228 .. note:: 1229 This method is intended to be overridden by artist subclasses. 1230 As an end-user of Matplotlib you will most likely not call this 1231 method yourself. 1232 1233 Cursor data can be used by Artists to provide additional context 1234 information for a given event. The default implementation just returns 1235 *None*. 1236 1237 Subclasses can override the method and return arbitrary data. However, 1238 when doing so, they must ensure that `.format_cursor_data` can convert 1239 the data to a string representation. 1240 1241 The only current use case is displaying the z-value of an `.AxesImage` 1242 in the status bar of a plot window, while moving the mouse. 1243 1244 Parameters 1245 ---------- 1246 event : `matplotlib.backend_bases.MouseEvent` 1247 1248 See Also 1249 -------- 1250 format_cursor_data 1251 1252 """ 1253 return None 1254 1255 def format_cursor_data(self, data): 1256 """ 1257 Return a string representation of *data*. 1258 1259 .. note:: 1260 This method is intended to be overridden by artist subclasses. 1261 As an end-user of Matplotlib you will most likely not call this 1262 method yourself. 1263 1264 The default implementation converts ints and floats and arrays of ints 1265 and floats into a comma-separated string enclosed in square brackets. 1266 1267 See Also 1268 -------- 1269 get_cursor_data 1270 """ 1271 try: 1272 data[0] 1273 except (TypeError, IndexError): 1274 data = [data] 1275 data_str = ', '.join('{:0.3g}'.format(item) for item in data 1276 if isinstance(item, Number)) 1277 return "[" + data_str + "]" 1278 1279 @property 1280 def mouseover(self): 1281 """ 1282 If this property is set to *True*, the artist will be queried for 1283 custom context information when the mouse cursor moves over it. 1284 1285 See also :meth:`get_cursor_data`, :class:`.ToolCursorPosition` and 1286 :class:`.NavigationToolbar2`. 1287 """ 1288 return self._mouseover 1289 1290 @mouseover.setter 1291 def mouseover(self, val): 1292 val = bool(val) 1293 self._mouseover = val 1294 ax = self.axes 1295 if ax: 1296 if val: 1297 ax._mouseover_set.add(self) 1298 else: 1299 ax._mouseover_set.discard(self) 1300 1301 1302class ArtistInspector: 1303 """ 1304 A helper class to inspect an `~matplotlib.artist.Artist` and return 1305 information about its settable properties and their current values. 1306 """ 1307 1308 def __init__(self, o): 1309 r""" 1310 Initialize the artist inspector with an `Artist` or an iterable of 1311 `Artist`\s. If an iterable is used, we assume it is a homogeneous 1312 sequence (all `Artist`\s are of the same type) and it is your 1313 responsibility to make sure this is so. 1314 """ 1315 if not isinstance(o, Artist): 1316 if np.iterable(o): 1317 o = list(o) 1318 if len(o): 1319 o = o[0] 1320 1321 self.oorig = o 1322 if not isinstance(o, type): 1323 o = type(o) 1324 self.o = o 1325 1326 self.aliasd = self.get_aliases() 1327 1328 def get_aliases(self): 1329 """ 1330 Get a dict mapping property fullnames to sets of aliases for each alias 1331 in the :class:`~matplotlib.artist.ArtistInspector`. 1332 1333 e.g., for lines:: 1334 1335 {'markerfacecolor': {'mfc'}, 1336 'linewidth' : {'lw'}, 1337 } 1338 """ 1339 names = [name for name in dir(self.o) 1340 if name.startswith(('set_', 'get_')) 1341 and callable(getattr(self.o, name))] 1342 aliases = {} 1343 for name in names: 1344 func = getattr(self.o, name) 1345 if not self.is_alias(func): 1346 continue 1347 propname = re.search("`({}.*)`".format(name[:4]), # get_.*/set_.* 1348 inspect.getdoc(func)).group(1) 1349 aliases.setdefault(propname[4:], set()).add(name[4:]) 1350 return aliases 1351 1352 _get_valid_values_regex = re.compile( 1353 r"\n\s*(?:\.\.\s+)?ACCEPTS:\s*((?:.|\n)*?)(?:$|(?:\n\n))" 1354 ) 1355 1356 def get_valid_values(self, attr): 1357 """ 1358 Get the legal arguments for the setter associated with *attr*. 1359 1360 This is done by querying the docstring of the setter for a line that 1361 begins with "ACCEPTS:" or ".. ACCEPTS:", and then by looking for a 1362 numpydoc-style documentation for the setter's first argument. 1363 """ 1364 1365 name = 'set_%s' % attr 1366 if not hasattr(self.o, name): 1367 raise AttributeError('%s has no function %s' % (self.o, name)) 1368 func = getattr(self.o, name) 1369 1370 docstring = inspect.getdoc(func) 1371 if docstring is None: 1372 return 'unknown' 1373 1374 if docstring.startswith('Alias for '): 1375 return None 1376 1377 match = self._get_valid_values_regex.search(docstring) 1378 if match is not None: 1379 return re.sub("\n *", " ", match.group(1)) 1380 1381 # Much faster than list(inspect.signature(func).parameters)[1], 1382 # although barely relevant wrt. matplotlib's total import time. 1383 param_name = func.__code__.co_varnames[1] 1384 # We could set the presence * based on whether the parameter is a 1385 # varargs (it can't be a varkwargs) but it's not really worth the it. 1386 match = re.search(r"(?m)^ *\*?{} : (.+)".format(param_name), docstring) 1387 if match: 1388 return match.group(1) 1389 1390 return 'unknown' 1391 1392 def _replace_path(self, source_class): 1393 """ 1394 Changes the full path to the public API path that is used 1395 in sphinx. This is needed for links to work. 1396 """ 1397 replace_dict = {'_base._AxesBase': 'Axes', 1398 '_axes.Axes': 'Axes'} 1399 for key, value in replace_dict.items(): 1400 source_class = source_class.replace(key, value) 1401 return source_class 1402 1403 def get_setters(self): 1404 """ 1405 Get the attribute strings with setters for object. 1406 1407 For example, for a line, return ``['markerfacecolor', 'linewidth', 1408 ....]``. 1409 """ 1410 setters = [] 1411 for name in dir(self.o): 1412 if not name.startswith('set_'): 1413 continue 1414 func = getattr(self.o, name) 1415 if (not callable(func) 1416 or len(inspect.signature(func).parameters) < 2 1417 or self.is_alias(func)): 1418 continue 1419 setters.append(name[4:]) 1420 return setters 1421 1422 def is_alias(self, o): 1423 """Return whether method object *o* is an alias for another method.""" 1424 ds = inspect.getdoc(o) 1425 if ds is None: 1426 return False 1427 return ds.startswith('Alias for ') 1428 1429 def aliased_name(self, s): 1430 """ 1431 Return 'PROPNAME or alias' if *s* has an alias, else return 'PROPNAME'. 1432 1433 e.g., for the line markerfacecolor property, which has an 1434 alias, return 'markerfacecolor or mfc' and for the transform 1435 property, which does not, return 'transform'. 1436 """ 1437 aliases = ''.join(' or %s' % x for x in sorted(self.aliasd.get(s, []))) 1438 return s + aliases 1439 1440 def aliased_name_rest(self, s, target): 1441 """ 1442 Return 'PROPNAME or alias' if *s* has an alias, else return 'PROPNAME', 1443 formatted for reST. 1444 1445 e.g., for the line markerfacecolor property, which has an 1446 alias, return 'markerfacecolor or mfc' and for the transform 1447 property, which does not, return 'transform'. 1448 """ 1449 aliases = ''.join(' or %s' % x for x in sorted(self.aliasd.get(s, []))) 1450 return ':meth:`%s <%s>`%s' % (s, target, aliases) 1451 1452 def pprint_setters(self, prop=None, leadingspace=2): 1453 """ 1454 If *prop* is *None*, return a list of strings of all settable 1455 properties and their valid values. 1456 1457 If *prop* is not *None*, it is a valid property name and that 1458 property will be returned as a string of property : valid 1459 values. 1460 """ 1461 if leadingspace: 1462 pad = ' ' * leadingspace 1463 else: 1464 pad = '' 1465 if prop is not None: 1466 accepts = self.get_valid_values(prop) 1467 return '%s%s: %s' % (pad, prop, accepts) 1468 1469 lines = [] 1470 for prop in sorted(self.get_setters()): 1471 accepts = self.get_valid_values(prop) 1472 name = self.aliased_name(prop) 1473 lines.append('%s%s: %s' % (pad, name, accepts)) 1474 return lines 1475 1476 def pprint_setters_rest(self, prop=None, leadingspace=4): 1477 """ 1478 If *prop* is *None*, return a list of reST-formatted strings of all 1479 settable properties and their valid values. 1480 1481 If *prop* is not *None*, it is a valid property name and that 1482 property will be returned as a string of "property : valid" 1483 values. 1484 """ 1485 if leadingspace: 1486 pad = ' ' * leadingspace 1487 else: 1488 pad = '' 1489 if prop is not None: 1490 accepts = self.get_valid_values(prop) 1491 return '%s%s: %s' % (pad, prop, accepts) 1492 1493 prop_and_qualnames = [] 1494 for prop in sorted(self.get_setters()): 1495 # Find the parent method which actually provides the docstring. 1496 for cls in self.o.__mro__: 1497 method = getattr(cls, f"set_{prop}", None) 1498 if method and method.__doc__ is not None: 1499 break 1500 else: # No docstring available. 1501 method = getattr(self.o, f"set_{prop}") 1502 prop_and_qualnames.append( 1503 (prop, f"{method.__module__}.{method.__qualname__}")) 1504 1505 names = [self.aliased_name_rest(prop, target) 1506 .replace('_base._AxesBase', 'Axes') 1507 .replace('_axes.Axes', 'Axes') 1508 for prop, target in prop_and_qualnames] 1509 accepts = [self.get_valid_values(prop) 1510 for prop, _ in prop_and_qualnames] 1511 1512 col0_len = max(len(n) for n in names) 1513 col1_len = max(len(a) for a in accepts) 1514 table_formatstr = pad + ' ' + '=' * col0_len + ' ' + '=' * col1_len 1515 1516 return [ 1517 '', 1518 pad + '.. table::', 1519 pad + ' :class: property-table', 1520 '', 1521 table_formatstr, 1522 pad + ' ' + 'Property'.ljust(col0_len) 1523 + ' ' + 'Description'.ljust(col1_len), 1524 table_formatstr, 1525 *[pad + ' ' + n.ljust(col0_len) + ' ' + a.ljust(col1_len) 1526 for n, a in zip(names, accepts)], 1527 table_formatstr, 1528 '', 1529 ] 1530 1531 def properties(self): 1532 """Return a dictionary mapping property name -> value.""" 1533 o = self.oorig 1534 getters = [name for name in dir(o) 1535 if name.startswith('get_') and callable(getattr(o, name))] 1536 getters.sort() 1537 d = {} 1538 for name in getters: 1539 func = getattr(o, name) 1540 if self.is_alias(func): 1541 continue 1542 try: 1543 with warnings.catch_warnings(): 1544 warnings.simplefilter('ignore') 1545 val = func() 1546 except Exception: 1547 continue 1548 else: 1549 d[name[4:]] = val 1550 return d 1551 1552 def pprint_getters(self): 1553 """Return the getters and actual values as list of strings.""" 1554 lines = [] 1555 for name, val in sorted(self.properties().items()): 1556 if getattr(val, 'shape', ()) != () and len(val) > 6: 1557 s = str(val[:6]) + '...' 1558 else: 1559 s = str(val) 1560 s = s.replace('\n', ' ') 1561 if len(s) > 50: 1562 s = s[:50] + '...' 1563 name = self.aliased_name(name) 1564 lines.append(' %s = %s' % (name, s)) 1565 return lines 1566 1567 1568def getp(obj, property=None): 1569 """ 1570 Return the value of an `.Artist`'s *property*, or print all of them. 1571 1572 Parameters 1573 ---------- 1574 obj : `.Artist` 1575 The queried artist; e.g., a `.Line2D`, a `.Text`, or an `~.axes.Axes`. 1576 1577 property : str or None, default: None 1578 If *property* is 'somename', this function returns 1579 ``obj.get_somename()``. 1580 1581 If is is None (or unset), it *prints* all gettable properties from 1582 *obj*. Many properties have aliases for shorter typing, e.g. 'lw' is 1583 an alias for 'linewidth'. In the output, aliases and full property 1584 names will be listed as: 1585 1586 property or alias = value 1587 1588 e.g.: 1589 1590 linewidth or lw = 2 1591 1592 See Also 1593 -------- 1594 setp 1595 """ 1596 if property is None: 1597 insp = ArtistInspector(obj) 1598 ret = insp.pprint_getters() 1599 print('\n'.join(ret)) 1600 return 1601 return getattr(obj, 'get_' + property)() 1602 1603# alias 1604get = getp 1605 1606 1607def setp(obj, *args, file=None, **kwargs): 1608 """ 1609 Set one or more properties on an `.Artist`, or list allowed values. 1610 1611 Parameters 1612 ---------- 1613 obj : `.Artist` or list of `.Artist` 1614 The artist(s) whose properties are being set or queried. When setting 1615 properties, all artists are affected; when querying the allowed values, 1616 only the first instance in the sequence is queried. 1617 1618 For example, two lines can be made thicker and red with a single call: 1619 1620 >>> x = arange(0, 1, 0.01) 1621 >>> lines = plot(x, sin(2*pi*x), x, sin(4*pi*x)) 1622 >>> setp(lines, linewidth=2, color='r') 1623 1624 file : file-like, default: `sys.stdout` 1625 Where `setp` writes its output when asked to list allowed values. 1626 1627 >>> with open('output.log') as file: 1628 ... setp(line, file=file) 1629 1630 The default, ``None``, means `sys.stdout`. 1631 1632 *args, **kwargs 1633 The properties to set. The following combinations are supported: 1634 1635 - Set the linestyle of a line to be dashed: 1636 1637 >>> line, = plot([1, 2, 3]) 1638 >>> setp(line, linestyle='--') 1639 1640 - Set multiple properties at once: 1641 1642 >>> setp(line, linewidth=2, color='r') 1643 1644 - List allowed values for a line's linestyle: 1645 1646 >>> setp(line, 'linestyle') 1647 linestyle: {'-', '--', '-.', ':', '', (offset, on-off-seq), ...} 1648 1649 - List all properties that can be set, and their allowed values: 1650 1651 >>> setp(line) 1652 agg_filter: a filter function, ... 1653 [long output listing omitted] 1654 1655 `setp` also supports MATLAB style string/value pairs. For example, the 1656 following are equivalent: 1657 1658 >>> setp(lines, 'linewidth', 2, 'color', 'r') # MATLAB style 1659 >>> setp(lines, linewidth=2, color='r') # Python style 1660 1661 See Also 1662 -------- 1663 getp 1664 """ 1665 1666 if isinstance(obj, Artist): 1667 objs = [obj] 1668 else: 1669 objs = list(cbook.flatten(obj)) 1670 1671 if not objs: 1672 return 1673 1674 insp = ArtistInspector(objs[0]) 1675 1676 if not kwargs and len(args) < 2: 1677 if args: 1678 print(insp.pprint_setters(prop=args[0]), file=file) 1679 else: 1680 print('\n'.join(insp.pprint_setters()), file=file) 1681 return 1682 1683 if len(args) % 2: 1684 raise ValueError('The set args must be string, value pairs') 1685 1686 # put args into ordereddict to maintain order 1687 funcvals = OrderedDict((k, v) for k, v in zip(args[::2], args[1::2])) 1688 ret = [o.update(funcvals) for o in objs] + [o.set(**kwargs) for o in objs] 1689 return list(cbook.flatten(ret)) 1690 1691 1692def kwdoc(artist): 1693 r""" 1694 Inspect an `~matplotlib.artist.Artist` class (using `.ArtistInspector`) and 1695 return information about its settable properties and their current values. 1696 1697 Parameters 1698 ---------- 1699 artist : `~matplotlib.artist.Artist` or an iterable of `Artist`\s 1700 1701 Returns 1702 ------- 1703 str 1704 The settable properties of *artist*, as plain text if 1705 :rc:`docstring.hardcopy` is False and as a rst table (intended for 1706 use in Sphinx) if it is True. 1707 """ 1708 ai = ArtistInspector(artist) 1709 return ('\n'.join(ai.pprint_setters_rest(leadingspace=4)) 1710 if mpl.rcParams['docstring.hardcopy'] else 1711 'Properties:\n' + '\n'.join(ai.pprint_setters(leadingspace=4))) 1712 1713 1714docstring.interpd.update(Artist_kwdoc=kwdoc(Artist)) 1715