1""" 2Classes for the ticks and x and y axis 3""" 4from __future__ import (absolute_import, division, print_function, 5 unicode_literals) 6 7import six 8 9import logging 10 11from matplotlib import rcParams 12import matplotlib.artist as artist 13from matplotlib.artist import allow_rasterization 14import matplotlib.cbook as cbook 15from matplotlib.cbook import _string_to_bool 16import matplotlib.font_manager as font_manager 17import matplotlib.lines as mlines 18import matplotlib.patches as mpatches 19import matplotlib.scale as mscale 20import matplotlib.text as mtext 21import matplotlib.ticker as mticker 22import matplotlib.transforms as mtransforms 23import matplotlib.units as munits 24import numpy as np 25import warnings 26 27_log = logging.getLogger(__name__) 28 29GRIDLINE_INTERPOLATION_STEPS = 180 30 31# This list is being used for compatibility with Axes.grid, which 32# allows all Line2D kwargs. 33_line_AI = artist.ArtistInspector(mlines.Line2D) 34_line_param_names = _line_AI.get_setters() 35_line_param_aliases = [list(d.keys())[0] for d in _line_AI.aliasd.values()] 36_gridline_param_names = ['grid_' + name 37 for name in _line_param_names + _line_param_aliases] 38 39 40class Tick(artist.Artist): 41 """ 42 Abstract base class for the axis ticks, grid lines and labels 43 44 1 refers to the bottom of the plot for xticks and the left for yticks 45 2 refers to the top of the plot for xticks and the right for yticks 46 47 Attributes 48 ---------- 49 tick1line : Line2D 50 51 tick2line : Line2D 52 53 gridline : Line2D 54 55 label1 : Text 56 57 label2 : Text 58 59 gridOn : bool 60 Determines whether to draw the tickline. 61 62 tick1On : bool 63 Determines whether to draw the first tickline. 64 65 tick2On : bool 66 Determines whether to draw the second tickline. 67 68 label1On : bool 69 Determines whether to draw the first tick label. 70 71 label2On : bool 72 Determines whether to draw the second tick label. 73 """ 74 def __init__(self, axes, loc, label, 75 size=None, # points 76 width=None, 77 color=None, 78 tickdir=None, 79 pad=None, 80 labelsize=None, 81 labelcolor=None, 82 zorder=None, 83 gridOn=None, # defaults to axes.grid depending on 84 # axes.grid.which 85 tick1On=True, 86 tick2On=True, 87 label1On=True, 88 label2On=False, 89 major=True, 90 labelrotation=0, 91 grid_color=None, 92 grid_linestyle=None, 93 grid_linewidth=None, 94 grid_alpha=None, 95 **kw # Other Line2D kwargs applied to gridlines. 96 ): 97 """ 98 bbox is the Bound2D bounding box in display coords of the Axes 99 loc is the tick location in data coords 100 size is the tick size in points 101 """ 102 artist.Artist.__init__(self) 103 104 if gridOn is None: 105 if major and (rcParams['axes.grid.which'] in ('both', 'major')): 106 gridOn = rcParams['axes.grid'] 107 elif (not major) and (rcParams['axes.grid.which'] 108 in ('both', 'minor')): 109 gridOn = rcParams['axes.grid'] 110 else: 111 gridOn = False 112 113 self.set_figure(axes.figure) 114 self.axes = axes 115 116 name = self.__name__.lower() 117 self._name = name 118 119 self._loc = loc 120 121 if size is None: 122 if major: 123 size = rcParams['%s.major.size' % name] 124 else: 125 size = rcParams['%s.minor.size' % name] 126 self._size = size 127 128 if width is None: 129 if major: 130 width = rcParams['%s.major.width' % name] 131 else: 132 width = rcParams['%s.minor.width' % name] 133 self._width = width 134 135 if color is None: 136 color = rcParams['%s.color' % name] 137 self._color = color 138 139 if pad is None: 140 if major: 141 pad = rcParams['%s.major.pad' % name] 142 else: 143 pad = rcParams['%s.minor.pad' % name] 144 self._base_pad = pad 145 146 if labelcolor is None: 147 labelcolor = rcParams['%s.color' % name] 148 self._labelcolor = labelcolor 149 150 if labelsize is None: 151 labelsize = rcParams['%s.labelsize' % name] 152 self._labelsize = labelsize 153 154 self._set_labelrotation(labelrotation) 155 156 if zorder is None: 157 if major: 158 zorder = mlines.Line2D.zorder + 0.01 159 else: 160 zorder = mlines.Line2D.zorder 161 self._zorder = zorder 162 163 self._grid_color = (rcParams['grid.color'] 164 if grid_color is None else grid_color) 165 self._grid_linestyle = (rcParams['grid.linestyle'] 166 if grid_linestyle is None else grid_linestyle) 167 self._grid_linewidth = (rcParams['grid.linewidth'] 168 if grid_linewidth is None else grid_linewidth) 169 self._grid_alpha = (rcParams['grid.alpha'] 170 if grid_alpha is None else grid_alpha) 171 172 self._grid_kw = {k[5:]: v for k, v in kw.items()} 173 174 self.apply_tickdir(tickdir) 175 176 self.tick1line = self._get_tick1line() 177 self.tick2line = self._get_tick2line() 178 self.gridline = self._get_gridline() 179 180 self.label1 = self._get_text1() 181 self.label = self.label1 # legacy name 182 self.label2 = self._get_text2() 183 184 self.gridOn = gridOn 185 self.tick1On = tick1On 186 self.tick2On = tick2On 187 self.label1On = label1On 188 self.label2On = label2On 189 190 self.update_position(loc) 191 192 def _set_labelrotation(self, labelrotation): 193 if isinstance(labelrotation, six.string_types): 194 mode = labelrotation 195 angle = 0 196 elif isinstance(labelrotation, (tuple, list)): 197 mode, angle = labelrotation 198 else: 199 mode = 'default' 200 angle = labelrotation 201 if mode not in ('auto', 'default'): 202 raise ValueError("Label rotation mode must be 'default' or " 203 "'auto', not '{}'.".format(mode)) 204 self._labelrotation = (mode, angle) 205 206 def apply_tickdir(self, tickdir): 207 """ 208 Calculate self._pad and self._tickmarkers 209 """ 210 pass 211 212 def get_tickdir(self): 213 return self._tickdir 214 215 def get_tick_padding(self): 216 """ 217 Get the length of the tick outside of the axes. 218 """ 219 padding = { 220 'in': 0.0, 221 'inout': 0.5, 222 'out': 1.0 223 } 224 return self._size * padding[self._tickdir] 225 226 def get_children(self): 227 children = [self.tick1line, self.tick2line, 228 self.gridline, self.label1, self.label2] 229 return children 230 231 def set_clip_path(self, clippath, transform=None): 232 artist.Artist.set_clip_path(self, clippath, transform) 233 self.gridline.set_clip_path(clippath, transform) 234 self.stale = True 235 236 set_clip_path.__doc__ = artist.Artist.set_clip_path.__doc__ 237 238 def get_pad_pixels(self): 239 return self.figure.dpi * self._base_pad / 72.0 240 241 def contains(self, mouseevent): 242 """ 243 Test whether the mouse event occurred in the Tick marks. 244 245 This function always returns false. It is more useful to test if the 246 axis as a whole contains the mouse rather than the set of tick marks. 247 """ 248 if callable(self._contains): 249 return self._contains(self, mouseevent) 250 return False, {} 251 252 def set_pad(self, val): 253 """ 254 Set the tick label pad in points 255 256 ACCEPTS: float 257 """ 258 self._apply_params(pad=val) 259 self.stale = True 260 261 def get_pad(self): 262 'Get the value of the tick label pad in points' 263 return self._base_pad 264 265 def _get_text1(self): 266 'Get the default Text 1 instance' 267 pass 268 269 def _get_text2(self): 270 'Get the default Text 2 instance' 271 pass 272 273 def _get_tick1line(self): 274 'Get the default line2D instance for tick1' 275 pass 276 277 def _get_tick2line(self): 278 'Get the default line2D instance for tick2' 279 pass 280 281 def _get_gridline(self): 282 'Get the default grid Line2d instance for this tick' 283 pass 284 285 def get_loc(self): 286 'Return the tick location (data coords) as a scalar' 287 return self._loc 288 289 @allow_rasterization 290 def draw(self, renderer): 291 if not self.get_visible(): 292 self.stale = False 293 return 294 295 renderer.open_group(self.__name__) 296 if self.gridOn: 297 self.gridline.draw(renderer) 298 if self.tick1On: 299 self.tick1line.draw(renderer) 300 if self.tick2On: 301 self.tick2line.draw(renderer) 302 303 if self.label1On: 304 self.label1.draw(renderer) 305 if self.label2On: 306 self.label2.draw(renderer) 307 renderer.close_group(self.__name__) 308 309 self.stale = False 310 311 def set_label1(self, s): 312 """ 313 Set the text of ticklabel 314 315 ACCEPTS: str 316 """ 317 self.label1.set_text(s) 318 self.stale = True 319 320 set_label = set_label1 321 322 def set_label2(self, s): 323 """ 324 Set the text of ticklabel2 325 326 ACCEPTS: str 327 """ 328 self.label2.set_text(s) 329 self.stale = True 330 331 def _set_artist_props(self, a): 332 a.set_figure(self.figure) 333 334 def get_view_interval(self): 335 'return the view Interval instance for the axis this tick is ticking' 336 raise NotImplementedError('Derived must override') 337 338 def _apply_params(self, **kw): 339 switchkw = ['gridOn', 'tick1On', 'tick2On', 'label1On', 'label2On'] 340 switches = [k for k in kw if k in switchkw] 341 for k in switches: 342 setattr(self, k, kw.pop(k)) 343 newmarker = [k for k in kw if k in ['size', 'width', 'pad', 'tickdir']] 344 if newmarker: 345 self._size = kw.pop('size', self._size) 346 # Width could be handled outside this block, but it is 347 # convenient to leave it here. 348 self._width = kw.pop('width', self._width) 349 self._base_pad = kw.pop('pad', self._base_pad) 350 # apply_tickdir uses _size and _base_pad to make _pad, 351 # and also makes _tickmarkers. 352 self.apply_tickdir(kw.pop('tickdir', self._tickdir)) 353 self.tick1line.set_marker(self._tickmarkers[0]) 354 self.tick2line.set_marker(self._tickmarkers[1]) 355 for line in (self.tick1line, self.tick2line): 356 line.set_markersize(self._size) 357 line.set_markeredgewidth(self._width) 358 # _get_text1_transform uses _pad from apply_tickdir. 359 trans = self._get_text1_transform()[0] 360 self.label1.set_transform(trans) 361 trans = self._get_text2_transform()[0] 362 self.label2.set_transform(trans) 363 tick_kw = {k: v for k, v in six.iteritems(kw) 364 if k in ['color', 'zorder']} 365 if tick_kw: 366 self.tick1line.set(**tick_kw) 367 self.tick2line.set(**tick_kw) 368 for k, v in six.iteritems(tick_kw): 369 setattr(self, '_' + k, v) 370 371 if 'labelrotation' in kw: 372 self._set_labelrotation(kw.pop('labelrotation')) 373 self.label1.set(rotation=self._labelrotation[1]) 374 self.label2.set(rotation=self._labelrotation[1]) 375 376 label_list = [k for k in six.iteritems(kw) 377 if k[0] in ['labelsize', 'labelcolor']] 378 if label_list: 379 label_kw = {k[5:]: v for k, v in label_list} 380 self.label1.set(**label_kw) 381 self.label2.set(**label_kw) 382 for k, v in six.iteritems(label_kw): 383 # for labelsize the text objects covert str ('small') 384 # -> points. grab the integer from the `Text` object 385 # instead of saving the string representation 386 v = getattr(self.label1, 'get_' + k)() 387 setattr(self, '_label' + k, v) 388 389 grid_list = [k for k in six.iteritems(kw) 390 if k[0] in _gridline_param_names] 391 if grid_list: 392 grid_kw = {k[5:]: v for k, v in grid_list} 393 self.gridline.set(**grid_kw) 394 for k, v in six.iteritems(grid_kw): 395 setattr(self, '_grid_' + k, v) 396 397 def update_position(self, loc): 398 'Set the location of tick in data coords with scalar *loc*' 399 raise NotImplementedError('Derived must override') 400 401 def _get_text1_transform(self): 402 raise NotImplementedError('Derived must override') 403 404 def _get_text2_transform(self): 405 raise NotImplementedError('Derived must override') 406 407 408class XTick(Tick): 409 """ 410 Contains all the Artists needed to make an x tick - the tick line, 411 the label text and the grid line 412 """ 413 __name__ = 'xtick' 414 415 def _get_text1_transform(self): 416 return self.axes.get_xaxis_text1_transform(self._pad) 417 418 def _get_text2_transform(self): 419 return self.axes.get_xaxis_text2_transform(self._pad) 420 421 def apply_tickdir(self, tickdir): 422 if tickdir is None: 423 tickdir = rcParams['%s.direction' % self._name] 424 self._tickdir = tickdir 425 426 if self._tickdir == 'in': 427 self._tickmarkers = (mlines.TICKUP, mlines.TICKDOWN) 428 elif self._tickdir == 'inout': 429 self._tickmarkers = ('|', '|') 430 else: 431 self._tickmarkers = (mlines.TICKDOWN, mlines.TICKUP) 432 self._pad = self._base_pad + self.get_tick_padding() 433 self.stale = True 434 435 def _get_text1(self): 436 'Get the default Text instance' 437 # the y loc is 3 points below the min of y axis 438 # get the affine as an a,b,c,d,tx,ty list 439 # x in data coords, y in axes coords 440 trans, vert, horiz = self._get_text1_transform() 441 t = mtext.Text( 442 x=0, y=0, 443 fontproperties=font_manager.FontProperties(size=self._labelsize), 444 color=self._labelcolor, 445 verticalalignment=vert, 446 horizontalalignment=horiz, 447 ) 448 t.set_transform(trans) 449 self._set_artist_props(t) 450 return t 451 452 def _get_text2(self): 453 454 'Get the default Text 2 instance' 455 # x in data coords, y in axes coords 456 trans, vert, horiz = self._get_text2_transform() 457 t = mtext.Text( 458 x=0, y=1, 459 fontproperties=font_manager.FontProperties(size=self._labelsize), 460 color=self._labelcolor, 461 verticalalignment=vert, 462 horizontalalignment=horiz, 463 ) 464 t.set_transform(trans) 465 self._set_artist_props(t) 466 return t 467 468 def _get_tick1line(self): 469 'Get the default line2D instance' 470 # x in data coords, y in axes coords 471 l = mlines.Line2D(xdata=(0,), ydata=(0,), color=self._color, 472 linestyle='None', marker=self._tickmarkers[0], 473 markersize=self._size, 474 markeredgewidth=self._width, zorder=self._zorder) 475 l.set_transform(self.axes.get_xaxis_transform(which='tick1')) 476 self._set_artist_props(l) 477 return l 478 479 def _get_tick2line(self): 480 'Get the default line2D instance' 481 # x in data coords, y in axes coords 482 l = mlines.Line2D(xdata=(0,), ydata=(1,), 483 color=self._color, 484 linestyle='None', 485 marker=self._tickmarkers[1], 486 markersize=self._size, 487 markeredgewidth=self._width, 488 zorder=self._zorder) 489 490 l.set_transform(self.axes.get_xaxis_transform(which='tick2')) 491 self._set_artist_props(l) 492 return l 493 494 def _get_gridline(self): 495 'Get the default line2D instance' 496 # x in data coords, y in axes coords 497 l = mlines.Line2D(xdata=(0.0, 0.0), ydata=(0, 1.0), 498 color=self._grid_color, 499 linestyle=self._grid_linestyle, 500 linewidth=self._grid_linewidth, 501 alpha=self._grid_alpha, 502 markersize=0, 503 **self._grid_kw) 504 l.set_transform(self.axes.get_xaxis_transform(which='grid')) 505 l.get_path()._interpolation_steps = GRIDLINE_INTERPOLATION_STEPS 506 self._set_artist_props(l) 507 508 return l 509 510 def update_position(self, loc): 511 'Set the location of tick in data coords with scalar *loc*' 512 if self.tick1On: 513 self.tick1line.set_xdata((loc,)) 514 if self.tick2On: 515 self.tick2line.set_xdata((loc,)) 516 if self.gridOn: 517 self.gridline.set_xdata((loc,)) 518 if self.label1On: 519 self.label1.set_x(loc) 520 if self.label2On: 521 self.label2.set_x(loc) 522 523 self._loc = loc 524 self.stale = True 525 526 def get_view_interval(self): 527 'return the Interval instance for this axis view limits' 528 return self.axes.viewLim.intervalx 529 530 531class YTick(Tick): 532 """ 533 Contains all the Artists needed to make a Y tick - the tick line, 534 the label text and the grid line 535 """ 536 __name__ = 'ytick' 537 538 def _get_text1_transform(self): 539 return self.axes.get_yaxis_text1_transform(self._pad) 540 541 def _get_text2_transform(self): 542 return self.axes.get_yaxis_text2_transform(self._pad) 543 544 def apply_tickdir(self, tickdir): 545 if tickdir is None: 546 tickdir = rcParams['%s.direction' % self._name] 547 self._tickdir = tickdir 548 549 if self._tickdir == 'in': 550 self._tickmarkers = (mlines.TICKRIGHT, mlines.TICKLEFT) 551 elif self._tickdir == 'inout': 552 self._tickmarkers = ('_', '_') 553 else: 554 self._tickmarkers = (mlines.TICKLEFT, mlines.TICKRIGHT) 555 self._pad = self._base_pad + self.get_tick_padding() 556 self.stale = True 557 558 # how far from the y axis line the right of the ticklabel are 559 def _get_text1(self): 560 'Get the default Text instance' 561 # x in axes coords, y in data coords 562 trans, vert, horiz = self._get_text1_transform() 563 t = mtext.Text( 564 x=0, y=0, 565 fontproperties=font_manager.FontProperties(size=self._labelsize), 566 color=self._labelcolor, 567 verticalalignment=vert, 568 horizontalalignment=horiz, 569 ) 570 t.set_transform(trans) 571 self._set_artist_props(t) 572 return t 573 574 def _get_text2(self): 575 'Get the default Text instance' 576 # x in axes coords, y in data coords 577 trans, vert, horiz = self._get_text2_transform() 578 t = mtext.Text( 579 x=1, y=0, 580 fontproperties=font_manager.FontProperties(size=self._labelsize), 581 color=self._labelcolor, 582 verticalalignment=vert, 583 horizontalalignment=horiz, 584 ) 585 t.set_transform(trans) 586 self._set_artist_props(t) 587 return t 588 589 def _get_tick1line(self): 590 'Get the default line2D instance' 591 # x in axes coords, y in data coords 592 593 l = mlines.Line2D((0,), (0,), 594 color=self._color, 595 marker=self._tickmarkers[0], 596 linestyle='None', 597 markersize=self._size, 598 markeredgewidth=self._width, 599 zorder=self._zorder) 600 l.set_transform(self.axes.get_yaxis_transform(which='tick1')) 601 self._set_artist_props(l) 602 return l 603 604 def _get_tick2line(self): 605 'Get the default line2D instance' 606 # x in axes coords, y in data coords 607 l = mlines.Line2D((1,), (0,), 608 color=self._color, 609 marker=self._tickmarkers[1], 610 linestyle='None', 611 markersize=self._size, 612 markeredgewidth=self._width, 613 zorder=self._zorder) 614 l.set_transform(self.axes.get_yaxis_transform(which='tick2')) 615 self._set_artist_props(l) 616 return l 617 618 def _get_gridline(self): 619 'Get the default line2D instance' 620 # x in axes coords, y in data coords 621 l = mlines.Line2D(xdata=(0, 1), ydata=(0, 0), 622 color=self._grid_color, 623 linestyle=self._grid_linestyle, 624 linewidth=self._grid_linewidth, 625 alpha=self._grid_alpha, 626 markersize=0, 627 **self._grid_kw) 628 l.set_transform(self.axes.get_yaxis_transform(which='grid')) 629 l.get_path()._interpolation_steps = GRIDLINE_INTERPOLATION_STEPS 630 self._set_artist_props(l) 631 return l 632 633 def update_position(self, loc): 634 'Set the location of tick in data coords with scalar *loc*' 635 if self.tick1On: 636 self.tick1line.set_ydata((loc,)) 637 if self.tick2On: 638 self.tick2line.set_ydata((loc,)) 639 if self.gridOn: 640 self.gridline.set_ydata((loc,)) 641 if self.label1On: 642 self.label1.set_y(loc) 643 if self.label2On: 644 self.label2.set_y(loc) 645 646 self._loc = loc 647 self.stale = True 648 649 def get_view_interval(self): 650 'return the Interval instance for this axis view limits' 651 return self.axes.viewLim.intervaly 652 653 654class Ticker(object): 655 locator = None 656 formatter = None 657 658 659class _LazyTickList(object): 660 """ 661 A descriptor for lazy instantiation of tick lists. 662 663 See comment above definition of the ``majorTicks`` and ``minorTicks`` 664 attributes. 665 """ 666 667 def __init__(self, major): 668 self._major = major 669 670 def __get__(self, instance, cls): 671 if instance is None: 672 return self 673 else: 674 # instance._get_tick() can itself try to access the majorTicks 675 # attribute (e.g. in certain projection classes which override 676 # e.g. get_xaxis_text1_transform). In order to avoid infinite 677 # recursion, first set the majorTicks on the instance to an empty 678 # list, then create the tick and append it. 679 if self._major: 680 instance.majorTicks = [] 681 tick = instance._get_tick(major=True) 682 instance.majorTicks.append(tick) 683 return instance.majorTicks 684 else: 685 instance.minorTicks = [] 686 tick = instance._get_tick(major=False) 687 instance.minorTicks.append(tick) 688 return instance.minorTicks 689 690 691class Axis(artist.Artist): 692 """ 693 Public attributes 694 695 * :attr:`axes.transData` - transform data coords to display coords 696 * :attr:`axes.transAxes` - transform axis coords to display coords 697 * :attr:`labelpad` - number of points between the axis and its label 698 """ 699 OFFSETTEXTPAD = 3 700 701 def __str__(self): 702 return self.__class__.__name__ \ 703 + "(%f,%f)" % tuple(self.axes.transAxes.transform_point((0, 0))) 704 705 def __init__(self, axes, pickradius=15): 706 """ 707 Init the axis with the parent Axes instance 708 """ 709 artist.Artist.__init__(self) 710 self.set_figure(axes.figure) 711 712 self.isDefault_label = True 713 714 self.axes = axes 715 self.major = Ticker() 716 self.minor = Ticker() 717 self.callbacks = cbook.CallbackRegistry() 718 719 self._autolabelpos = True 720 self._smart_bounds = False 721 722 self.label = self._get_label() 723 self.labelpad = rcParams['axes.labelpad'] 724 self.offsetText = self._get_offset_text() 725 726 self.pickradius = pickradius 727 728 # Initialize here for testing; later add API 729 self._major_tick_kw = dict() 730 self._minor_tick_kw = dict() 731 732 self.cla() 733 self._set_scale('linear') 734 735 # During initialization, Axis objects often create ticks that are later 736 # unused; this turns out to be a very slow step. Instead, use a custom 737 # descriptor to make the tick lists lazy and instantiate them as needed. 738 majorTicks = _LazyTickList(major=True) 739 minorTicks = _LazyTickList(major=False) 740 741 def set_label_coords(self, x, y, transform=None): 742 """ 743 Set the coordinates of the label. By default, the x 744 coordinate of the y label is determined by the tick label 745 bounding boxes, but this can lead to poor alignment of 746 multiple ylabels if there are multiple axes. Ditto for the y 747 coordinate of the x label. 748 749 You can also specify the coordinate system of the label with 750 the transform. If None, the default coordinate system will be 751 the axes coordinate system (0,0) is (left,bottom), (0.5, 0.5) 752 is middle, etc 753 754 """ 755 756 self._autolabelpos = False 757 if transform is None: 758 transform = self.axes.transAxes 759 760 self.label.set_transform(transform) 761 self.label.set_position((x, y)) 762 self.stale = True 763 764 def get_transform(self): 765 return self._scale.get_transform() 766 767 def get_scale(self): 768 return self._scale.name 769 770 def _set_scale(self, value, **kwargs): 771 self._scale = mscale.scale_factory(value, self, **kwargs) 772 self._scale.set_default_locators_and_formatters(self) 773 774 self.isDefault_majloc = True 775 self.isDefault_minloc = True 776 self.isDefault_majfmt = True 777 self.isDefault_minfmt = True 778 779 def limit_range_for_scale(self, vmin, vmax): 780 return self._scale.limit_range_for_scale(vmin, vmax, self.get_minpos()) 781 782 @property 783 @cbook.deprecated("2.2.0") 784 def unit_data(self): 785 return self.units 786 787 @unit_data.setter 788 @cbook.deprecated("2.2.0") 789 def unit_data(self, unit_data): 790 self.set_units(unit_data) 791 792 def get_children(self): 793 children = [self.label, self.offsetText] 794 majorticks = self.get_major_ticks() 795 minorticks = self.get_minor_ticks() 796 797 children.extend(majorticks) 798 children.extend(minorticks) 799 return children 800 801 def cla(self): 802 'clear the current axis' 803 804 self.label.set_text('') # self.set_label_text would change isDefault_ 805 806 self._set_scale('linear') 807 808 # Clear the callback registry for this axis, or it may "leak" 809 self.callbacks = cbook.CallbackRegistry() 810 811 # whether the grids are on 812 self._gridOnMajor = (rcParams['axes.grid'] and 813 rcParams['axes.grid.which'] in ('both', 'major')) 814 self._gridOnMinor = (rcParams['axes.grid'] and 815 rcParams['axes.grid.which'] in ('both', 'minor')) 816 817 self.reset_ticks() 818 819 self.converter = None 820 self.units = None 821 self.set_units(None) 822 self.stale = True 823 824 def reset_ticks(self): 825 """ 826 Re-initialize the major and minor Tick lists. 827 828 Each list starts with a single fresh Tick. 829 """ 830 # Restore the lazy tick lists. 831 try: 832 del self.majorTicks 833 except AttributeError: 834 pass 835 try: 836 del self.minorTicks 837 except AttributeError: 838 pass 839 try: 840 self.set_clip_path(self.axes.patch) 841 except AttributeError: 842 pass 843 844 def set_tick_params(self, which='major', reset=False, **kw): 845 """ 846 Set appearance parameters for ticks, ticklabels, and gridlines. 847 848 For documentation of keyword arguments, see 849 :meth:`matplotlib.axes.Axes.tick_params`. 850 """ 851 dicts = [] 852 if which == 'major' or which == 'both': 853 dicts.append(self._major_tick_kw) 854 if which == 'minor' or which == 'both': 855 dicts.append(self._minor_tick_kw) 856 kwtrans = self._translate_tick_kw(kw, to_init_kw=True) 857 for d in dicts: 858 if reset: 859 d.clear() 860 d.update(kwtrans) 861 862 if reset: 863 self.reset_ticks() 864 else: 865 if which == 'major' or which == 'both': 866 for tick in self.majorTicks: 867 tick._apply_params(**self._major_tick_kw) 868 if which == 'minor' or which == 'both': 869 for tick in self.minorTicks: 870 tick._apply_params(**self._minor_tick_kw) 871 if 'labelcolor' in kwtrans: 872 self.offsetText.set_color(kwtrans['labelcolor']) 873 self.stale = True 874 875 @staticmethod 876 def _translate_tick_kw(kw, to_init_kw=True): 877 # The following lists may be moved to a more 878 # accessible location. 879 kwkeys0 = ['size', 'width', 'color', 'tickdir', 'pad', 880 'labelsize', 'labelcolor', 'zorder', 'gridOn', 881 'tick1On', 'tick2On', 'label1On', 'label2On'] 882 kwkeys1 = ['length', 'direction', 'left', 'bottom', 'right', 'top', 883 'labelleft', 'labelbottom', 'labelright', 'labeltop', 884 'labelrotation'] 885 kwkeys2 = _gridline_param_names 886 kwkeys = kwkeys0 + kwkeys1 + kwkeys2 887 kwtrans = dict() 888 if to_init_kw: 889 if 'length' in kw: 890 kwtrans['size'] = kw.pop('length') 891 if 'direction' in kw: 892 kwtrans['tickdir'] = kw.pop('direction') 893 if 'rotation' in kw: 894 kwtrans['labelrotation'] = kw.pop('rotation') 895 if 'left' in kw: 896 kwtrans['tick1On'] = _string_to_bool(kw.pop('left')) 897 if 'bottom' in kw: 898 kwtrans['tick1On'] = _string_to_bool(kw.pop('bottom')) 899 if 'right' in kw: 900 kwtrans['tick2On'] = _string_to_bool(kw.pop('right')) 901 if 'top' in kw: 902 kwtrans['tick2On'] = _string_to_bool(kw.pop('top')) 903 904 if 'labelleft' in kw: 905 kwtrans['label1On'] = _string_to_bool(kw.pop('labelleft')) 906 if 'labelbottom' in kw: 907 kwtrans['label1On'] = _string_to_bool(kw.pop('labelbottom')) 908 if 'labelright' in kw: 909 kwtrans['label2On'] = _string_to_bool(kw.pop('labelright')) 910 if 'labeltop' in kw: 911 kwtrans['label2On'] = _string_to_bool(kw.pop('labeltop')) 912 if 'colors' in kw: 913 c = kw.pop('colors') 914 kwtrans['color'] = c 915 kwtrans['labelcolor'] = c 916 # Maybe move the checking up to the caller of this method. 917 for key in kw: 918 if key not in kwkeys: 919 raise ValueError( 920 "keyword %s is not recognized; valid keywords are %s" 921 % (key, kwkeys)) 922 kwtrans.update(kw) 923 else: 924 raise NotImplementedError("Inverse translation is deferred") 925 return kwtrans 926 927 def set_clip_path(self, clippath, transform=None): 928 artist.Artist.set_clip_path(self, clippath, transform) 929 for child in self.majorTicks + self.minorTicks: 930 child.set_clip_path(clippath, transform) 931 self.stale = True 932 933 def get_view_interval(self): 934 'return the Interval instance for this axis view limits' 935 raise NotImplementedError('Derived must override') 936 937 def set_view_interval(self, vmin, vmax, ignore=False): 938 raise NotImplementedError('Derived must override') 939 940 def get_data_interval(self): 941 'return the Interval instance for this axis data limits' 942 raise NotImplementedError('Derived must override') 943 944 def set_data_interval(self): 945 '''set the axis data limits''' 946 raise NotImplementedError('Derived must override') 947 948 def set_default_intervals(self): 949 '''set the default limits for the axis data and view interval if they 950 are not mutated''' 951 952 # this is mainly in support of custom object plotting. For 953 # example, if someone passes in a datetime object, we do not 954 # know automagically how to set the default min/max of the 955 # data and view limits. The unit conversion AxisInfo 956 # interface provides a hook for custom types to register 957 # default limits through the AxisInfo.default_limits 958 # attribute, and the derived code below will check for that 959 # and use it if is available (else just use 0..1) 960 pass 961 962 def _set_artist_props(self, a): 963 if a is None: 964 return 965 a.set_figure(self.figure) 966 967 def iter_ticks(self): 968 """ 969 Iterate through all of the major and minor ticks. 970 """ 971 majorLocs = self.major.locator() 972 majorTicks = self.get_major_ticks(len(majorLocs)) 973 self.major.formatter.set_locs(majorLocs) 974 majorLabels = [self.major.formatter(val, i) 975 for i, val in enumerate(majorLocs)] 976 977 minorLocs = self.minor.locator() 978 minorTicks = self.get_minor_ticks(len(minorLocs)) 979 self.minor.formatter.set_locs(minorLocs) 980 minorLabels = [self.minor.formatter(val, i) 981 for i, val in enumerate(minorLocs)] 982 983 major_minor = [ 984 (majorTicks, majorLocs, majorLabels), 985 (minorTicks, minorLocs, minorLabels)] 986 987 for group in major_minor: 988 for tick in zip(*group): 989 yield tick 990 991 def get_ticklabel_extents(self, renderer): 992 """ 993 Get the extents of the tick labels on either side 994 of the axes. 995 """ 996 997 ticks_to_draw = self._update_ticks(renderer) 998 ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(ticks_to_draw, 999 renderer) 1000 1001 if len(ticklabelBoxes): 1002 bbox = mtransforms.Bbox.union(ticklabelBoxes) 1003 else: 1004 bbox = mtransforms.Bbox.from_extents(0, 0, 0, 0) 1005 if len(ticklabelBoxes2): 1006 bbox2 = mtransforms.Bbox.union(ticklabelBoxes2) 1007 else: 1008 bbox2 = mtransforms.Bbox.from_extents(0, 0, 0, 0) 1009 return bbox, bbox2 1010 1011 def set_smart_bounds(self, value): 1012 """set the axis to have smart bounds""" 1013 self._smart_bounds = value 1014 self.stale = True 1015 1016 def get_smart_bounds(self): 1017 """get whether the axis has smart bounds""" 1018 return self._smart_bounds 1019 1020 def _update_ticks(self, renderer): 1021 """ 1022 Update ticks (position and labels) using the current data 1023 interval of the axes. Returns a list of ticks that will be 1024 drawn. 1025 """ 1026 1027 interval = self.get_view_interval() 1028 tick_tups = list(self.iter_ticks()) # iter_ticks calls the locator 1029 if self._smart_bounds and tick_tups: 1030 # handle inverted limits 1031 view_low, view_high = sorted(interval) 1032 data_low, data_high = sorted(self.get_data_interval()) 1033 locs = np.sort([ti[1] for ti in tick_tups]) 1034 if data_low <= view_low: 1035 # data extends beyond view, take view as limit 1036 ilow = view_low 1037 else: 1038 # data stops within view, take best tick 1039 good_locs = locs[locs <= data_low] 1040 if len(good_locs): 1041 # last tick prior or equal to first data point 1042 ilow = good_locs[-1] 1043 else: 1044 # No ticks (why not?), take first tick 1045 ilow = locs[0] 1046 if data_high >= view_high: 1047 # data extends beyond view, take view as limit 1048 ihigh = view_high 1049 else: 1050 # data stops within view, take best tick 1051 good_locs = locs[locs >= data_high] 1052 if len(good_locs): 1053 # first tick after or equal to last data point 1054 ihigh = good_locs[0] 1055 else: 1056 # No ticks (why not?), take last tick 1057 ihigh = locs[-1] 1058 tick_tups = [ti for ti in tick_tups if ilow <= ti[1] <= ihigh] 1059 1060 # so that we don't lose ticks on the end, expand out the interval ever 1061 # so slightly. The "ever so slightly" is defined to be the width of a 1062 # half of a pixel. We don't want to draw a tick that even one pixel 1063 # outside of the defined axis interval. 1064 if interval[0] <= interval[1]: 1065 interval_expanded = interval 1066 else: 1067 interval_expanded = interval[1], interval[0] 1068 1069 if hasattr(self, '_get_pixel_distance_along_axis'): 1070 # normally, one does not want to catch all exceptions that 1071 # could possibly happen, but it is not clear exactly what 1072 # exceptions might arise from a user's projection (their 1073 # rendition of the Axis object). So, we catch all, with 1074 # the idea that one would rather potentially lose a tick 1075 # from one side of the axis or another, rather than see a 1076 # stack trace. 1077 # We also catch users warnings here. These are the result of 1078 # invalid numpy calculations that may be the result of out of 1079 # bounds on axis with finite allowed intervals such as geo 1080 # projections i.e. Mollweide. 1081 with np.errstate(invalid='ignore'): 1082 try: 1083 ds1 = self._get_pixel_distance_along_axis( 1084 interval_expanded[0], -0.5) 1085 except: 1086 warnings.warn("Unable to find pixel distance along axis " 1087 "for interval padding of ticks; assuming no " 1088 "interval padding needed.") 1089 ds1 = 0.0 1090 if np.isnan(ds1): 1091 ds1 = 0.0 1092 try: 1093 ds2 = self._get_pixel_distance_along_axis( 1094 interval_expanded[1], +0.5) 1095 except: 1096 warnings.warn("Unable to find pixel distance along axis " 1097 "for interval padding of ticks; assuming no " 1098 "interval padding needed.") 1099 ds2 = 0.0 1100 if np.isnan(ds2): 1101 ds2 = 0.0 1102 interval_expanded = (interval_expanded[0] - ds1, 1103 interval_expanded[1] + ds2) 1104 1105 ticks_to_draw = [] 1106 for tick, loc, label in tick_tups: 1107 if tick is None: 1108 continue 1109 # NB: always update labels and position to avoid issues like #9397 1110 tick.update_position(loc) 1111 tick.set_label1(label) 1112 tick.set_label2(label) 1113 if not mtransforms.interval_contains(interval_expanded, loc): 1114 continue 1115 ticks_to_draw.append(tick) 1116 1117 return ticks_to_draw 1118 1119 def _get_tick_bboxes(self, ticks, renderer): 1120 """ 1121 Given the list of ticks, return two lists of bboxes. One for 1122 tick lable1's and another for tick label2's. 1123 """ 1124 1125 ticklabelBoxes = [] 1126 ticklabelBoxes2 = [] 1127 1128 for tick in ticks: 1129 if tick.label1On and tick.label1.get_visible(): 1130 extent = tick.label1.get_window_extent(renderer) 1131 ticklabelBoxes.append(extent) 1132 if tick.label2On and tick.label2.get_visible(): 1133 extent = tick.label2.get_window_extent(renderer) 1134 ticklabelBoxes2.append(extent) 1135 return ticklabelBoxes, ticklabelBoxes2 1136 1137 def get_tightbbox(self, renderer): 1138 """ 1139 Return a bounding box that encloses the axis. It only accounts 1140 tick labels, axis label, and offsetText. 1141 """ 1142 if not self.get_visible(): 1143 return 1144 1145 ticks_to_draw = self._update_ticks(renderer) 1146 1147 self._update_label_position(renderer) 1148 1149 # go back to just this axis's tick labels 1150 ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes( 1151 ticks_to_draw, renderer) 1152 1153 self._update_offset_text_position(ticklabelBoxes, ticklabelBoxes2) 1154 self.offsetText.set_text(self.major.formatter.get_offset()) 1155 1156 bb = [] 1157 1158 for a in [self.label, self.offsetText]: 1159 if a.get_visible(): 1160 bb.append(a.get_window_extent(renderer)) 1161 1162 bb.extend(ticklabelBoxes) 1163 bb.extend(ticklabelBoxes2) 1164 1165 bb = [b for b in bb if b.width != 0 or b.height != 0] 1166 if bb: 1167 _bbox = mtransforms.Bbox.union(bb) 1168 return _bbox 1169 else: 1170 return None 1171 1172 def get_tick_padding(self): 1173 values = [] 1174 if len(self.majorTicks): 1175 values.append(self.majorTicks[0].get_tick_padding()) 1176 if len(self.minorTicks): 1177 values.append(self.minorTicks[0].get_tick_padding()) 1178 if len(values): 1179 return max(values) 1180 return 0.0 1181 1182 @allow_rasterization 1183 def draw(self, renderer, *args, **kwargs): 1184 'Draw the axis lines, grid lines, tick lines and labels' 1185 1186 if not self.get_visible(): 1187 return 1188 renderer.open_group(__name__) 1189 1190 ticks_to_draw = self._update_ticks(renderer) 1191 ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(ticks_to_draw, 1192 renderer) 1193 1194 for tick in ticks_to_draw: 1195 tick.draw(renderer) 1196 1197 # scale up the axis label box to also find the neighbors, not 1198 # just the tick labels that actually overlap note we need a 1199 # *copy* of the axis label box because we don't wan't to scale 1200 # the actual bbox 1201 1202 self._update_label_position(renderer) 1203 1204 self.label.draw(renderer) 1205 1206 self._update_offset_text_position(ticklabelBoxes, ticklabelBoxes2) 1207 self.offsetText.set_text(self.major.formatter.get_offset()) 1208 self.offsetText.draw(renderer) 1209 1210 if 0: # draw the bounding boxes around the text for debug 1211 for tick in self.majorTicks: 1212 label = tick.label1 1213 mpatches.bbox_artist(label, renderer) 1214 mpatches.bbox_artist(self.label, renderer) 1215 1216 renderer.close_group(__name__) 1217 self.stale = False 1218 1219 def _get_label(self): 1220 raise NotImplementedError('Derived must override') 1221 1222 def _get_offset_text(self): 1223 raise NotImplementedError('Derived must override') 1224 1225 def get_gridlines(self): 1226 'Return the grid lines as a list of Line2D instance' 1227 ticks = self.get_major_ticks() 1228 return cbook.silent_list('Line2D gridline', 1229 [tick.gridline for tick in ticks]) 1230 1231 def get_label(self): 1232 'Return the axis label as a Text instance' 1233 return self.label 1234 1235 def get_offset_text(self): 1236 'Return the axis offsetText as a Text instance' 1237 return self.offsetText 1238 1239 def get_pickradius(self): 1240 'Return the depth of the axis used by the picker' 1241 return self.pickradius 1242 1243 def get_majorticklabels(self): 1244 'Return a list of Text instances for the major ticklabels' 1245 ticks = self.get_major_ticks() 1246 labels1 = [tick.label1 for tick in ticks if tick.label1On] 1247 labels2 = [tick.label2 for tick in ticks if tick.label2On] 1248 return cbook.silent_list('Text major ticklabel', labels1 + labels2) 1249 1250 def get_minorticklabels(self): 1251 'Return a list of Text instances for the minor ticklabels' 1252 ticks = self.get_minor_ticks() 1253 labels1 = [tick.label1 for tick in ticks if tick.label1On] 1254 labels2 = [tick.label2 for tick in ticks if tick.label2On] 1255 return cbook.silent_list('Text minor ticklabel', labels1 + labels2) 1256 1257 def get_ticklabels(self, minor=False, which=None): 1258 """ 1259 Get the tick labels as a list of :class:`~matplotlib.text.Text` 1260 instances. 1261 1262 Parameters 1263 ---------- 1264 minor : bool 1265 If True return the minor ticklabels, 1266 else return the major ticklabels 1267 1268 which : None, ('minor', 'major', 'both') 1269 Overrides `minor`. 1270 1271 Selects which ticklabels to return 1272 1273 Returns 1274 ------- 1275 ret : list 1276 List of :class:`~matplotlib.text.Text` instances. 1277 """ 1278 1279 if which is not None: 1280 if which == 'minor': 1281 return self.get_minorticklabels() 1282 elif which == 'major': 1283 return self.get_majorticklabels() 1284 elif which == 'both': 1285 return self.get_majorticklabels() + self.get_minorticklabels() 1286 else: 1287 raise ValueError("`which` must be one of ('minor', 'major', " 1288 "'both') not " + str(which)) 1289 if minor: 1290 return self.get_minorticklabels() 1291 return self.get_majorticklabels() 1292 1293 def get_majorticklines(self): 1294 'Return the major tick lines as a list of Line2D instances' 1295 lines = [] 1296 ticks = self.get_major_ticks() 1297 for tick in ticks: 1298 lines.append(tick.tick1line) 1299 lines.append(tick.tick2line) 1300 return cbook.silent_list('Line2D ticklines', lines) 1301 1302 def get_minorticklines(self): 1303 'Return the minor tick lines as a list of Line2D instances' 1304 lines = [] 1305 ticks = self.get_minor_ticks() 1306 for tick in ticks: 1307 lines.append(tick.tick1line) 1308 lines.append(tick.tick2line) 1309 return cbook.silent_list('Line2D ticklines', lines) 1310 1311 def get_ticklines(self, minor=False): 1312 'Return the tick lines as a list of Line2D instances' 1313 if minor: 1314 return self.get_minorticklines() 1315 return self.get_majorticklines() 1316 1317 def get_majorticklocs(self): 1318 "Get the major tick locations in data coordinates as a numpy array" 1319 return self.major.locator() 1320 1321 def get_minorticklocs(self): 1322 "Get the minor tick locations in data coordinates as a numpy array" 1323 return self.minor.locator() 1324 1325 def get_ticklocs(self, minor=False): 1326 "Get the tick locations in data coordinates as a numpy array" 1327 if minor: 1328 return self.minor.locator() 1329 return self.major.locator() 1330 1331 def get_ticks_direction(self, minor=False): 1332 """ 1333 Get the tick directions as a numpy array 1334 1335 Parameters 1336 ---------- 1337 minor : boolean 1338 True to return the minor tick directions, 1339 False to return the major tick directions, 1340 Default is False 1341 1342 Returns 1343 ------- 1344 numpy array of tick directions 1345 """ 1346 if minor: 1347 return np.array( 1348 [tick._tickdir for tick in self.get_minor_ticks()]) 1349 else: 1350 return np.array( 1351 [tick._tickdir for tick in self.get_major_ticks()]) 1352 1353 def _get_tick(self, major): 1354 'return the default tick instance' 1355 raise NotImplementedError('derived must override') 1356 1357 def _copy_tick_props(self, src, dest): 1358 'Copy the props from src tick to dest tick' 1359 if src is None or dest is None: 1360 return 1361 dest.label1.update_from(src.label1) 1362 dest.label2.update_from(src.label2) 1363 1364 dest.tick1line.update_from(src.tick1line) 1365 dest.tick2line.update_from(src.tick2line) 1366 dest.gridline.update_from(src.gridline) 1367 1368 dest.tick1On = src.tick1On 1369 dest.tick2On = src.tick2On 1370 dest.label1On = src.label1On 1371 dest.label2On = src.label2On 1372 1373 def get_label_text(self): 1374 'Get the text of the label' 1375 return self.label.get_text() 1376 1377 def get_major_locator(self): 1378 'Get the locator of the major ticker' 1379 return self.major.locator 1380 1381 def get_minor_locator(self): 1382 'Get the locator of the minor ticker' 1383 return self.minor.locator 1384 1385 def get_major_formatter(self): 1386 'Get the formatter of the major ticker' 1387 return self.major.formatter 1388 1389 def get_minor_formatter(self): 1390 'Get the formatter of the minor ticker' 1391 return self.minor.formatter 1392 1393 def get_major_ticks(self, numticks=None): 1394 'get the tick instances; grow as necessary' 1395 if numticks is None: 1396 numticks = len(self.get_major_locator()()) 1397 1398 while len(self.majorTicks) < numticks: 1399 # update the new tick label properties from the old 1400 tick = self._get_tick(major=True) 1401 self.majorTicks.append(tick) 1402 if self._gridOnMajor: 1403 tick.gridOn = True 1404 self._copy_tick_props(self.majorTicks[0], tick) 1405 1406 return self.majorTicks[:numticks] 1407 1408 def get_minor_ticks(self, numticks=None): 1409 'get the minor tick instances; grow as necessary' 1410 if numticks is None: 1411 numticks = len(self.get_minor_locator()()) 1412 1413 while len(self.minorTicks) < numticks: 1414 # update the new tick label properties from the old 1415 tick = self._get_tick(major=False) 1416 self.minorTicks.append(tick) 1417 if self._gridOnMinor: 1418 tick.gridOn = True 1419 self._copy_tick_props(self.minorTicks[0], tick) 1420 1421 return self.minorTicks[:numticks] 1422 1423 def grid(self, b=None, which='major', **kwargs): 1424 """ 1425 Set the axis grid on or off; b is a boolean. Use *which* = 1426 'major' | 'minor' | 'both' to set the grid for major or minor ticks. 1427 1428 If *b* is *None* and len(kwargs)==0, toggle the grid state. If 1429 *kwargs* are supplied, it is assumed you want the grid on and *b* 1430 will be set to True. 1431 1432 *kwargs* are used to set the line properties of the grids, e.g., 1433 1434 xax.grid(color='r', linestyle='-', linewidth=2) 1435 """ 1436 if len(kwargs): 1437 b = True 1438 which = which.lower() 1439 gridkw = {'grid_' + item[0]: item[1] for item in kwargs.items()} 1440 if which in ['minor', 'both']: 1441 if b is None: 1442 self._gridOnMinor = not self._gridOnMinor 1443 else: 1444 self._gridOnMinor = b 1445 self.set_tick_params(which='minor', gridOn=self._gridOnMinor, 1446 **gridkw) 1447 if which in ['major', 'both']: 1448 if b is None: 1449 self._gridOnMajor = not self._gridOnMajor 1450 else: 1451 self._gridOnMajor = b 1452 self.set_tick_params(which='major', gridOn=self._gridOnMajor, 1453 **gridkw) 1454 self.stale = True 1455 1456 def update_units(self, data): 1457 """ 1458 introspect *data* for units converter and update the 1459 axis.converter instance if necessary. Return *True* 1460 if *data* is registered for unit conversion. 1461 """ 1462 1463 converter = munits.registry.get_converter(data) 1464 if converter is None: 1465 return False 1466 1467 neednew = self.converter != converter 1468 self.converter = converter 1469 default = self.converter.default_units(data, self) 1470 if default is not None and self.units is None: 1471 self.set_units(default) 1472 1473 if neednew: 1474 self._update_axisinfo() 1475 self.stale = True 1476 return True 1477 1478 def _update_axisinfo(self): 1479 """ 1480 check the axis converter for the stored units to see if the 1481 axis info needs to be updated 1482 """ 1483 if self.converter is None: 1484 return 1485 1486 info = self.converter.axisinfo(self.units, self) 1487 1488 if info is None: 1489 return 1490 if info.majloc is not None and \ 1491 self.major.locator != info.majloc and self.isDefault_majloc: 1492 self.set_major_locator(info.majloc) 1493 self.isDefault_majloc = True 1494 if info.minloc is not None and \ 1495 self.minor.locator != info.minloc and self.isDefault_minloc: 1496 self.set_minor_locator(info.minloc) 1497 self.isDefault_minloc = True 1498 if info.majfmt is not None and \ 1499 self.major.formatter != info.majfmt and self.isDefault_majfmt: 1500 self.set_major_formatter(info.majfmt) 1501 self.isDefault_majfmt = True 1502 if info.minfmt is not None and \ 1503 self.minor.formatter != info.minfmt and self.isDefault_minfmt: 1504 self.set_minor_formatter(info.minfmt) 1505 self.isDefault_minfmt = True 1506 if info.label is not None and self.isDefault_label: 1507 self.set_label_text(info.label) 1508 self.isDefault_label = True 1509 1510 self.set_default_intervals() 1511 1512 def have_units(self): 1513 return self.converter is not None or self.units is not None 1514 1515 def convert_units(self, x): 1516 # If x is already a number, doesn't need converting 1517 if munits.ConversionInterface.is_numlike(x): 1518 return x 1519 1520 if self.converter is None: 1521 self.converter = munits.registry.get_converter(x) 1522 1523 if self.converter is None: 1524 return x 1525 1526 ret = self.converter.convert(x, self.units, self) 1527 return ret 1528 1529 def set_units(self, u): 1530 """ 1531 set the units for axis 1532 1533 ACCEPTS: a units tag 1534 """ 1535 pchanged = False 1536 if u is None: 1537 self.units = None 1538 pchanged = True 1539 else: 1540 if u != self.units: 1541 self.units = u 1542 pchanged = True 1543 if pchanged: 1544 self._update_axisinfo() 1545 self.callbacks.process('units') 1546 self.callbacks.process('units finalize') 1547 self.stale = True 1548 1549 def get_units(self): 1550 'return the units for axis' 1551 return self.units 1552 1553 def set_label_text(self, label, fontdict=None, **kwargs): 1554 """ Sets the text value of the axis label 1555 1556 ACCEPTS: A string value for the label 1557 """ 1558 self.isDefault_label = False 1559 self.label.set_text(label) 1560 if fontdict is not None: 1561 self.label.update(fontdict) 1562 self.label.update(kwargs) 1563 self.stale = True 1564 return self.label 1565 1566 def set_major_formatter(self, formatter): 1567 """ 1568 Set the formatter of the major ticker 1569 1570 ACCEPTS: A :class:`~matplotlib.ticker.Formatter` instance 1571 """ 1572 self.isDefault_majfmt = False 1573 self.major.formatter = formatter 1574 formatter.set_axis(self) 1575 self.stale = True 1576 1577 def set_minor_formatter(self, formatter): 1578 """ 1579 Set the formatter of the minor ticker 1580 1581 ACCEPTS: A :class:`~matplotlib.ticker.Formatter` instance 1582 """ 1583 self.isDefault_minfmt = False 1584 self.minor.formatter = formatter 1585 formatter.set_axis(self) 1586 self.stale = True 1587 1588 def set_major_locator(self, locator): 1589 """ 1590 Set the locator of the major ticker 1591 1592 ACCEPTS: a :class:`~matplotlib.ticker.Locator` instance 1593 """ 1594 self.isDefault_majloc = False 1595 self.major.locator = locator 1596 locator.set_axis(self) 1597 self.stale = True 1598 1599 def set_minor_locator(self, locator): 1600 """ 1601 Set the locator of the minor ticker 1602 1603 ACCEPTS: a :class:`~matplotlib.ticker.Locator` instance 1604 """ 1605 self.isDefault_minloc = False 1606 self.minor.locator = locator 1607 locator.set_axis(self) 1608 self.stale = True 1609 1610 def set_pickradius(self, pickradius): 1611 """ 1612 Set the depth of the axis used by the picker 1613 1614 ACCEPTS: a distance in points 1615 """ 1616 self.pickradius = pickradius 1617 1618 def set_ticklabels(self, ticklabels, *args, **kwargs): 1619 """ 1620 Set the text values of the tick labels. Return a list of Text 1621 instances. Use *kwarg* *minor=True* to select minor ticks. 1622 All other kwargs are used to update the text object properties. 1623 As for get_ticklabels, label1 (left or bottom) is 1624 affected for a given tick only if its label1On attribute 1625 is True, and similarly for label2. The list of returned 1626 label text objects consists of all such label1 objects followed 1627 by all such label2 objects. 1628 1629 The input *ticklabels* is assumed to match the set of 1630 tick locations, regardless of the state of label1On and 1631 label2On. 1632 1633 ACCEPTS: sequence of strings or Text objects 1634 """ 1635 get_labels = [] 1636 for t in ticklabels: 1637 # try calling get_text() to check whether it is Text object 1638 # if it is Text, get label content 1639 try: 1640 get_labels.append(t.get_text()) 1641 # otherwise add the label to the list directly 1642 except AttributeError: 1643 get_labels.append(t) 1644 # replace the ticklabels list with the processed one 1645 ticklabels = get_labels 1646 1647 minor = kwargs.pop('minor', False) 1648 if minor: 1649 self.set_minor_formatter(mticker.FixedFormatter(ticklabels)) 1650 ticks = self.get_minor_ticks() 1651 else: 1652 self.set_major_formatter(mticker.FixedFormatter(ticklabels)) 1653 ticks = self.get_major_ticks() 1654 ret = [] 1655 for tick_label, tick in zip(ticklabels, ticks): 1656 # deal with label1 1657 tick.label1.set_text(tick_label) 1658 tick.label1.update(kwargs) 1659 # deal with label2 1660 tick.label2.set_text(tick_label) 1661 tick.label2.update(kwargs) 1662 # only return visible tick labels 1663 if tick.label1On: 1664 ret.append(tick.label1) 1665 if tick.label2On: 1666 ret.append(tick.label2) 1667 1668 self.stale = True 1669 return ret 1670 1671 def set_ticks(self, ticks, minor=False): 1672 """ 1673 Set the locations of the tick marks from sequence ticks 1674 1675 ACCEPTS: sequence of floats 1676 """ 1677 # XXX if the user changes units, the information will be lost here 1678 ticks = self.convert_units(ticks) 1679 if len(ticks) > 1: 1680 xleft, xright = self.get_view_interval() 1681 if xright > xleft: 1682 self.set_view_interval(min(ticks), max(ticks)) 1683 else: 1684 self.set_view_interval(max(ticks), min(ticks)) 1685 if minor: 1686 self.set_minor_locator(mticker.FixedLocator(ticks)) 1687 return self.get_minor_ticks(len(ticks)) 1688 else: 1689 self.set_major_locator(mticker.FixedLocator(ticks)) 1690 return self.get_major_ticks(len(ticks)) 1691 1692 def _get_tick_boxes_siblings(self, xdir, renderer): 1693 """ 1694 Get the bounding boxes for this `.axis` and its siblings 1695 as set by `.Figure.align_xlabels` or `.Figure.align_ylablels`. 1696 1697 By default it just gets bboxes for self. 1698 """ 1699 raise NotImplementedError('Derived must override') 1700 1701 def _update_label_position(self, renderer): 1702 """ 1703 Update the label position based on the bounding box enclosing 1704 all the ticklabels and axis spine 1705 """ 1706 raise NotImplementedError('Derived must override') 1707 1708 def _update_offset_text_position(self, bboxes, bboxes2): 1709 """ 1710 Update the label position based on the sequence of bounding 1711 boxes of all the ticklabels 1712 """ 1713 raise NotImplementedError('Derived must override') 1714 1715 def pan(self, numsteps): 1716 'Pan *numsteps* (can be positive or negative)' 1717 self.major.locator.pan(numsteps) 1718 1719 def zoom(self, direction): 1720 "Zoom in/out on axis; if *direction* is >0 zoom in, else zoom out" 1721 self.major.locator.zoom(direction) 1722 1723 def axis_date(self, tz=None): 1724 """ 1725 Sets up x-axis ticks and labels that treat the x data as dates. 1726 *tz* is a :class:`tzinfo` instance or a timezone string. 1727 This timezone is used to create date labels. 1728 """ 1729 # By providing a sample datetime instance with the desired 1730 # timezone, the registered converter can be selected, 1731 # and the "units" attribute, which is the timezone, can 1732 # be set. 1733 import datetime 1734 if isinstance(tz, six.string_types): 1735 import pytz 1736 tz = pytz.timezone(tz) 1737 self.update_units(datetime.datetime(2009, 1, 1, 0, 0, 0, 0, tz)) 1738 1739 def get_tick_space(self): 1740 """ 1741 Return the estimated number of ticks that can fit on the axis. 1742 """ 1743 # Must be overridden in the subclass 1744 raise NotImplementedError() 1745 1746 def get_label_position(self): 1747 """ 1748 Return the label position (top or bottom) 1749 """ 1750 return self.label_position 1751 1752 def set_label_position(self, position): 1753 """ 1754 Set the label position (top or bottom) 1755 1756 ACCEPTS: [ 'top' | 'bottom' ] 1757 """ 1758 raise NotImplementedError() 1759 1760 def get_minpos(self): 1761 raise NotImplementedError() 1762 1763 1764class XAxis(Axis): 1765 __name__ = 'xaxis' 1766 axis_name = 'x' 1767 1768 def contains(self, mouseevent): 1769 """Test whether the mouse event occurred in the x axis. 1770 """ 1771 if callable(self._contains): 1772 return self._contains(self, mouseevent) 1773 1774 x, y = mouseevent.x, mouseevent.y 1775 try: 1776 trans = self.axes.transAxes.inverted() 1777 xaxes, yaxes = trans.transform_point((x, y)) 1778 except ValueError: 1779 return False, {} 1780 l, b = self.axes.transAxes.transform_point((0, 0)) 1781 r, t = self.axes.transAxes.transform_point((1, 1)) 1782 inaxis = xaxes >= 0 and xaxes <= 1 and ( 1783 (y < b and y > b - self.pickradius) or 1784 (y > t and y < t + self.pickradius)) 1785 return inaxis, {} 1786 1787 def _get_tick(self, major): 1788 if major: 1789 tick_kw = self._major_tick_kw 1790 else: 1791 tick_kw = self._minor_tick_kw 1792 return XTick(self.axes, 0, '', major=major, **tick_kw) 1793 1794 def _get_label(self): 1795 # x in axes coords, y in display coords (to be updated at draw 1796 # time by _update_label_positions) 1797 label = mtext.Text(x=0.5, y=0, 1798 fontproperties=font_manager.FontProperties( 1799 size=rcParams['axes.labelsize'], 1800 weight=rcParams['axes.labelweight']), 1801 color=rcParams['axes.labelcolor'], 1802 verticalalignment='top', 1803 horizontalalignment='center') 1804 1805 label.set_transform(mtransforms.blended_transform_factory( 1806 self.axes.transAxes, mtransforms.IdentityTransform())) 1807 1808 self._set_artist_props(label) 1809 self.label_position = 'bottom' 1810 return label 1811 1812 def _get_offset_text(self): 1813 # x in axes coords, y in display coords (to be updated at draw time) 1814 offsetText = mtext.Text(x=1, y=0, 1815 fontproperties=font_manager.FontProperties( 1816 size=rcParams['xtick.labelsize']), 1817 color=rcParams['xtick.color'], 1818 verticalalignment='top', 1819 horizontalalignment='right') 1820 offsetText.set_transform(mtransforms.blended_transform_factory( 1821 self.axes.transAxes, mtransforms.IdentityTransform()) 1822 ) 1823 self._set_artist_props(offsetText) 1824 self.offset_text_position = 'bottom' 1825 return offsetText 1826 1827 def _get_pixel_distance_along_axis(self, where, perturb): 1828 """ 1829 Returns the amount, in data coordinates, that a single pixel 1830 corresponds to in the locality given by "where", which is also given 1831 in data coordinates, and is an x coordinate. "perturb" is the amount 1832 to perturb the pixel. Usually +0.5 or -0.5. 1833 1834 Implementing this routine for an axis is optional; if present, it will 1835 ensure that no ticks are lost due to round-off at the extreme ends of 1836 an axis. 1837 """ 1838 1839 # Note that this routine does not work for a polar axis, because of 1840 # the 1e-10 below. To do things correctly, we need to use rmax 1841 # instead of 1e-10 for a polar axis. But since we do not have that 1842 # kind of information at this point, we just don't try to pad anything 1843 # for the theta axis of a polar plot. 1844 if self.axes.name == 'polar': 1845 return 0.0 1846 1847 # 1848 # first figure out the pixel location of the "where" point. We use 1849 # 1e-10 for the y point, so that we remain compatible with log axes. 1850 1851 # transformation from data coords to display coords 1852 trans = self.axes.transData 1853 # transformation from display coords to data coords 1854 transinv = trans.inverted() 1855 pix = trans.transform_point((where, 1e-10)) 1856 # perturb the pixel 1857 ptp = transinv.transform_point((pix[0] + perturb, pix[1])) 1858 dx = abs(ptp[0] - where) 1859 1860 return dx 1861 1862 def set_label_position(self, position): 1863 """ 1864 Set the label position (top or bottom) 1865 1866 ACCEPTS: [ 'top' | 'bottom' ] 1867 """ 1868 if position == 'top': 1869 self.label.set_verticalalignment('baseline') 1870 elif position == 'bottom': 1871 self.label.set_verticalalignment('top') 1872 else: 1873 raise ValueError("Position accepts only 'top' or 'bottom'") 1874 self.label_position = position 1875 self.stale = True 1876 1877 def _get_tick_boxes_siblings(self, renderer): 1878 """ 1879 Get the bounding boxes for this `.axis` and its siblings 1880 as set by `.Figure.align_xlabels` or `.Figure.align_ylablels`. 1881 1882 By default it just gets bboxes for self. 1883 """ 1884 bboxes = [] 1885 bboxes2 = [] 1886 # get the Grouper that keeps track of x-label groups for this figure 1887 grp = self.figure._align_xlabel_grp 1888 # if we want to align labels from other axes: 1889 for nn, axx in enumerate(grp.get_siblings(self.axes)): 1890 ticks_to_draw = axx.xaxis._update_ticks(renderer) 1891 tlb, tlb2 = axx.xaxis._get_tick_bboxes(ticks_to_draw, renderer) 1892 bboxes.extend(tlb) 1893 bboxes2.extend(tlb2) 1894 return bboxes, bboxes2 1895 1896 def _update_label_position(self, renderer): 1897 """ 1898 Update the label position based on the bounding box enclosing 1899 all the ticklabels and axis spine 1900 """ 1901 if not self._autolabelpos: 1902 return 1903 1904 # get bounding boxes for this axis and any siblings 1905 # that have been set by `fig.align_xlabels()` 1906 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer) 1907 1908 x, y = self.label.get_position() 1909 if self.label_position == 'bottom': 1910 try: 1911 spine = self.axes.spines['bottom'] 1912 spinebbox = spine.get_transform().transform_path( 1913 spine.get_path()).get_extents() 1914 except KeyError: 1915 # use axes if spine doesn't exist 1916 spinebbox = self.axes.bbox 1917 bbox = mtransforms.Bbox.union(bboxes + [spinebbox]) 1918 bottom = bbox.y0 1919 1920 self.label.set_position( 1921 (x, bottom - self.labelpad * self.figure.dpi / 72.0) 1922 ) 1923 1924 else: 1925 try: 1926 spine = self.axes.spines['top'] 1927 spinebbox = spine.get_transform().transform_path( 1928 spine.get_path()).get_extents() 1929 except KeyError: 1930 # use axes if spine doesn't exist 1931 spinebbox = self.axes.bbox 1932 bbox = mtransforms.Bbox.union(bboxes2 + [spinebbox]) 1933 top = bbox.y1 1934 1935 self.label.set_position( 1936 (x, top + self.labelpad * self.figure.dpi / 72.0) 1937 ) 1938 1939 def _update_offset_text_position(self, bboxes, bboxes2): 1940 """ 1941 Update the offset_text position based on the sequence of bounding 1942 boxes of all the ticklabels 1943 """ 1944 x, y = self.offsetText.get_position() 1945 if not len(bboxes): 1946 bottom = self.axes.bbox.ymin 1947 else: 1948 bbox = mtransforms.Bbox.union(bboxes) 1949 bottom = bbox.y0 1950 self.offsetText.set_position( 1951 (x, bottom - self.OFFSETTEXTPAD * self.figure.dpi / 72.0) 1952 ) 1953 1954 def get_text_heights(self, renderer): 1955 """ 1956 Returns the amount of space one should reserve for text 1957 above and below the axes. Returns a tuple (above, below) 1958 """ 1959 bbox, bbox2 = self.get_ticklabel_extents(renderer) 1960 # MGDTODO: Need a better way to get the pad 1961 padPixels = self.majorTicks[0].get_pad_pixels() 1962 1963 above = 0.0 1964 if bbox2.height: 1965 above += bbox2.height + padPixels 1966 below = 0.0 1967 if bbox.height: 1968 below += bbox.height + padPixels 1969 1970 if self.get_label_position() == 'top': 1971 above += self.label.get_window_extent(renderer).height + padPixels 1972 else: 1973 below += self.label.get_window_extent(renderer).height + padPixels 1974 return above, below 1975 1976 def set_ticks_position(self, position): 1977 """ 1978 Set the ticks position (top, bottom, both, default or none) 1979 both sets the ticks to appear on both positions, but does not 1980 change the tick labels. 'default' resets the tick positions to 1981 the default: ticks on both positions, labels at bottom. 'none' 1982 can be used if you don't want any ticks. 'none' and 'both' 1983 affect only the ticks, not the labels. 1984 1985 ACCEPTS: [ 'top' | 'bottom' | 'both' | 'default' | 'none' ] 1986 """ 1987 if position == 'top': 1988 self.set_tick_params(which='both', top=True, labeltop=True, 1989 bottom=False, labelbottom=False) 1990 elif position == 'bottom': 1991 self.set_tick_params(which='both', top=False, labeltop=False, 1992 bottom=True, labelbottom=True) 1993 elif position == 'both': 1994 self.set_tick_params(which='both', top=True, 1995 bottom=True) 1996 elif position == 'none': 1997 self.set_tick_params(which='both', top=False, 1998 bottom=False) 1999 elif position == 'default': 2000 self.set_tick_params(which='both', top=True, labeltop=False, 2001 bottom=True, labelbottom=True) 2002 else: 2003 raise ValueError("invalid position: %s" % position) 2004 self.stale = True 2005 2006 def tick_top(self): 2007 """ 2008 Move ticks and ticklabels (if present) to the top of the axes. 2009 """ 2010 label = True 2011 if 'label1On' in self._major_tick_kw: 2012 label = (self._major_tick_kw['label1On'] 2013 or self._major_tick_kw['label2On']) 2014 self.set_ticks_position('top') 2015 # if labels were turned off before this was called 2016 # leave them off 2017 self.set_tick_params(which='both', labeltop=label) 2018 2019 def tick_bottom(self): 2020 """ 2021 Move ticks and ticklabels (if present) to the bottom of the axes. 2022 """ 2023 label = True 2024 if 'label1On' in self._major_tick_kw: 2025 label = (self._major_tick_kw['label1On'] 2026 or self._major_tick_kw['label2On']) 2027 self.set_ticks_position('bottom') 2028 # if labels were turned off before this was called 2029 # leave them off 2030 self.set_tick_params(which='both', labelbottom=label) 2031 2032 def get_ticks_position(self): 2033 """ 2034 Return the ticks position (top, bottom, default or unknown) 2035 """ 2036 majt = self.majorTicks[0] 2037 mT = self.minorTicks[0] 2038 2039 majorTop = ((not majt.tick1On) and majt.tick2On and 2040 (not majt.label1On) and majt.label2On) 2041 minorTop = ((not mT.tick1On) and mT.tick2On and 2042 (not mT.label1On) and mT.label2On) 2043 if majorTop and minorTop: 2044 return 'top' 2045 2046 MajorBottom = (majt.tick1On and (not majt.tick2On) and 2047 majt.label1On and (not majt.label2On)) 2048 MinorBottom = (mT.tick1On and (not mT.tick2On) and 2049 mT.label1On and (not mT.label2On)) 2050 if MajorBottom and MinorBottom: 2051 return 'bottom' 2052 2053 majorDefault = (majt.tick1On and majt.tick2On and 2054 majt.label1On and (not majt.label2On)) 2055 minorDefault = (mT.tick1On and mT.tick2On and 2056 mT.label1On and (not mT.label2On)) 2057 if majorDefault and minorDefault: 2058 return 'default' 2059 2060 return 'unknown' 2061 2062 def get_view_interval(self): 2063 'return the Interval instance for this axis view limits' 2064 return self.axes.viewLim.intervalx 2065 2066 def set_view_interval(self, vmin, vmax, ignore=False): 2067 """ 2068 If *ignore* is *False*, the order of vmin, vmax 2069 does not matter; the original axis orientation will 2070 be preserved. In addition, the view limits can be 2071 expanded, but will not be reduced. This method is 2072 for mpl internal use; for normal use, see 2073 :meth:`~matplotlib.axes.Axes.set_xlim`. 2074 2075 """ 2076 if ignore: 2077 self.axes.viewLim.intervalx = vmin, vmax 2078 else: 2079 Vmin, Vmax = self.get_view_interval() 2080 if Vmin < Vmax: 2081 self.axes.viewLim.intervalx = (min(vmin, vmax, Vmin), 2082 max(vmin, vmax, Vmax)) 2083 else: 2084 self.axes.viewLim.intervalx = (max(vmin, vmax, Vmin), 2085 min(vmin, vmax, Vmax)) 2086 2087 def get_minpos(self): 2088 return self.axes.dataLim.minposx 2089 2090 def get_data_interval(self): 2091 'return the Interval instance for this axis data limits' 2092 return self.axes.dataLim.intervalx 2093 2094 def set_data_interval(self, vmin, vmax, ignore=False): 2095 'set the axis data limits' 2096 if ignore: 2097 self.axes.dataLim.intervalx = vmin, vmax 2098 else: 2099 Vmin, Vmax = self.get_data_interval() 2100 self.axes.dataLim.intervalx = min(vmin, Vmin), max(vmax, Vmax) 2101 self.stale = True 2102 2103 def set_default_intervals(self): 2104 'set the default limits for the axis interval if they are not mutated' 2105 xmin, xmax = 0., 1. 2106 dataMutated = self.axes.dataLim.mutatedx() 2107 viewMutated = self.axes.viewLim.mutatedx() 2108 if not dataMutated or not viewMutated: 2109 if self.converter is not None: 2110 info = self.converter.axisinfo(self.units, self) 2111 if info.default_limits is not None: 2112 valmin, valmax = info.default_limits 2113 xmin = self.converter.convert(valmin, self.units, self) 2114 xmax = self.converter.convert(valmax, self.units, self) 2115 if not dataMutated: 2116 self.axes.dataLim.intervalx = xmin, xmax 2117 if not viewMutated: 2118 self.axes.viewLim.intervalx = xmin, xmax 2119 self.stale = True 2120 2121 def get_tick_space(self): 2122 ends = self.axes.transAxes.transform([[0, 0], [1, 0]]) 2123 length = ((ends[1][0] - ends[0][0]) / self.axes.figure.dpi) * 72.0 2124 tick = self._get_tick(True) 2125 # There is a heuristic here that the aspect ratio of tick text 2126 # is no more than 3:1 2127 size = tick.label1.get_size() * 3 2128 if size > 0: 2129 return int(np.floor(length / size)) 2130 else: 2131 return 2**31 - 1 2132 2133 2134class YAxis(Axis): 2135 __name__ = 'yaxis' 2136 axis_name = 'y' 2137 2138 def contains(self, mouseevent): 2139 """Test whether the mouse event occurred in the y axis. 2140 2141 Returns *True* | *False* 2142 """ 2143 if callable(self._contains): 2144 return self._contains(self, mouseevent) 2145 2146 x, y = mouseevent.x, mouseevent.y 2147 try: 2148 trans = self.axes.transAxes.inverted() 2149 xaxes, yaxes = trans.transform_point((x, y)) 2150 except ValueError: 2151 return False, {} 2152 l, b = self.axes.transAxes.transform_point((0, 0)) 2153 r, t = self.axes.transAxes.transform_point((1, 1)) 2154 inaxis = yaxes >= 0 and yaxes <= 1 and ( 2155 (x < l and x > l - self.pickradius) or 2156 (x > r and x < r + self.pickradius)) 2157 return inaxis, {} 2158 2159 def _get_tick(self, major): 2160 if major: 2161 tick_kw = self._major_tick_kw 2162 else: 2163 tick_kw = self._minor_tick_kw 2164 return YTick(self.axes, 0, '', major=major, **tick_kw) 2165 2166 def _get_label(self): 2167 # x in display coords (updated by _update_label_position) 2168 # y in axes coords 2169 label = mtext.Text(x=0, y=0.5, 2170 # todo: get the label position 2171 fontproperties=font_manager.FontProperties( 2172 size=rcParams['axes.labelsize'], 2173 weight=rcParams['axes.labelweight']), 2174 color=rcParams['axes.labelcolor'], 2175 verticalalignment='bottom', 2176 horizontalalignment='center', 2177 rotation='vertical', 2178 rotation_mode='anchor') 2179 label.set_transform(mtransforms.blended_transform_factory( 2180 mtransforms.IdentityTransform(), self.axes.transAxes)) 2181 2182 self._set_artist_props(label) 2183 self.label_position = 'left' 2184 return label 2185 2186 def _get_offset_text(self): 2187 # x in display coords, y in axes coords (to be updated at draw time) 2188 offsetText = mtext.Text(x=0, y=0.5, 2189 fontproperties=font_manager.FontProperties( 2190 size=rcParams['ytick.labelsize'] 2191 ), 2192 color=rcParams['ytick.color'], 2193 verticalalignment='baseline', 2194 horizontalalignment='left') 2195 offsetText.set_transform(mtransforms.blended_transform_factory( 2196 self.axes.transAxes, mtransforms.IdentityTransform()) 2197 ) 2198 self._set_artist_props(offsetText) 2199 self.offset_text_position = 'left' 2200 return offsetText 2201 2202 def _get_pixel_distance_along_axis(self, where, perturb): 2203 """ 2204 Returns the amount, in data coordinates, that a single pixel 2205 corresponds to in the locality given by *where*, which is also given 2206 in data coordinates, and is a y coordinate. 2207 2208 *perturb* is the amount to perturb the pixel. Usually +0.5 or -0.5. 2209 2210 Implementing this routine for an axis is optional; if present, it will 2211 ensure that no ticks are lost due to round-off at the extreme ends of 2212 an axis. 2213 """ 2214 2215 # 2216 # first figure out the pixel location of the "where" point. We use 2217 # 1e-10 for the x point, so that we remain compatible with log axes. 2218 2219 # transformation from data coords to display coords 2220 trans = self.axes.transData 2221 # transformation from display coords to data coords 2222 transinv = trans.inverted() 2223 pix = trans.transform_point((1e-10, where)) 2224 # perturb the pixel 2225 ptp = transinv.transform_point((pix[0], pix[1] + perturb)) 2226 dy = abs(ptp[1] - where) 2227 return dy 2228 2229 def set_label_position(self, position): 2230 """ 2231 Set the label position (left or right) 2232 2233 ACCEPTS: [ 'left' | 'right' ] 2234 """ 2235 self.label.set_rotation_mode('anchor') 2236 self.label.set_horizontalalignment('center') 2237 if position == 'left': 2238 self.label.set_verticalalignment('bottom') 2239 elif position == 'right': 2240 self.label.set_verticalalignment('top') 2241 else: 2242 raise ValueError("Position accepts only 'left' or 'right'") 2243 self.label_position = position 2244 self.stale = True 2245 2246 def _get_tick_boxes_siblings(self, renderer): 2247 """ 2248 Get the bounding boxes for this `.axis` and its siblings 2249 as set by `.Figure.align_xlabels` or `.Figure.align_ylablels`. 2250 2251 By default it just gets bboxes for self. 2252 """ 2253 bboxes = [] 2254 bboxes2 = [] 2255 # get the Grouper that keeps track of y-label groups for this figure 2256 grp = self.figure._align_ylabel_grp 2257 # if we want to align labels from other axes: 2258 for axx in grp.get_siblings(self.axes): 2259 ticks_to_draw = axx.yaxis._update_ticks(renderer) 2260 tlb, tlb2 = axx.yaxis._get_tick_bboxes(ticks_to_draw, renderer) 2261 bboxes.extend(tlb) 2262 bboxes2.extend(tlb2) 2263 return bboxes, bboxes2 2264 2265 def _update_label_position(self, renderer): 2266 """ 2267 Update the label position based on the bounding box enclosing 2268 all the ticklabels and axis spine 2269 """ 2270 if not self._autolabelpos: 2271 return 2272 2273 # get bounding boxes for this axis and any siblings 2274 # that have been set by `fig.align_ylabels()` 2275 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer) 2276 2277 x, y = self.label.get_position() 2278 if self.label_position == 'left': 2279 try: 2280 spine = self.axes.spines['left'] 2281 spinebbox = spine.get_transform().transform_path( 2282 spine.get_path()).get_extents() 2283 except KeyError: 2284 # use axes if spine doesn't exist 2285 spinebbox = self.axes.bbox 2286 bbox = mtransforms.Bbox.union(bboxes + [spinebbox]) 2287 left = bbox.x0 2288 self.label.set_position( 2289 (left - self.labelpad * self.figure.dpi / 72.0, y) 2290 ) 2291 2292 else: 2293 try: 2294 spine = self.axes.spines['right'] 2295 spinebbox = spine.get_transform().transform_path( 2296 spine.get_path()).get_extents() 2297 except KeyError: 2298 # use axes if spine doesn't exist 2299 spinebbox = self.axes.bbox 2300 bbox = mtransforms.Bbox.union(bboxes2 + [spinebbox]) 2301 right = bbox.x1 2302 2303 self.label.set_position( 2304 (right + self.labelpad * self.figure.dpi / 72.0, y) 2305 ) 2306 2307 def _update_offset_text_position(self, bboxes, bboxes2): 2308 """ 2309 Update the offset_text position based on the sequence of bounding 2310 boxes of all the ticklabels 2311 """ 2312 x, y = self.offsetText.get_position() 2313 top = self.axes.bbox.ymax 2314 self.offsetText.set_position( 2315 (x, top + self.OFFSETTEXTPAD * self.figure.dpi / 72.0) 2316 ) 2317 2318 def set_offset_position(self, position): 2319 """ 2320 .. ACCEPTS: [ 'left' | 'right' ] 2321 """ 2322 x, y = self.offsetText.get_position() 2323 if position == 'left': 2324 x = 0 2325 elif position == 'right': 2326 x = 1 2327 else: 2328 raise ValueError("Position accepts only [ 'left' | 'right' ]") 2329 2330 self.offsetText.set_ha(position) 2331 self.offsetText.set_position((x, y)) 2332 self.stale = True 2333 2334 def get_text_widths(self, renderer): 2335 bbox, bbox2 = self.get_ticklabel_extents(renderer) 2336 # MGDTODO: Need a better way to get the pad 2337 padPixels = self.majorTicks[0].get_pad_pixels() 2338 2339 left = 0.0 2340 if bbox.width: 2341 left += bbox.width + padPixels 2342 right = 0.0 2343 if bbox2.width: 2344 right += bbox2.width + padPixels 2345 2346 if self.get_label_position() == 'left': 2347 left += self.label.get_window_extent(renderer).width + padPixels 2348 else: 2349 right += self.label.get_window_extent(renderer).width + padPixels 2350 return left, right 2351 2352 def set_ticks_position(self, position): 2353 """ 2354 Set the ticks position (left, right, both, default or none) 2355 'both' sets the ticks to appear on both positions, but does not 2356 change the tick labels. 'default' resets the tick positions to 2357 the default: ticks on both positions, labels at left. 'none' 2358 can be used if you don't want any ticks. 'none' and 'both' 2359 affect only the ticks, not the labels. 2360 2361 ACCEPTS: [ 'left' | 'right' | 'both' | 'default' | 'none' ] 2362 """ 2363 if position == 'right': 2364 self.set_tick_params(which='both', right=True, labelright=True, 2365 left=False, labelleft=False) 2366 self.set_offset_position(position) 2367 elif position == 'left': 2368 self.set_tick_params(which='both', right=False, labelright=False, 2369 left=True, labelleft=True) 2370 self.set_offset_position(position) 2371 elif position == 'both': 2372 self.set_tick_params(which='both', right=True, 2373 left=True) 2374 elif position == 'none': 2375 self.set_tick_params(which='both', right=False, 2376 left=False) 2377 elif position == 'default': 2378 self.set_tick_params(which='both', right=True, labelright=False, 2379 left=True, labelleft=True) 2380 else: 2381 raise ValueError("invalid position: %s" % position) 2382 self.stale = True 2383 2384 def tick_right(self): 2385 """ 2386 Move ticks and ticklabels (if present) to the right of the axes. 2387 """ 2388 label = True 2389 if 'label1On' in self._major_tick_kw: 2390 label = (self._major_tick_kw['label1On'] 2391 or self._major_tick_kw['label2On']) 2392 self.set_ticks_position('right') 2393 # if labels were turned off before this was called 2394 # leave them off 2395 self.set_tick_params(which='both', labelright=label) 2396 2397 def tick_left(self): 2398 """ 2399 Move ticks and ticklabels (if present) to the left of the axes. 2400 """ 2401 label = True 2402 if 'label1On' in self._major_tick_kw: 2403 label = (self._major_tick_kw['label1On'] 2404 or self._major_tick_kw['label2On']) 2405 self.set_ticks_position('left') 2406 # if labels were turned off before this was called 2407 # leave them off 2408 self.set_tick_params(which='both', labelleft=label) 2409 2410 def get_ticks_position(self): 2411 """ 2412 Return the ticks position (left, right, both or unknown) 2413 """ 2414 majt = self.majorTicks[0] 2415 mT = self.minorTicks[0] 2416 2417 majorRight = ((not majt.tick1On) and majt.tick2On and 2418 (not majt.label1On) and majt.label2On) 2419 minorRight = ((not mT.tick1On) and mT.tick2On and 2420 (not mT.label1On) and mT.label2On) 2421 if majorRight and minorRight: 2422 return 'right' 2423 2424 majorLeft = (majt.tick1On and (not majt.tick2On) and 2425 majt.label1On and (not majt.label2On)) 2426 minorLeft = (mT.tick1On and (not mT.tick2On) and 2427 mT.label1On and (not mT.label2On)) 2428 if majorLeft and minorLeft: 2429 return 'left' 2430 2431 majorDefault = (majt.tick1On and majt.tick2On and 2432 majt.label1On and (not majt.label2On)) 2433 minorDefault = (mT.tick1On and mT.tick2On and 2434 mT.label1On and (not mT.label2On)) 2435 if majorDefault and minorDefault: 2436 return 'default' 2437 2438 return 'unknown' 2439 2440 def get_view_interval(self): 2441 'return the Interval instance for this axis view limits' 2442 return self.axes.viewLim.intervaly 2443 2444 def set_view_interval(self, vmin, vmax, ignore=False): 2445 """ 2446 If *ignore* is *False*, the order of vmin, vmax 2447 does not matter; the original axis orientation will 2448 be preserved. In addition, the view limits can be 2449 expanded, but will not be reduced. This method is 2450 for mpl internal use; for normal use, see 2451 :meth:`~matplotlib.axes.Axes.set_ylim`. 2452 2453 """ 2454 if ignore: 2455 self.axes.viewLim.intervaly = vmin, vmax 2456 else: 2457 Vmin, Vmax = self.get_view_interval() 2458 if Vmin < Vmax: 2459 self.axes.viewLim.intervaly = (min(vmin, vmax, Vmin), 2460 max(vmin, vmax, Vmax)) 2461 else: 2462 self.axes.viewLim.intervaly = (max(vmin, vmax, Vmin), 2463 min(vmin, vmax, Vmax)) 2464 self.stale = True 2465 2466 def get_minpos(self): 2467 return self.axes.dataLim.minposy 2468 2469 def get_data_interval(self): 2470 'return the Interval instance for this axis data limits' 2471 return self.axes.dataLim.intervaly 2472 2473 def set_data_interval(self, vmin, vmax, ignore=False): 2474 'set the axis data limits' 2475 if ignore: 2476 self.axes.dataLim.intervaly = vmin, vmax 2477 else: 2478 Vmin, Vmax = self.get_data_interval() 2479 self.axes.dataLim.intervaly = min(vmin, Vmin), max(vmax, Vmax) 2480 self.stale = True 2481 2482 def set_default_intervals(self): 2483 'set the default limits for the axis interval if they are not mutated' 2484 ymin, ymax = 0., 1. 2485 dataMutated = self.axes.dataLim.mutatedy() 2486 viewMutated = self.axes.viewLim.mutatedy() 2487 if not dataMutated or not viewMutated: 2488 if self.converter is not None: 2489 info = self.converter.axisinfo(self.units, self) 2490 if info.default_limits is not None: 2491 valmin, valmax = info.default_limits 2492 ymin = self.converter.convert(valmin, self.units, self) 2493 ymax = self.converter.convert(valmax, self.units, self) 2494 if not dataMutated: 2495 self.axes.dataLim.intervaly = ymin, ymax 2496 if not viewMutated: 2497 self.axes.viewLim.intervaly = ymin, ymax 2498 self.stale = True 2499 2500 def get_tick_space(self): 2501 ends = self.axes.transAxes.transform([[0, 0], [0, 1]]) 2502 length = ((ends[1][1] - ends[0][1]) / self.axes.figure.dpi) * 72.0 2503 tick = self._get_tick(True) 2504 # Having a spacing of at least 2 just looks good. 2505 size = tick.label1.get_size() * 2.0 2506 if size > 0: 2507 return int(np.floor(length / size)) 2508 else: 2509 return 2**31 - 1 2510