1""" 2Tick locating and formatting 3============================ 4 5This module contains classes to support completely configurable tick 6locating and formatting. Although the locators know nothing about major 7or minor ticks, they are used by the Axis class to support major and 8minor tick locating and formatting. Generic tick locators and 9formatters are provided, as well as domain specific custom ones. 10 11Default Formatter 12----------------- 13 14The default formatter identifies when the x-data being plotted is a 15small range on top of a large offset. To reduce the chances that the 16ticklabels overlap, the ticks are labeled as deltas from a fixed offset. 17For example:: 18 19 ax.plot(np.arange(2000, 2010), range(10)) 20 21will have tick of 0-9 with an offset of +2e3. If this is not desired 22turn off the use of the offset on the default formatter:: 23 24 ax.get_xaxis().get_major_formatter().set_useOffset(False) 25 26set the rcParam ``axes.formatter.useoffset=False`` to turn it off 27globally, or set a different formatter. 28 29Tick locating 30------------- 31 32The Locator class is the base class for all tick locators. The locators 33handle autoscaling of the view limits based on the data limits, and the 34choosing of tick locations. A useful semi-automatic tick locator is 35`MultipleLocator`. It is initialized with a base, e.g., 10, and it picks 36axis limits and ticks that are multiples of that base. 37 38The Locator subclasses defined here are 39 40:class:`AutoLocator` 41 `MaxNLocator` with simple defaults. This is the default tick locator for 42 most plotting. 43 44:class:`MaxNLocator` 45 Finds up to a max number of intervals with ticks at nice locations. 46 47:class:`LinearLocator` 48 Space ticks evenly from min to max. 49 50:class:`LogLocator` 51 Space ticks logarithmically from min to max. 52 53:class:`MultipleLocator` 54 Ticks and range are a multiple of base; either integer or float. 55 56:class:`FixedLocator` 57 Tick locations are fixed. 58 59:class:`IndexLocator` 60 Locator for index plots (e.g., where ``x = range(len(y))``). 61 62:class:`NullLocator` 63 No ticks. 64 65:class:`SymmetricalLogLocator` 66 Locator for use with with the symlog norm; works like `LogLocator` for the 67 part outside of the threshold and adds 0 if inside the limits. 68 69:class:`LogitLocator` 70 Locator for logit scaling. 71 72:class:`OldAutoLocator` 73 Choose a `MultipleLocator` and dynamically reassign it for intelligent 74 ticking during navigation. 75 76:class:`AutoMinorLocator` 77 Locator for minor ticks when the axis is linear and the 78 major ticks are uniformly spaced. Subdivides the major 79 tick interval into a specified number of minor intervals, 80 defaulting to 4 or 5 depending on the major interval. 81 82 83There are a number of locators specialized for date locations - see 84the `dates` module. 85 86You can define your own locator by deriving from Locator. You must 87override the ``__call__`` method, which returns a sequence of locations, 88and you will probably want to override the autoscale method to set the 89view limits from the data limits. 90 91If you want to override the default locator, use one of the above or a custom 92locator and pass it to the x or y axis instance. The relevant methods are:: 93 94 ax.xaxis.set_major_locator(xmajor_locator) 95 ax.xaxis.set_minor_locator(xminor_locator) 96 ax.yaxis.set_major_locator(ymajor_locator) 97 ax.yaxis.set_minor_locator(yminor_locator) 98 99The default minor locator is `NullLocator`, i.e., no minor ticks on by default. 100 101Tick formatting 102--------------- 103 104Tick formatting is controlled by classes derived from Formatter. The formatter 105operates on a single tick value and returns a string to the axis. 106 107:class:`NullFormatter` 108 No labels on the ticks. 109 110:class:`IndexFormatter` 111 Set the strings from a list of labels. 112 113:class:`FixedFormatter` 114 Set the strings manually for the labels. 115 116:class:`FuncFormatter` 117 User defined function sets the labels. 118 119:class:`StrMethodFormatter` 120 Use string `format` method. 121 122:class:`FormatStrFormatter` 123 Use an old-style sprintf format string. 124 125:class:`ScalarFormatter` 126 Default formatter for scalars: autopick the format string. 127 128:class:`LogFormatter` 129 Formatter for log axes. 130 131:class:`LogFormatterExponent` 132 Format values for log axis using ``exponent = log_base(value)``. 133 134:class:`LogFormatterMathtext` 135 Format values for log axis using ``exponent = log_base(value)`` 136 using Math text. 137 138:class:`LogFormatterSciNotation` 139 Format values for log axis using scientific notation. 140 141:class:`LogitFormatter` 142 Probability formatter. 143 144:class:`EngFormatter` 145 Format labels in engineering notation 146 147:class:`PercentFormatter` 148 Format labels as a percentage 149 150You can derive your own formatter from the Formatter base class by 151simply overriding the ``__call__`` method. The formatter class has 152access to the axis view and data limits. 153 154To control the major and minor tick label formats, use one of the 155following methods:: 156 157 ax.xaxis.set_major_formatter(xmajor_formatter) 158 ax.xaxis.set_minor_formatter(xminor_formatter) 159 ax.yaxis.set_major_formatter(ymajor_formatter) 160 ax.yaxis.set_minor_formatter(yminor_formatter) 161 162See :doc:`/gallery/ticks_and_spines/major_minor_demo` for an 163example of setting major and minor ticks. See the :mod:`matplotlib.dates` 164module for more information and examples of using date locators and formatters. 165""" 166 167from __future__ import (absolute_import, division, print_function, 168 unicode_literals) 169 170import six 171 172import itertools 173import locale 174import math 175import numpy as np 176from matplotlib import rcParams 177from matplotlib import cbook 178from matplotlib import transforms as mtransforms 179from matplotlib.cbook import mplDeprecation 180 181import warnings 182 183 184__all__ = ('TickHelper', 'Formatter', 'FixedFormatter', 185 'NullFormatter', 'FuncFormatter', 'FormatStrFormatter', 186 'StrMethodFormatter', 'ScalarFormatter', 'LogFormatter', 187 'LogFormatterExponent', 'LogFormatterMathtext', 188 'IndexFormatter', 'LogFormatterSciNotation', 189 'LogitFormatter', 'EngFormatter', 'PercentFormatter', 190 'Locator', 'IndexLocator', 'FixedLocator', 'NullLocator', 191 'LinearLocator', 'LogLocator', 'AutoLocator', 192 'MultipleLocator', 'MaxNLocator', 'AutoMinorLocator', 193 'SymmetricalLogLocator', 'LogitLocator') 194 195 196if six.PY3: 197 long = int 198 199 200# Work around numpy/numpy#6127. 201def _divmod(x, y): 202 if isinstance(x, np.generic): 203 x = x.item() 204 if isinstance(y, np.generic): 205 y = y.item() 206 return six.moves.builtins.divmod(x, y) 207 208 209def _mathdefault(s): 210 return '\\mathdefault{%s}' % s 211 212 213class _DummyAxis(object): 214 def __init__(self, minpos=0): 215 self.dataLim = mtransforms.Bbox.unit() 216 self.viewLim = mtransforms.Bbox.unit() 217 self._minpos = minpos 218 219 def get_view_interval(self): 220 return self.viewLim.intervalx 221 222 def set_view_interval(self, vmin, vmax): 223 self.viewLim.intervalx = vmin, vmax 224 225 def get_minpos(self): 226 return self._minpos 227 228 def get_data_interval(self): 229 return self.dataLim.intervalx 230 231 def set_data_interval(self, vmin, vmax): 232 self.dataLim.intervalx = vmin, vmax 233 234 def get_tick_space(self): 235 # Just use the long-standing default of nbins==9 236 return 9 237 238 239class TickHelper(object): 240 axis = None 241 242 def set_axis(self, axis): 243 self.axis = axis 244 245 def create_dummy_axis(self, **kwargs): 246 if self.axis is None: 247 self.axis = _DummyAxis(**kwargs) 248 249 def set_view_interval(self, vmin, vmax): 250 self.axis.set_view_interval(vmin, vmax) 251 252 def set_data_interval(self, vmin, vmax): 253 self.axis.set_data_interval(vmin, vmax) 254 255 def set_bounds(self, vmin, vmax): 256 self.set_view_interval(vmin, vmax) 257 self.set_data_interval(vmin, vmax) 258 259 260class Formatter(TickHelper): 261 """ 262 Create a string based on a tick value and location. 263 """ 264 # some classes want to see all the locs to help format 265 # individual ones 266 locs = [] 267 268 def __call__(self, x, pos=None): 269 """ 270 Return the format for tick value *x* at position pos. 271 ``pos=None`` indicates an unspecified location. 272 """ 273 raise NotImplementedError('Derived must override') 274 275 def format_data(self, value): 276 """ 277 Returns the full string representation of the value with the 278 position unspecified. 279 """ 280 return self.__call__(value) 281 282 def format_data_short(self, value): 283 """ 284 Return a short string version of the tick value. 285 286 Defaults to the position-independent long value. 287 """ 288 return self.format_data(value) 289 290 def get_offset(self): 291 return '' 292 293 def set_locs(self, locs): 294 self.locs = locs 295 296 def fix_minus(self, s): 297 """ 298 Some classes may want to replace a hyphen for minus with the 299 proper unicode symbol (U+2212) for typographical correctness. 300 The default is to not replace it. 301 302 Note, if you use this method, e.g., in :meth:`format_data` or 303 call, you probably don't want to use it for 304 :meth:`format_data_short` since the toolbar uses this for 305 interactive coord reporting and I doubt we can expect GUIs 306 across platforms will handle the unicode correctly. So for 307 now the classes that override :meth:`fix_minus` should have an 308 explicit :meth:`format_data_short` method 309 """ 310 return s 311 312 313class IndexFormatter(Formatter): 314 """ 315 Format the position x to the nearest i-th label where i=int(x+0.5) 316 """ 317 def __init__(self, labels): 318 self.labels = labels 319 self.n = len(labels) 320 321 def __call__(self, x, pos=None): 322 """ 323 Return the format for tick value `x` at position pos. 324 325 The position is ignored and the value is rounded to the nearest 326 integer, which is used to look up the label. 327 """ 328 i = int(x + 0.5) 329 if i < 0 or i >= self.n: 330 return '' 331 else: 332 return self.labels[i] 333 334 335class NullFormatter(Formatter): 336 """ 337 Always return the empty string. 338 """ 339 def __call__(self, x, pos=None): 340 """ 341 Returns an empty string for all inputs. 342 """ 343 return '' 344 345 346class FixedFormatter(Formatter): 347 """ 348 Return fixed strings for tick labels based only on position, not 349 value. 350 """ 351 def __init__(self, seq): 352 """ 353 Set the sequence of strings that will be used for labels. 354 """ 355 self.seq = seq 356 self.offset_string = '' 357 358 def __call__(self, x, pos=None): 359 """ 360 Returns the label that matches the position regardless of the 361 value. 362 363 For positions ``pos < len(seq)``, return `seq[i]` regardless of 364 `x`. Otherwise return empty string. `seq` is the sequence of 365 strings that this object was initialized with. 366 """ 367 if pos is None or pos >= len(self.seq): 368 return '' 369 else: 370 return self.seq[pos] 371 372 def get_offset(self): 373 return self.offset_string 374 375 def set_offset_string(self, ofs): 376 self.offset_string = ofs 377 378 379class FuncFormatter(Formatter): 380 """ 381 Use a user-defined function for formatting. 382 383 The function should take in two inputs (a tick value ``x`` and a 384 position ``pos``), and return a string containing the corresponding 385 tick label. 386 """ 387 def __init__(self, func): 388 self.func = func 389 390 def __call__(self, x, pos=None): 391 """ 392 Return the value of the user defined function. 393 394 `x` and `pos` are passed through as-is. 395 """ 396 return self.func(x, pos) 397 398 399class FormatStrFormatter(Formatter): 400 """ 401 Use an old-style ('%' operator) format string to format the tick. 402 403 The format string should have a single variable format (%) in it. 404 It will be applied to the value (not the position) of the tick. 405 """ 406 def __init__(self, fmt): 407 self.fmt = fmt 408 409 def __call__(self, x, pos=None): 410 """ 411 Return the formatted label string. 412 413 Only the value `x` is formatted. The position is ignored. 414 """ 415 return self.fmt % x 416 417 418class StrMethodFormatter(Formatter): 419 """ 420 Use a new-style format string (as used by `str.format()`) 421 to format the tick. 422 423 The field used for the value must be labeled `x` and the field used 424 for the position must be labeled `pos`. 425 """ 426 def __init__(self, fmt): 427 self.fmt = fmt 428 429 def __call__(self, x, pos=None): 430 """ 431 Return the formatted label string. 432 433 `x` and `pos` are passed to `str.format` as keyword arguments 434 with those exact names. 435 """ 436 return self.fmt.format(x=x, pos=pos) 437 438 439class OldScalarFormatter(Formatter): 440 """ 441 Tick location is a plain old number. 442 """ 443 444 def __call__(self, x, pos=None): 445 """ 446 Return the format for tick val `x` based on the width of the 447 axis. 448 449 The position `pos` is ignored. 450 """ 451 xmin, xmax = self.axis.get_view_interval() 452 d = abs(xmax - xmin) 453 454 return self.pprint_val(x, d) 455 456 def pprint_val(self, x, d): 457 """ 458 Formats the value `x` based on the size of the axis range `d`. 459 """ 460 #if the number is not too big and it's an int, format it as an 461 #int 462 if abs(x) < 1e4 and x == int(x): 463 return '%d' % x 464 465 if d < 1e-2: 466 fmt = '%1.3e' 467 elif d < 1e-1: 468 fmt = '%1.3f' 469 elif d > 1e5: 470 fmt = '%1.1e' 471 elif d > 10: 472 fmt = '%1.1f' 473 elif d > 1: 474 fmt = '%1.2f' 475 else: 476 fmt = '%1.3f' 477 s = fmt % x 478 tup = s.split('e') 479 if len(tup) == 2: 480 mantissa = tup[0].rstrip('0').rstrip('.') 481 sign = tup[1][0].replace('+', '') 482 exponent = tup[1][1:].lstrip('0') 483 s = '%se%s%s' % (mantissa, sign, exponent) 484 else: 485 s = s.rstrip('0').rstrip('.') 486 return s 487 488 489class ScalarFormatter(Formatter): 490 """ 491 Format tick values as a number. 492 493 Tick value is interpreted as a plain old number. If 494 ``useOffset==True`` and the data range is much smaller than the data 495 average, then an offset will be determined such that the tick labels 496 are meaningful. Scientific notation is used for ``data < 10^-n`` or 497 ``data >= 10^m``, where ``n`` and ``m`` are the power limits set 498 using ``set_powerlimits((n,m))``. The defaults for these are 499 controlled by the ``axes.formatter.limits`` rc parameter. 500 """ 501 def __init__(self, useOffset=None, useMathText=None, useLocale=None): 502 # useOffset allows plotting small data ranges with large offsets: for 503 # example: [1+1e-9,1+2e-9,1+3e-9] useMathText will render the offset 504 # and scientific notation in mathtext 505 506 if useOffset is None: 507 useOffset = rcParams['axes.formatter.useoffset'] 508 self._offset_threshold = rcParams['axes.formatter.offset_threshold'] 509 self.set_useOffset(useOffset) 510 self._usetex = rcParams['text.usetex'] 511 if useMathText is None: 512 useMathText = rcParams['axes.formatter.use_mathtext'] 513 self.set_useMathText(useMathText) 514 self.orderOfMagnitude = 0 515 self.format = '' 516 self._scientific = True 517 self._powerlimits = rcParams['axes.formatter.limits'] 518 if useLocale is None: 519 useLocale = rcParams['axes.formatter.use_locale'] 520 self._useLocale = useLocale 521 522 def get_useOffset(self): 523 return self._useOffset 524 525 def set_useOffset(self, val): 526 if val in [True, False]: 527 self.offset = 0 528 self._useOffset = val 529 else: 530 self._useOffset = False 531 self.offset = val 532 533 useOffset = property(fget=get_useOffset, fset=set_useOffset) 534 535 def get_useLocale(self): 536 return self._useLocale 537 538 def set_useLocale(self, val): 539 if val is None: 540 self._useLocale = rcParams['axes.formatter.use_locale'] 541 else: 542 self._useLocale = val 543 544 useLocale = property(fget=get_useLocale, fset=set_useLocale) 545 546 def get_useMathText(self): 547 return self._useMathText 548 549 def set_useMathText(self, val): 550 if val is None: 551 self._useMathText = rcParams['axes.formatter.use_mathtext'] 552 else: 553 self._useMathText = val 554 555 useMathText = property(fget=get_useMathText, fset=set_useMathText) 556 557 def fix_minus(self, s): 558 """ 559 Replace hyphens with a unicode minus. 560 """ 561 if rcParams['text.usetex'] or not rcParams['axes.unicode_minus']: 562 return s 563 else: 564 return s.replace('-', '\N{MINUS SIGN}') 565 566 def __call__(self, x, pos=None): 567 """ 568 Return the format for tick value `x` at position `pos`. 569 """ 570 if len(self.locs) == 0: 571 return '' 572 else: 573 s = self.pprint_val(x) 574 return self.fix_minus(s) 575 576 def set_scientific(self, b): 577 """ 578 Turn scientific notation on or off. 579 580 .. seealso:: Method :meth:`set_powerlimits` 581 """ 582 self._scientific = bool(b) 583 584 def set_powerlimits(self, lims): 585 """ 586 Sets size thresholds for scientific notation. 587 588 Parameters 589 ---------- 590 lims : (min_exp, max_exp) 591 A tuple containing the powers of 10 that determine the switchover 592 threshold. Numbers below ``10**min_exp`` and above ``10**max_exp`` 593 will be displayed in scientific notation. 594 595 For example, ``formatter.set_powerlimits((-3, 4))`` sets the 596 pre-2007 default in which scientific notation is used for 597 numbers less than 1e-3 or greater than 1e4. 598 599 .. seealso:: Method :meth:`set_scientific` 600 """ 601 if len(lims) != 2: 602 raise ValueError("'lims' must be a sequence of length 2") 603 self._powerlimits = lims 604 605 def format_data_short(self, value): 606 """ 607 Return a short formatted string representation of a number. 608 """ 609 if self._useLocale: 610 return locale.format_string('%-12g', (value,)) 611 else: 612 return '%-12g' % value 613 614 def format_data(self, value): 615 """ 616 Return a formatted string representation of a number. 617 """ 618 if self._useLocale: 619 s = locale.format_string('%1.10e', (value,)) 620 else: 621 s = '%1.10e' % value 622 s = self._formatSciNotation(s) 623 return self.fix_minus(s) 624 625 def get_offset(self): 626 """ 627 Return scientific notation, plus offset. 628 """ 629 if len(self.locs) == 0: 630 return '' 631 s = '' 632 if self.orderOfMagnitude or self.offset: 633 offsetStr = '' 634 sciNotStr = '' 635 if self.offset: 636 offsetStr = self.format_data(self.offset) 637 if self.offset > 0: 638 offsetStr = '+' + offsetStr 639 if self.orderOfMagnitude: 640 if self._usetex or self._useMathText: 641 sciNotStr = self.format_data(10 ** self.orderOfMagnitude) 642 else: 643 sciNotStr = '1e%d' % self.orderOfMagnitude 644 if self._useMathText: 645 if sciNotStr != '': 646 sciNotStr = r'\times%s' % _mathdefault(sciNotStr) 647 s = ''.join(('$', sciNotStr, _mathdefault(offsetStr), '$')) 648 elif self._usetex: 649 if sciNotStr != '': 650 sciNotStr = r'\times%s' % sciNotStr 651 s = ''.join(('$', sciNotStr, offsetStr, '$')) 652 else: 653 s = ''.join((sciNotStr, offsetStr)) 654 655 return self.fix_minus(s) 656 657 def set_locs(self, locs): 658 """ 659 Set the locations of the ticks. 660 """ 661 self.locs = locs 662 if len(self.locs) > 0: 663 vmin, vmax = self.axis.get_view_interval() 664 d = abs(vmax - vmin) 665 if self._useOffset: 666 self._compute_offset() 667 self._set_orderOfMagnitude(d) 668 self._set_format(vmin, vmax) 669 670 def _compute_offset(self): 671 locs = self.locs 672 if locs is None or not len(locs): 673 self.offset = 0 674 return 675 # Restrict to visible ticks. 676 vmin, vmax = sorted(self.axis.get_view_interval()) 677 locs = np.asarray(locs) 678 locs = locs[(vmin <= locs) & (locs <= vmax)] 679 if not len(locs): 680 self.offset = 0 681 return 682 lmin, lmax = locs.min(), locs.max() 683 # Only use offset if there are at least two ticks and every tick has 684 # the same sign. 685 if lmin == lmax or lmin <= 0 <= lmax: 686 self.offset = 0 687 return 688 # min, max comparing absolute values (we want division to round towards 689 # zero so we work on absolute values). 690 abs_min, abs_max = sorted([abs(float(lmin)), abs(float(lmax))]) 691 sign = math.copysign(1, lmin) 692 # What is the smallest power of ten such that abs_min and abs_max are 693 # equal up to that precision? 694 # Note: Internally using oom instead of 10 ** oom avoids some numerical 695 # accuracy issues. 696 oom_max = np.ceil(math.log10(abs_max)) 697 oom = 1 + next(oom for oom in itertools.count(oom_max, -1) 698 if abs_min // 10 ** oom != abs_max // 10 ** oom) 699 if (abs_max - abs_min) / 10 ** oom <= 1e-2: 700 # Handle the case of straddling a multiple of a large power of ten 701 # (relative to the span). 702 # What is the smallest power of ten such that abs_min and abs_max 703 # are no more than 1 apart at that precision? 704 oom = 1 + next(oom for oom in itertools.count(oom_max, -1) 705 if abs_max // 10 ** oom - abs_min // 10 ** oom > 1) 706 # Only use offset if it saves at least _offset_threshold digits. 707 n = self._offset_threshold - 1 708 self.offset = (sign * (abs_max // 10 ** oom) * 10 ** oom 709 if abs_max // 10 ** oom >= 10**n 710 else 0) 711 712 def _set_orderOfMagnitude(self, range): 713 # if scientific notation is to be used, find the appropriate exponent 714 # if using an numerical offset, find the exponent after applying the 715 # offset 716 if not self._scientific: 717 self.orderOfMagnitude = 0 718 return 719 locs = np.abs(self.locs) 720 if self.offset: 721 oom = math.floor(math.log10(range)) 722 else: 723 if locs[0] > locs[-1]: 724 val = locs[0] 725 else: 726 val = locs[-1] 727 if val == 0: 728 oom = 0 729 else: 730 oom = math.floor(math.log10(val)) 731 if oom <= self._powerlimits[0]: 732 self.orderOfMagnitude = oom 733 elif oom >= self._powerlimits[1]: 734 self.orderOfMagnitude = oom 735 else: 736 self.orderOfMagnitude = 0 737 738 def _set_format(self, vmin, vmax): 739 # set the format string to format all the ticklabels 740 if len(self.locs) < 2: 741 # Temporarily augment the locations with the axis end points. 742 _locs = list(self.locs) + [vmin, vmax] 743 else: 744 _locs = self.locs 745 locs = (np.asarray(_locs) - self.offset) / 10. ** self.orderOfMagnitude 746 loc_range = np.ptp(locs) 747 # Curvilinear coordinates can yield two identical points. 748 if loc_range == 0: 749 loc_range = np.max(np.abs(locs)) 750 # Both points might be zero. 751 if loc_range == 0: 752 loc_range = 1 753 if len(self.locs) < 2: 754 # We needed the end points only for the loc_range calculation. 755 locs = locs[:-2] 756 loc_range_oom = int(math.floor(math.log10(loc_range))) 757 # first estimate: 758 sigfigs = max(0, 3 - loc_range_oom) 759 # refined estimate: 760 thresh = 1e-3 * 10 ** loc_range_oom 761 while sigfigs >= 0: 762 if np.abs(locs - np.round(locs, decimals=sigfigs)).max() < thresh: 763 sigfigs -= 1 764 else: 765 break 766 sigfigs += 1 767 self.format = '%1.' + str(sigfigs) + 'f' 768 if self._usetex: 769 self.format = '$%s$' % self.format 770 elif self._useMathText: 771 self.format = '$%s$' % _mathdefault(self.format) 772 773 def pprint_val(self, x): 774 xp = (x - self.offset) / (10. ** self.orderOfMagnitude) 775 if np.abs(xp) < 1e-8: 776 xp = 0 777 if self._useLocale: 778 return locale.format_string(self.format, (xp,)) 779 else: 780 return self.format % xp 781 782 def _formatSciNotation(self, s): 783 # transform 1e+004 into 1e4, for example 784 if self._useLocale: 785 decimal_point = locale.localeconv()['decimal_point'] 786 positive_sign = locale.localeconv()['positive_sign'] 787 else: 788 decimal_point = '.' 789 positive_sign = '+' 790 tup = s.split('e') 791 try: 792 significand = tup[0].rstrip('0').rstrip(decimal_point) 793 sign = tup[1][0].replace(positive_sign, '') 794 exponent = tup[1][1:].lstrip('0') 795 if self._useMathText or self._usetex: 796 if significand == '1' and exponent != '': 797 # reformat 1x10^y as 10^y 798 significand = '' 799 if exponent: 800 exponent = '10^{%s%s}' % (sign, exponent) 801 if significand and exponent: 802 return r'%s{\times}%s' % (significand, exponent) 803 else: 804 return r'%s%s' % (significand, exponent) 805 else: 806 s = ('%se%s%s' % (significand, sign, exponent)).rstrip('e') 807 return s 808 except IndexError: 809 return s 810 811 812class LogFormatter(Formatter): 813 """ 814 Base class for formatting ticks on a log or symlog scale. 815 816 It may be instantiated directly, or subclassed. 817 818 Parameters 819 ---------- 820 base : float, optional, default: 10. 821 Base of the logarithm used in all calculations. 822 823 labelOnlyBase : bool, optional, default: False 824 If True, label ticks only at integer powers of base. 825 This is normally True for major ticks and False for 826 minor ticks. 827 828 minor_thresholds : (subset, all), optional, default: (1, 0.4) 829 If labelOnlyBase is False, these two numbers control 830 the labeling of ticks that are not at integer powers of 831 base; normally these are the minor ticks. The controlling 832 parameter is the log of the axis data range. In the typical 833 case where base is 10 it is the number of decades spanned 834 by the axis, so we can call it 'numdec'. If ``numdec <= all``, 835 all minor ticks will be labeled. If ``all < numdec <= subset``, 836 then only a subset of minor ticks will be labeled, so as to 837 avoid crowding. If ``numdec > subset`` then no minor ticks will 838 be labeled. 839 840 linthresh : None or float, optional, default: None 841 If a symmetric log scale is in use, its ``linthresh`` 842 parameter must be supplied here. 843 844 Notes 845 ----- 846 The `set_locs` method must be called to enable the subsetting 847 logic controlled by the ``minor_thresholds`` parameter. 848 849 In some cases such as the colorbar, there is no distinction between 850 major and minor ticks; the tick locations might be set manually, 851 or by a locator that puts ticks at integer powers of base and 852 at intermediate locations. For this situation, disable the 853 minor_thresholds logic by using ``minor_thresholds=(np.inf, np.inf)``, 854 so that all ticks will be labeled. 855 856 To disable labeling of minor ticks when 'labelOnlyBase' is False, 857 use ``minor_thresholds=(0, 0)``. This is the default for the 858 "classic" style. 859 860 Examples 861 -------- 862 To label a subset of minor ticks when the view limits span up 863 to 2 decades, and all of the ticks when zoomed in to 0.5 decades 864 or less, use ``minor_thresholds=(2, 0.5)``. 865 866 To label all minor ticks when the view limits span up to 1.5 867 decades, use ``minor_thresholds=(1.5, 1.5)``. 868 869 """ 870 def __init__(self, base=10.0, labelOnlyBase=False, 871 minor_thresholds=None, 872 linthresh=None): 873 874 self._base = float(base) 875 self.labelOnlyBase = labelOnlyBase 876 if minor_thresholds is None: 877 if rcParams['_internal.classic_mode']: 878 minor_thresholds = (0, 0) 879 else: 880 minor_thresholds = (1, 0.4) 881 self.minor_thresholds = minor_thresholds 882 self._sublabels = None 883 self._linthresh = linthresh 884 885 def base(self, base): 886 """ 887 Change the *base* for labeling. 888 889 .. warning:: 890 Should always match the base used for :class:`LogLocator` 891 892 """ 893 self._base = base 894 895 def label_minor(self, labelOnlyBase): 896 """ 897 Switch minor tick labeling on or off. 898 899 Parameters 900 ---------- 901 labelOnlyBase : bool 902 If True, label ticks only at integer powers of base. 903 904 """ 905 self.labelOnlyBase = labelOnlyBase 906 907 def set_locs(self, locs=None): 908 """ 909 Use axis view limits to control which ticks are labeled. 910 911 The *locs* parameter is ignored in the present algorithm. 912 913 """ 914 if np.isinf(self.minor_thresholds[0]): 915 self._sublabels = None 916 return 917 918 # Handle symlog case: 919 linthresh = self._linthresh 920 if linthresh is None: 921 try: 922 linthresh = self.axis.get_transform().linthresh 923 except AttributeError: 924 pass 925 926 vmin, vmax = self.axis.get_view_interval() 927 if vmin > vmax: 928 vmin, vmax = vmax, vmin 929 930 if linthresh is None and vmin <= 0: 931 # It's probably a colorbar with 932 # a format kwarg setting a LogFormatter in the manner 933 # that worked with 1.5.x, but that doesn't work now. 934 self._sublabels = set((1,)) # label powers of base 935 return 936 937 b = self._base 938 if linthresh is not None: # symlog 939 # Only compute the number of decades in the logarithmic part of the 940 # axis 941 numdec = 0 942 if vmin < -linthresh: 943 rhs = min(vmax, -linthresh) 944 numdec += math.log(vmin / rhs) / math.log(b) 945 if vmax > linthresh: 946 lhs = max(vmin, linthresh) 947 numdec += math.log(vmax / lhs) / math.log(b) 948 else: 949 vmin = math.log(vmin) / math.log(b) 950 vmax = math.log(vmax) / math.log(b) 951 numdec = abs(vmax - vmin) 952 953 if numdec > self.minor_thresholds[0]: 954 # Label only bases 955 self._sublabels = {1} 956 elif numdec > self.minor_thresholds[1]: 957 # Add labels between bases at log-spaced coefficients; 958 # include base powers in case the locations include 959 # "major" and "minor" points, as in colorbar. 960 c = np.logspace(0, 1, int(b)//2 + 1, base=b) 961 self._sublabels = set(np.round(c)) 962 # For base 10, this yields (1, 2, 3, 4, 6, 10). 963 else: 964 # Label all integer multiples of base**n. 965 self._sublabels = set(np.arange(1, b + 1)) 966 967 def _num_to_string(self, x, vmin, vmax): 968 if x > 10000: 969 s = '%1.0e' % x 970 elif x < 1: 971 s = '%1.0e' % x 972 else: 973 s = self.pprint_val(x, vmax - vmin) 974 return s 975 976 def __call__(self, x, pos=None): 977 """ 978 Return the format for tick val *x*. 979 """ 980 if x == 0.0: # Symlog 981 return '0' 982 983 x = abs(x) 984 b = self._base 985 # only label the decades 986 fx = math.log(x) / math.log(b) 987 is_x_decade = is_close_to_int(fx) 988 exponent = np.round(fx) if is_x_decade else np.floor(fx) 989 coeff = np.round(x / b ** exponent) 990 991 if self.labelOnlyBase and not is_x_decade: 992 return '' 993 if self._sublabels is not None and coeff not in self._sublabels: 994 return '' 995 996 vmin, vmax = self.axis.get_view_interval() 997 vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) 998 s = self._num_to_string(x, vmin, vmax) 999 return self.fix_minus(s) 1000 1001 def format_data(self, value): 1002 b = self.labelOnlyBase 1003 self.labelOnlyBase = False 1004 value = cbook.strip_math(self.__call__(value)) 1005 self.labelOnlyBase = b 1006 return value 1007 1008 def format_data_short(self, value): 1009 """ 1010 Return a short formatted string representation of a number. 1011 """ 1012 return '%-12g' % value 1013 1014 def pprint_val(self, x, d): 1015 #if the number is not too big and it's an int, format it as an 1016 #int 1017 if abs(x) < 1e4 and x == int(x): 1018 return '%d' % x 1019 1020 if d < 1e-2: 1021 fmt = '%1.3e' 1022 elif d < 1e-1: 1023 fmt = '%1.3f' 1024 elif d > 1e5: 1025 fmt = '%1.1e' 1026 elif d > 10: 1027 fmt = '%1.1f' 1028 elif d > 1: 1029 fmt = '%1.2f' 1030 else: 1031 fmt = '%1.3f' 1032 s = fmt % x 1033 1034 tup = s.split('e') 1035 if len(tup) == 2: 1036 mantissa = tup[0].rstrip('0').rstrip('.') 1037 exponent = int(tup[1]) 1038 if exponent: 1039 s = '%se%d' % (mantissa, exponent) 1040 else: 1041 s = mantissa 1042 else: 1043 s = s.rstrip('0').rstrip('.') 1044 return s 1045 1046 1047class LogFormatterExponent(LogFormatter): 1048 """ 1049 Format values for log axis using ``exponent = log_base(value)``. 1050 """ 1051 def _num_to_string(self, x, vmin, vmax): 1052 fx = math.log(x) / math.log(self._base) 1053 if abs(fx) > 10000: 1054 s = '%1.0g' % fx 1055 elif abs(fx) < 1: 1056 s = '%1.0g' % fx 1057 else: 1058 fd = math.log(vmax - vmin) / math.log(self._base) 1059 s = self.pprint_val(fx, fd) 1060 return s 1061 1062 1063class LogFormatterMathtext(LogFormatter): 1064 """ 1065 Format values for log axis using ``exponent = log_base(value)``. 1066 """ 1067 1068 def _non_decade_format(self, sign_string, base, fx, usetex): 1069 'Return string for non-decade locations' 1070 if usetex: 1071 return (r'$%s%s^{%.2f}$') % (sign_string, base, fx) 1072 else: 1073 return ('$%s$' % _mathdefault('%s%s^{%.2f}' % 1074 (sign_string, base, fx))) 1075 1076 def __call__(self, x, pos=None): 1077 """ 1078 Return the format for tick value *x*. 1079 1080 The position *pos* is ignored. 1081 """ 1082 usetex = rcParams['text.usetex'] 1083 min_exp = rcParams['axes.formatter.min_exponent'] 1084 1085 if x == 0: # Symlog 1086 if usetex: 1087 return '$0$' 1088 else: 1089 return '$%s$' % _mathdefault('0') 1090 1091 sign_string = '-' if x < 0 else '' 1092 x = abs(x) 1093 b = self._base 1094 1095 # only label the decades 1096 fx = math.log(x) / math.log(b) 1097 is_x_decade = is_close_to_int(fx) 1098 exponent = np.round(fx) if is_x_decade else np.floor(fx) 1099 coeff = np.round(x / b ** exponent) 1100 if is_x_decade: 1101 fx = nearest_long(fx) 1102 1103 if self.labelOnlyBase and not is_x_decade: 1104 return '' 1105 if self._sublabels is not None and coeff not in self._sublabels: 1106 return '' 1107 1108 # use string formatting of the base if it is not an integer 1109 if b % 1 == 0.0: 1110 base = '%d' % b 1111 else: 1112 base = '%s' % b 1113 1114 if np.abs(fx) < min_exp: 1115 if usetex: 1116 return r'${0}{1:g}$'.format(sign_string, x) 1117 else: 1118 return '${0}$'.format(_mathdefault( 1119 '{0}{1:g}'.format(sign_string, x))) 1120 elif not is_x_decade: 1121 return self._non_decade_format(sign_string, base, fx, usetex) 1122 else: 1123 if usetex: 1124 return (r'$%s%s^{%d}$') % (sign_string, 1125 base, 1126 nearest_long(fx)) 1127 else: 1128 return ('$%s$' % _mathdefault( 1129 '%s%s^{%d}' % 1130 (sign_string, base, nearest_long(fx)))) 1131 1132 1133class LogFormatterSciNotation(LogFormatterMathtext): 1134 """ 1135 Format values following scientific notation in a logarithmic axis. 1136 """ 1137 1138 def _non_decade_format(self, sign_string, base, fx, usetex): 1139 'Return string for non-decade locations' 1140 b = float(base) 1141 exponent = math.floor(fx) 1142 coeff = b ** fx / b ** exponent 1143 if is_close_to_int(coeff): 1144 coeff = nearest_long(coeff) 1145 if usetex: 1146 return (r'$%s%g\times%s^{%d}$') % \ 1147 (sign_string, coeff, base, exponent) 1148 else: 1149 return ('$%s$' % _mathdefault(r'%s%g\times%s^{%d}' % 1150 (sign_string, coeff, base, exponent))) 1151 1152 1153class LogitFormatter(Formatter): 1154 """ 1155 Probability formatter (using Math text). 1156 """ 1157 def __call__(self, x, pos=None): 1158 s = '' 1159 if 0.01 <= x <= 0.99: 1160 s = '{:.2f}'.format(x) 1161 elif x < 0.01: 1162 if is_decade(x): 1163 s = '$10^{{{:.0f}}}$'.format(np.log10(x)) 1164 else: 1165 s = '${:.5f}$'.format(x) 1166 else: # x > 0.99 1167 if is_decade(1-x): 1168 s = '$1-10^{{{:.0f}}}$'.format(np.log10(1-x)) 1169 else: 1170 s = '$1-{:.5f}$'.format(1-x) 1171 return s 1172 1173 def format_data_short(self, value): 1174 'return a short formatted string representation of a number' 1175 return '%-12g' % value 1176 1177 1178class EngFormatter(Formatter): 1179 """ 1180 Formats axis values using engineering prefixes to represent powers 1181 of 1000, plus a specified unit, e.g., 10 MHz instead of 1e7. 1182 """ 1183 1184 # The SI engineering prefixes 1185 ENG_PREFIXES = { 1186 -24: "y", 1187 -21: "z", 1188 -18: "a", 1189 -15: "f", 1190 -12: "p", 1191 -9: "n", 1192 -6: "\N{GREEK SMALL LETTER MU}", 1193 -3: "m", 1194 0: "", 1195 3: "k", 1196 6: "M", 1197 9: "G", 1198 12: "T", 1199 15: "P", 1200 18: "E", 1201 21: "Z", 1202 24: "Y" 1203 } 1204 1205 def __init__(self, unit="", places=None, sep=" "): 1206 """ 1207 Parameters 1208 ---------- 1209 unit : str (default: "") 1210 Unit symbol to use, suitable for use with single-letter 1211 representations of powers of 1000. For example, 'Hz' or 'm'. 1212 1213 places : int (default: None) 1214 Precision with which to display the number, specified in 1215 digits after the decimal point (there will be between one 1216 and three digits before the decimal point). If it is None, 1217 the formatting falls back to the floating point format '%g', 1218 which displays up to 6 *significant* digits, i.e. the equivalent 1219 value for *places* varies between 0 and 5 (inclusive). 1220 1221 sep : str (default: " ") 1222 Separator used between the value and the prefix/unit. For 1223 example, one get '3.14 mV' if ``sep`` is " " (default) and 1224 '3.14mV' if ``sep`` is "". Besides the default behavior, some 1225 other useful options may be: 1226 1227 * ``sep=""`` to append directly the prefix/unit to the value; 1228 * ``sep="\\N{THIN SPACE}"`` (``U+2009``); 1229 * ``sep="\\N{NARROW NO-BREAK SPACE}"`` (``U+202F``); 1230 * ``sep="\\N{NO-BREAK SPACE}"`` (``U+00A0``). 1231 """ 1232 self.unit = unit 1233 self.places = places 1234 self.sep = sep 1235 1236 def __call__(self, x, pos=None): 1237 s = "%s%s" % (self.format_eng(x), self.unit) 1238 # Remove the trailing separator when there is neither prefix nor unit 1239 if len(self.sep) > 0 and s.endswith(self.sep): 1240 s = s[:-len(self.sep)] 1241 return self.fix_minus(s) 1242 1243 def format_eng(self, num): 1244 """ 1245 Formats a number in engineering notation, appending a letter 1246 representing the power of 1000 of the original number. 1247 Some examples: 1248 1249 >>> format_eng(0) # for self.places = 0 1250 '0' 1251 1252 >>> format_eng(1000000) # for self.places = 1 1253 '1.0 M' 1254 1255 >>> format_eng("-1e-6") # for self.places = 2 1256 u'-1.00 \N{GREEK SMALL LETTER MU}' 1257 1258 `num` may be a numeric value or a string that can be converted 1259 to a numeric value with ``float(num)``. 1260 """ 1261 if isinstance(num, six.string_types): 1262 warnings.warn( 1263 "Passing a string as *num* argument is deprecated since" 1264 "Matplotlib 2.1, and is expected to be removed in 2.3.", 1265 mplDeprecation) 1266 1267 dnum = float(num) 1268 sign = 1 1269 fmt = "g" if self.places is None else ".{:d}f".format(self.places) 1270 1271 if dnum < 0: 1272 sign = -1 1273 dnum = -dnum 1274 1275 if dnum != 0: 1276 pow10 = int(math.floor(math.log10(dnum) / 3) * 3) 1277 else: 1278 pow10 = 0 1279 # Force dnum to zero, to avoid inconsistencies like 1280 # format_eng(-0) = "0" and format_eng(0.0) = "0" 1281 # but format_eng(-0.0) = "-0.0" 1282 dnum = 0.0 1283 1284 pow10 = np.clip(pow10, min(self.ENG_PREFIXES), max(self.ENG_PREFIXES)) 1285 1286 mant = sign * dnum / (10.0 ** pow10) 1287 # Taking care of the cases like 999.9..., which 1288 # may be rounded to 1000 instead of 1 k. Beware 1289 # of the corner case of values that are beyond 1290 # the range of SI prefixes (i.e. > 'Y'). 1291 _fmant = float("{mant:{fmt}}".format(mant=mant, fmt=fmt)) 1292 if _fmant >= 1000 and pow10 != max(self.ENG_PREFIXES): 1293 mant /= 1000 1294 pow10 += 3 1295 1296 prefix = self.ENG_PREFIXES[int(pow10)] 1297 1298 formatted = "{mant:{fmt}}{sep}{prefix}".format( 1299 mant=mant, sep=self.sep, prefix=prefix, fmt=fmt) 1300 1301 return formatted 1302 1303 1304class PercentFormatter(Formatter): 1305 """ 1306 Format numbers as a percentage. 1307 1308 Parameters 1309 ---------- 1310 xmax : float 1311 Determines how the number is converted into a percentage. 1312 *xmax* is the data value that corresponds to 100%. 1313 Percentages are computed as ``x / xmax * 100``. So if the data is 1314 already scaled to be percentages, *xmax* will be 100. Another common 1315 situation is where `xmax` is 1.0. 1316 1317 decimals : None or int 1318 The number of decimal places to place after the point. 1319 If *None* (the default), the number will be computed automatically. 1320 1321 symbol : string or None 1322 A string that will be appended to the label. It may be 1323 *None* or empty to indicate that no symbol should be used. LaTeX 1324 special characters are escaped in *symbol* whenever latex mode is 1325 enabled, unless *is_latex* is *True*. 1326 1327 is_latex : bool 1328 If *False*, reserved LaTeX characters in *symbol* will be escaped. 1329 """ 1330 def __init__(self, xmax=100, decimals=None, symbol='%', is_latex=False): 1331 self.xmax = xmax + 0.0 1332 self.decimals = decimals 1333 self._symbol = symbol 1334 self._is_latex = is_latex 1335 1336 def __call__(self, x, pos=None): 1337 """ 1338 Formats the tick as a percentage with the appropriate scaling. 1339 """ 1340 ax_min, ax_max = self.axis.get_view_interval() 1341 display_range = abs(ax_max - ax_min) 1342 1343 return self.fix_minus(self.format_pct(x, display_range)) 1344 1345 def format_pct(self, x, display_range): 1346 """ 1347 Formats the number as a percentage number with the correct 1348 number of decimals and adds the percent symbol, if any. 1349 1350 If `self.decimals` is `None`, the number of digits after the 1351 decimal point is set based on the `display_range` of the axis 1352 as follows: 1353 1354 +---------------+----------+------------------------+ 1355 | display_range | decimals | sample | 1356 +---------------+----------+------------------------+ 1357 | >50 | 0 | ``x = 34.5`` => 35% | 1358 +---------------+----------+------------------------+ 1359 | >5 | 1 | ``x = 34.5`` => 34.5% | 1360 +---------------+----------+------------------------+ 1361 | >0.5 | 2 | ``x = 34.5`` => 34.50% | 1362 +---------------+----------+------------------------+ 1363 | ... | ... | ... | 1364 +---------------+----------+------------------------+ 1365 1366 This method will not be very good for tiny axis ranges or 1367 extremely large ones. It assumes that the values on the chart 1368 are percentages displayed on a reasonable scale. 1369 """ 1370 x = self.convert_to_pct(x) 1371 if self.decimals is None: 1372 # conversion works because display_range is a difference 1373 scaled_range = self.convert_to_pct(display_range) 1374 if scaled_range <= 0: 1375 decimals = 0 1376 else: 1377 # Luckily Python's built-in ceil rounds to +inf, not away from 1378 # zero. This is very important since the equation for decimals 1379 # starts out as `scaled_range > 0.5 * 10**(2 - decimals)` 1380 # and ends up with `decimals > 2 - log10(2 * scaled_range)`. 1381 decimals = math.ceil(2.0 - math.log10(2.0 * scaled_range)) 1382 if decimals > 5: 1383 decimals = 5 1384 elif decimals < 0: 1385 decimals = 0 1386 else: 1387 decimals = self.decimals 1388 s = '{x:0.{decimals}f}'.format(x=x, decimals=int(decimals)) 1389 1390 return s + self.symbol 1391 1392 def convert_to_pct(self, x): 1393 return 100.0 * (x / self.xmax) 1394 1395 @property 1396 def symbol(self): 1397 """ 1398 The configured percent symbol as a string. 1399 1400 If LaTeX is enabled via :rc:`text.usetex`, the special characters 1401 ``{'#', '$', '%', '&', '~', '_', '^', '\\', '{', '}'}`` are 1402 automatically escaped in the string. 1403 """ 1404 symbol = self._symbol 1405 if not symbol: 1406 symbol = '' 1407 elif rcParams['text.usetex'] and not self._is_latex: 1408 # Source: http://www.personal.ceu.hu/tex/specchar.htm 1409 # Backslash must be first for this to work correctly since 1410 # it keeps getting added in 1411 for spec in r'\#$%&~_^{}': 1412 symbol = symbol.replace(spec, '\\' + spec) 1413 return symbol 1414 1415 @symbol.setter 1416 def symbol(self, symbol): 1417 self._symbol = symbol 1418 1419 1420class Locator(TickHelper): 1421 """ 1422 Determine the tick locations; 1423 1424 Note, you should not use the same locator between different 1425 :class:`~matplotlib.axis.Axis` because the locator stores references to 1426 the Axis data and view limits 1427 """ 1428 1429 # Some automatic tick locators can generate so many ticks they 1430 # kill the machine when you try and render them. 1431 # This parameter is set to cause locators to raise an error if too 1432 # many ticks are generated. 1433 MAXTICKS = 1000 1434 1435 def tick_values(self, vmin, vmax): 1436 """ 1437 Return the values of the located ticks given **vmin** and **vmax**. 1438 1439 .. note:: 1440 To get tick locations with the vmin and vmax values defined 1441 automatically for the associated :attr:`axis` simply call 1442 the Locator instance:: 1443 1444 >>> print((type(loc))) 1445 <type 'Locator'> 1446 >>> print((loc())) 1447 [1, 2, 3, 4] 1448 1449 """ 1450 raise NotImplementedError('Derived must override') 1451 1452 def set_params(self, **kwargs): 1453 """ 1454 Do nothing, and rase a warning. Any locator class not supporting the 1455 set_params() function will call this. 1456 """ 1457 warnings.warn("'set_params()' not defined for locator of type " + 1458 str(type(self))) 1459 1460 def __call__(self): 1461 """Return the locations of the ticks""" 1462 # note: some locators return data limits, other return view limits, 1463 # hence there is no *one* interface to call self.tick_values. 1464 raise NotImplementedError('Derived must override') 1465 1466 def raise_if_exceeds(self, locs): 1467 """raise a RuntimeError if Locator attempts to create more than 1468 MAXTICKS locs""" 1469 if len(locs) >= self.MAXTICKS: 1470 raise RuntimeError("Locator attempting to generate {} ticks from " 1471 "{} to {}: exceeds Locator.MAXTICKS".format( 1472 len(locs), locs[0], locs[-1])) 1473 return locs 1474 1475 def view_limits(self, vmin, vmax): 1476 """ 1477 select a scale for the range from vmin to vmax 1478 1479 Normally this method is overridden by subclasses to 1480 change locator behaviour. 1481 """ 1482 return mtransforms.nonsingular(vmin, vmax) 1483 1484 def autoscale(self): 1485 """autoscale the view limits""" 1486 return self.view_limits(*self.axis.get_view_interval()) 1487 1488 def pan(self, numsteps): 1489 """Pan numticks (can be positive or negative)""" 1490 ticks = self() 1491 numticks = len(ticks) 1492 1493 vmin, vmax = self.axis.get_view_interval() 1494 vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) 1495 if numticks > 2: 1496 step = numsteps * abs(ticks[0] - ticks[1]) 1497 else: 1498 d = abs(vmax - vmin) 1499 step = numsteps * d / 6. 1500 1501 vmin += step 1502 vmax += step 1503 self.axis.set_view_interval(vmin, vmax, ignore=True) 1504 1505 def zoom(self, direction): 1506 "Zoom in/out on axis; if direction is >0 zoom in, else zoom out" 1507 1508 vmin, vmax = self.axis.get_view_interval() 1509 vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) 1510 interval = abs(vmax - vmin) 1511 step = 0.1 * interval * direction 1512 self.axis.set_view_interval(vmin + step, vmax - step, ignore=True) 1513 1514 def refresh(self): 1515 """refresh internal information based on current lim""" 1516 pass 1517 1518 1519class IndexLocator(Locator): 1520 """ 1521 Place a tick on every multiple of some base number of points 1522 plotted, e.g., on every 5th point. It is assumed that you are doing 1523 index plotting; i.e., the axis is 0, len(data). This is mainly 1524 useful for x ticks. 1525 """ 1526 def __init__(self, base, offset): 1527 'place ticks on the i-th data points where (i-offset)%base==0' 1528 self._base = base 1529 self.offset = offset 1530 1531 def set_params(self, base=None, offset=None): 1532 """Set parameters within this locator""" 1533 if base is not None: 1534 self._base = base 1535 if offset is not None: 1536 self.offset = offset 1537 1538 def __call__(self): 1539 """Return the locations of the ticks""" 1540 dmin, dmax = self.axis.get_data_interval() 1541 return self.tick_values(dmin, dmax) 1542 1543 def tick_values(self, vmin, vmax): 1544 return self.raise_if_exceeds( 1545 np.arange(vmin + self.offset, vmax + 1, self._base)) 1546 1547 1548class FixedLocator(Locator): 1549 """ 1550 Tick locations are fixed. If nbins is not None, 1551 the array of possible positions will be subsampled to 1552 keep the number of ticks <= nbins +1. 1553 The subsampling will be done so as to include the smallest 1554 absolute value; for example, if zero is included in the 1555 array of possibilities, then it is guaranteed to be one of 1556 the chosen ticks. 1557 """ 1558 1559 def __init__(self, locs, nbins=None): 1560 self.locs = np.asarray(locs) 1561 self.nbins = nbins 1562 if self.nbins is not None: 1563 self.nbins = max(self.nbins, 2) 1564 1565 def set_params(self, nbins=None): 1566 """Set parameters within this locator.""" 1567 if nbins is not None: 1568 self.nbins = nbins 1569 1570 def __call__(self): 1571 return self.tick_values(None, None) 1572 1573 def tick_values(self, vmin, vmax): 1574 """" 1575 Return the locations of the ticks. 1576 1577 .. note:: 1578 1579 Because the values are fixed, vmin and vmax are not used in this 1580 method. 1581 1582 """ 1583 if self.nbins is None: 1584 return self.locs 1585 step = max(int(np.ceil(len(self.locs) / self.nbins)), 1) 1586 ticks = self.locs[::step] 1587 for i in range(1, step): 1588 ticks1 = self.locs[i::step] 1589 if np.abs(ticks1).min() < np.abs(ticks).min(): 1590 ticks = ticks1 1591 return self.raise_if_exceeds(ticks) 1592 1593 1594class NullLocator(Locator): 1595 """ 1596 No ticks 1597 """ 1598 1599 def __call__(self): 1600 return self.tick_values(None, None) 1601 1602 def tick_values(self, vmin, vmax): 1603 """" 1604 Return the locations of the ticks. 1605 1606 .. note:: 1607 1608 Because the values are Null, vmin and vmax are not used in this 1609 method. 1610 """ 1611 return [] 1612 1613 1614class LinearLocator(Locator): 1615 """ 1616 Determine the tick locations 1617 1618 The first time this function is called it will try to set the 1619 number of ticks to make a nice tick partitioning. Thereafter the 1620 number of ticks will be fixed so that interactive navigation will 1621 be nice 1622 1623 """ 1624 def __init__(self, numticks=None, presets=None): 1625 """ 1626 Use presets to set locs based on lom. A dict mapping vmin, vmax->locs 1627 """ 1628 self.numticks = numticks 1629 if presets is None: 1630 self.presets = {} 1631 else: 1632 self.presets = presets 1633 1634 def set_params(self, numticks=None, presets=None): 1635 """Set parameters within this locator.""" 1636 if presets is not None: 1637 self.presets = presets 1638 if numticks is not None: 1639 self.numticks = numticks 1640 1641 def __call__(self): 1642 'Return the locations of the ticks' 1643 vmin, vmax = self.axis.get_view_interval() 1644 return self.tick_values(vmin, vmax) 1645 1646 def tick_values(self, vmin, vmax): 1647 vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) 1648 if vmax < vmin: 1649 vmin, vmax = vmax, vmin 1650 1651 if (vmin, vmax) in self.presets: 1652 return self.presets[(vmin, vmax)] 1653 1654 if self.numticks is None: 1655 self._set_numticks() 1656 1657 if self.numticks == 0: 1658 return [] 1659 ticklocs = np.linspace(vmin, vmax, self.numticks) 1660 1661 return self.raise_if_exceeds(ticklocs) 1662 1663 def _set_numticks(self): 1664 self.numticks = 11 # todo; be smart here; this is just for dev 1665 1666 def view_limits(self, vmin, vmax): 1667 'Try to choose the view limits intelligently' 1668 1669 if vmax < vmin: 1670 vmin, vmax = vmax, vmin 1671 1672 if vmin == vmax: 1673 vmin -= 1 1674 vmax += 1 1675 1676 if rcParams['axes.autolimit_mode'] == 'round_numbers': 1677 exponent, remainder = _divmod( 1678 math.log10(vmax - vmin), math.log10(max(self.numticks - 1, 1))) 1679 exponent -= (remainder < .5) 1680 scale = max(self.numticks - 1, 1) ** (-exponent) 1681 vmin = math.floor(scale * vmin) / scale 1682 vmax = math.ceil(scale * vmax) / scale 1683 1684 return mtransforms.nonsingular(vmin, vmax) 1685 1686 1687def closeto(x, y): 1688 if abs(x - y) < 1e-10: 1689 return True 1690 else: 1691 return False 1692 1693 1694class Base(object): 1695 'this solution has some hacks to deal with floating point inaccuracies' 1696 def __init__(self, base): 1697 if base <= 0: 1698 raise ValueError("'base' must be positive") 1699 self._base = base 1700 1701 def lt(self, x): 1702 'return the largest multiple of base < x' 1703 d, m = _divmod(x, self._base) 1704 if closeto(m, 0) and not closeto(m / self._base, 1): 1705 return (d - 1) * self._base 1706 return d * self._base 1707 1708 def le(self, x): 1709 'return the largest multiple of base <= x' 1710 d, m = _divmod(x, self._base) 1711 if closeto(m / self._base, 1): # was closeto(m, self._base) 1712 #looks like floating point error 1713 return (d + 1) * self._base 1714 return d * self._base 1715 1716 def gt(self, x): 1717 'return the smallest multiple of base > x' 1718 d, m = _divmod(x, self._base) 1719 if closeto(m / self._base, 1): 1720 #looks like floating point error 1721 return (d + 2) * self._base 1722 return (d + 1) * self._base 1723 1724 def ge(self, x): 1725 'return the smallest multiple of base >= x' 1726 d, m = _divmod(x, self._base) 1727 if closeto(m, 0) and not closeto(m / self._base, 1): 1728 return d * self._base 1729 return (d + 1) * self._base 1730 1731 def get_base(self): 1732 return self._base 1733 1734 1735class MultipleLocator(Locator): 1736 """ 1737 Set a tick on every integer that is multiple of base in the 1738 view interval 1739 """ 1740 1741 def __init__(self, base=1.0): 1742 self._base = Base(base) 1743 1744 def set_params(self, base): 1745 """Set parameters within this locator.""" 1746 if base is not None: 1747 self._base = base 1748 1749 def __call__(self): 1750 'Return the locations of the ticks' 1751 vmin, vmax = self.axis.get_view_interval() 1752 return self.tick_values(vmin, vmax) 1753 1754 def tick_values(self, vmin, vmax): 1755 if vmax < vmin: 1756 vmin, vmax = vmax, vmin 1757 vmin = self._base.ge(vmin) 1758 base = self._base.get_base() 1759 n = (vmax - vmin + 0.001 * base) // base 1760 locs = vmin - base + np.arange(n + 3) * base 1761 return self.raise_if_exceeds(locs) 1762 1763 def view_limits(self, dmin, dmax): 1764 """ 1765 Set the view limits to the nearest multiples of base that 1766 contain the data 1767 """ 1768 if rcParams['axes.autolimit_mode'] == 'round_numbers': 1769 vmin = self._base.le(dmin) 1770 vmax = self._base.ge(dmax) 1771 if vmin == vmax: 1772 vmin -= 1 1773 vmax += 1 1774 else: 1775 vmin = dmin 1776 vmax = dmax 1777 1778 return mtransforms.nonsingular(vmin, vmax) 1779 1780 1781def scale_range(vmin, vmax, n=1, threshold=100): 1782 dv = abs(vmax - vmin) # > 0 as nonsingular is called before. 1783 meanv = (vmax + vmin) / 2 1784 if abs(meanv) / dv < threshold: 1785 offset = 0 1786 else: 1787 offset = math.copysign(10 ** (math.log10(abs(meanv)) // 1), meanv) 1788 scale = 10 ** (math.log10(dv / n) // 1) 1789 return scale, offset 1790 1791 1792class MaxNLocator(Locator): 1793 """ 1794 Select no more than N intervals at nice locations. 1795 """ 1796 default_params = dict(nbins=10, 1797 steps=None, 1798 integer=False, 1799 symmetric=False, 1800 prune=None, 1801 min_n_ticks=2) 1802 1803 def __init__(self, *args, **kwargs): 1804 """ 1805 Keyword args: 1806 1807 *nbins* 1808 Maximum number of intervals; one less than max number of 1809 ticks. If the string `'auto'`, the number of bins will be 1810 automatically determined based on the length of the axis. 1811 1812 *steps* 1813 Sequence of nice numbers starting with 1 and ending with 10; 1814 e.g., [1, 2, 4, 5, 10], where the values are acceptable 1815 tick multiples. i.e. for the example, 20, 40, 60 would be 1816 an acceptable set of ticks, as would 0.4, 0.6, 0.8, because 1817 they are multiples of 2. However, 30, 60, 90 would not 1818 be allowed because 3 does not appear in the list of steps. 1819 1820 *integer* 1821 If True, ticks will take only integer values, provided 1822 at least `min_n_ticks` integers are found within the 1823 view limits. 1824 1825 *symmetric* 1826 If True, autoscaling will result in a range symmetric 1827 about zero. 1828 1829 *prune* 1830 ['lower' | 'upper' | 'both' | None] 1831 Remove edge ticks -- useful for stacked or ganged plots where 1832 the upper tick of one axes overlaps with the lower tick of the 1833 axes above it, primarily when :rc:`axes.autolimit_mode` is 1834 ``'round_numbers'``. If ``prune=='lower'``, the smallest tick will 1835 be removed. If ``prune == 'upper'``, the largest tick will be 1836 removed. If ``prune == 'both'``, the largest and smallest ticks 1837 will be removed. If ``prune == None``, no ticks will be removed. 1838 1839 *min_n_ticks* 1840 Relax `nbins` and `integer` constraints if necessary to 1841 obtain this minimum number of ticks. 1842 1843 """ 1844 if args: 1845 kwargs['nbins'] = args[0] 1846 if len(args) > 1: 1847 raise ValueError( 1848 "Keywords are required for all arguments except 'nbins'") 1849 self.set_params(**self.default_params) 1850 self.set_params(**kwargs) 1851 1852 @staticmethod 1853 def _validate_steps(steps): 1854 if not np.iterable(steps): 1855 raise ValueError('steps argument must be a sequence of numbers ' 1856 'from 1 to 10') 1857 steps = np.asarray(steps) 1858 if np.any(np.diff(steps) <= 0): 1859 raise ValueError('steps argument must be uniformly increasing') 1860 if steps[-1] > 10 or steps[0] < 1: 1861 warnings.warn('Steps argument should be a sequence of numbers\n' 1862 'increasing from 1 to 10, inclusive. Behavior with\n' 1863 'values outside this range is undefined, and will\n' 1864 'raise a ValueError in future versions of mpl.') 1865 if steps[0] != 1: 1866 steps = np.hstack((1, steps)) 1867 if steps[-1] != 10: 1868 steps = np.hstack((steps, 10)) 1869 return steps 1870 1871 @staticmethod 1872 def _staircase(steps): 1873 # Make an extended staircase within which the needed 1874 # step will be found. This is probably much larger 1875 # than necessary. 1876 flights = (0.1 * steps[:-1], steps, 10 * steps[1]) 1877 return np.hstack(flights) 1878 1879 def set_params(self, **kwargs): 1880 """Set parameters within this locator.""" 1881 if 'nbins' in kwargs: 1882 self._nbins = kwargs['nbins'] 1883 if self._nbins != 'auto': 1884 self._nbins = int(self._nbins) 1885 if 'symmetric' in kwargs: 1886 self._symmetric = kwargs['symmetric'] 1887 if 'prune' in kwargs: 1888 prune = kwargs['prune'] 1889 if prune is not None and prune not in ['upper', 'lower', 'both']: 1890 raise ValueError( 1891 "prune must be 'upper', 'lower', 'both', or None") 1892 self._prune = prune 1893 if 'min_n_ticks' in kwargs: 1894 self._min_n_ticks = max(1, kwargs['min_n_ticks']) 1895 if 'steps' in kwargs: 1896 steps = kwargs['steps'] 1897 if steps is None: 1898 self._steps = np.array([1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10]) 1899 else: 1900 self._steps = self._validate_steps(steps) 1901 self._extended_steps = self._staircase(self._steps) 1902 if 'integer' in kwargs: 1903 self._integer = kwargs['integer'] 1904 1905 def _raw_ticks(self, vmin, vmax): 1906 if self._nbins == 'auto': 1907 if self.axis is not None: 1908 nbins = np.clip(self.axis.get_tick_space(), 1909 max(1, self._min_n_ticks - 1), 9) 1910 else: 1911 nbins = 9 1912 else: 1913 nbins = self._nbins 1914 1915 scale, offset = scale_range(vmin, vmax, nbins) 1916 _vmin = vmin - offset 1917 _vmax = vmax - offset 1918 raw_step = (vmax - vmin) / nbins 1919 steps = self._extended_steps * scale 1920 if self._integer: 1921 # For steps > 1, keep only integer values. 1922 igood = (steps < 1) | (np.abs(steps - np.round(steps)) < 0.001) 1923 steps = steps[igood] 1924 1925 istep = np.nonzero(steps >= raw_step)[0][0] 1926 1927 # Classic round_numbers mode may require a larger step. 1928 if rcParams['axes.autolimit_mode'] == 'round_numbers': 1929 for istep in range(istep, len(steps)): 1930 step = steps[istep] 1931 best_vmin = (_vmin // step) * step 1932 best_vmax = best_vmin + step * nbins 1933 if (best_vmax >= _vmax): 1934 break 1935 1936 # This is an upper limit; move to smaller steps if necessary. 1937 for i in range(istep): 1938 step = steps[istep - i] 1939 if (self._integer and 1940 np.floor(_vmax) - np.ceil(_vmin) >= self._min_n_ticks - 1): 1941 step = max(1, step) 1942 best_vmin = (_vmin // step) * step 1943 1944 low = np.round(Base(step).le(_vmin - best_vmin) / step) 1945 high = np.round(Base(step).ge(_vmax - best_vmin) / step) 1946 ticks = np.arange(low, high + 1) * step + best_vmin + offset 1947 nticks = ((ticks <= vmax) & (ticks >= vmin)).sum() 1948 if nticks >= self._min_n_ticks: 1949 break 1950 return ticks 1951 1952 def __call__(self): 1953 vmin, vmax = self.axis.get_view_interval() 1954 return self.tick_values(vmin, vmax) 1955 1956 def tick_values(self, vmin, vmax): 1957 if self._symmetric: 1958 vmax = max(abs(vmin), abs(vmax)) 1959 vmin = -vmax 1960 vmin, vmax = mtransforms.nonsingular( 1961 vmin, vmax, expander=1e-13, tiny=1e-14) 1962 locs = self._raw_ticks(vmin, vmax) 1963 1964 prune = self._prune 1965 if prune == 'lower': 1966 locs = locs[1:] 1967 elif prune == 'upper': 1968 locs = locs[:-1] 1969 elif prune == 'both': 1970 locs = locs[1:-1] 1971 return self.raise_if_exceeds(locs) 1972 1973 def view_limits(self, dmin, dmax): 1974 if self._symmetric: 1975 dmax = max(abs(dmin), abs(dmax)) 1976 dmin = -dmax 1977 1978 dmin, dmax = mtransforms.nonsingular( 1979 dmin, dmax, expander=1e-12, tiny=1e-13) 1980 1981 if rcParams['axes.autolimit_mode'] == 'round_numbers': 1982 return self._raw_ticks(dmin, dmax)[[0, -1]] 1983 else: 1984 return dmin, dmax 1985 1986 1987def decade_down(x, base=10): 1988 'floor x to the nearest lower decade' 1989 if x == 0.0: 1990 return -base 1991 lx = np.floor(np.log(x) / np.log(base)) 1992 return base ** lx 1993 1994 1995def decade_up(x, base=10): 1996 'ceil x to the nearest higher decade' 1997 if x == 0.0: 1998 return base 1999 lx = np.ceil(np.log(x) / np.log(base)) 2000 return base ** lx 2001 2002 2003def nearest_long(x): 2004 if x == 0: 2005 return long(0) 2006 elif x > 0: 2007 return long(x + 0.5) 2008 else: 2009 return long(x - 0.5) 2010 2011 2012def is_decade(x, base=10): 2013 if not np.isfinite(x): 2014 return False 2015 if x == 0.0: 2016 return True 2017 lx = np.log(np.abs(x)) / np.log(base) 2018 return is_close_to_int(lx) 2019 2020 2021def is_close_to_int(x): 2022 if not np.isfinite(x): 2023 return False 2024 return abs(x - nearest_long(x)) < 1e-10 2025 2026 2027class LogLocator(Locator): 2028 """ 2029 Determine the tick locations for log axes 2030 """ 2031 2032 def __init__(self, base=10.0, subs=(1.0,), numdecs=4, numticks=None): 2033 """ 2034 Place ticks on the locations : subs[j] * base**i 2035 2036 Parameters 2037 ---------- 2038 subs : None, string, or sequence of float, optional, default (1.0,) 2039 Gives the multiples of integer powers of the base at which 2040 to place ticks. The default places ticks only at 2041 integer powers of the base. 2042 The permitted string values are ``'auto'`` and ``'all'``, 2043 both of which use an algorithm based on the axis view 2044 limits to determine whether and how to put ticks between 2045 integer powers of the base. With ``'auto'``, ticks are 2046 placed only between integer powers; with ``'all'``, the 2047 integer powers are included. A value of None is 2048 equivalent to ``'auto'``. 2049 2050 """ 2051 if numticks is None: 2052 if rcParams['_internal.classic_mode']: 2053 numticks = 15 2054 else: 2055 numticks = 'auto' 2056 self.base(base) 2057 self.subs(subs) 2058 self.numdecs = numdecs 2059 self.numticks = numticks 2060 2061 def set_params(self, base=None, subs=None, numdecs=None, numticks=None): 2062 """Set parameters within this locator.""" 2063 if base is not None: 2064 self.base(base) 2065 if subs is not None: 2066 self.subs(subs) 2067 if numdecs is not None: 2068 self.numdecs = numdecs 2069 if numticks is not None: 2070 self.numticks = numticks 2071 2072 # FIXME: these base and subs functions are contrary to our 2073 # usual and desired API. 2074 2075 def base(self, base): 2076 """ 2077 set the base of the log scaling (major tick every base**i, i integer) 2078 """ 2079 self._base = float(base) 2080 2081 def subs(self, subs): 2082 """ 2083 set the minor ticks for the log scaling every base**i*subs[j] 2084 """ 2085 if subs is None: # consistency with previous bad API 2086 self._subs = 'auto' 2087 elif isinstance(subs, six.string_types): 2088 if subs not in ('all', 'auto'): 2089 raise ValueError("A subs string must be 'all' or 'auto'; " 2090 "found '%s'." % subs) 2091 self._subs = subs 2092 else: 2093 self._subs = np.asarray(subs, dtype=float) 2094 2095 def __call__(self): 2096 'Return the locations of the ticks' 2097 vmin, vmax = self.axis.get_view_interval() 2098 return self.tick_values(vmin, vmax) 2099 2100 def tick_values(self, vmin, vmax): 2101 if self.numticks == 'auto': 2102 if self.axis is not None: 2103 numticks = np.clip(self.axis.get_tick_space(), 2, 9) 2104 else: 2105 numticks = 9 2106 else: 2107 numticks = self.numticks 2108 2109 b = self._base 2110 # dummy axis has no axes attribute 2111 if hasattr(self.axis, 'axes') and self.axis.axes.name == 'polar': 2112 vmax = math.ceil(math.log(vmax) / math.log(b)) 2113 decades = np.arange(vmax - self.numdecs, vmax) 2114 ticklocs = b ** decades 2115 2116 return ticklocs 2117 2118 if vmin <= 0.0: 2119 if self.axis is not None: 2120 vmin = self.axis.get_minpos() 2121 2122 if vmin <= 0.0 or not np.isfinite(vmin): 2123 raise ValueError( 2124 "Data has no positive values, and therefore can not be " 2125 "log-scaled.") 2126 2127 vmin = math.log(vmin) / math.log(b) 2128 vmax = math.log(vmax) / math.log(b) 2129 2130 if vmax < vmin: 2131 vmin, vmax = vmax, vmin 2132 2133 numdec = math.floor(vmax) - math.ceil(vmin) 2134 2135 if isinstance(self._subs, six.string_types): 2136 _first = 2.0 if self._subs == 'auto' else 1.0 2137 if numdec > 10 or b < 3: 2138 if self._subs == 'auto': 2139 return np.array([]) # no minor or major ticks 2140 else: 2141 subs = np.array([1.0]) # major ticks 2142 else: 2143 subs = np.arange(_first, b) 2144 else: 2145 subs = self._subs 2146 2147 stride = 1 2148 2149 if rcParams['_internal.classic_mode']: 2150 # Leave the bug left over from the PY2-PY3 transition. 2151 while numdec / stride + 1 > numticks: 2152 stride += 1 2153 else: 2154 while numdec // stride + 1 > numticks: 2155 stride += 1 2156 2157 # Does subs include anything other than 1? 2158 have_subs = len(subs) > 1 or (len(subs) == 1 and subs[0] != 1.0) 2159 2160 decades = np.arange(math.floor(vmin) - stride, 2161 math.ceil(vmax) + 2 * stride, stride) 2162 2163 if hasattr(self, '_transform'): 2164 ticklocs = self._transform.inverted().transform(decades) 2165 if have_subs: 2166 if stride == 1: 2167 ticklocs = np.ravel(np.outer(subs, ticklocs)) 2168 else: 2169 ticklocs = [] 2170 else: 2171 if have_subs: 2172 ticklocs = [] 2173 if stride == 1: 2174 for decadeStart in b ** decades: 2175 ticklocs.extend(subs * decadeStart) 2176 else: 2177 ticklocs = b ** decades 2178 2179 return self.raise_if_exceeds(np.asarray(ticklocs)) 2180 2181 def view_limits(self, vmin, vmax): 2182 'Try to choose the view limits intelligently' 2183 b = self._base 2184 2185 vmin, vmax = self.nonsingular(vmin, vmax) 2186 2187 if self.axis.axes.name == 'polar': 2188 vmax = math.ceil(math.log(vmax) / math.log(b)) 2189 vmin = b ** (vmax - self.numdecs) 2190 2191 if rcParams['axes.autolimit_mode'] == 'round_numbers': 2192 if not is_decade(vmin, self._base): 2193 vmin = decade_down(vmin, self._base) 2194 if not is_decade(vmax, self._base): 2195 vmax = decade_up(vmax, self._base) 2196 2197 return vmin, vmax 2198 2199 def nonsingular(self, vmin, vmax): 2200 if not np.isfinite(vmin) or not np.isfinite(vmax): 2201 return 1, 10 # initial range, no data plotted yet 2202 2203 if vmin > vmax: 2204 vmin, vmax = vmax, vmin 2205 if vmax <= 0: 2206 warnings.warn( 2207 "Data has no positive values, and therefore cannot be " 2208 "log-scaled.") 2209 return 1, 10 2210 2211 minpos = self.axis.get_minpos() 2212 if not np.isfinite(minpos): 2213 minpos = 1e-300 # This should never take effect. 2214 if vmin <= 0: 2215 vmin = minpos 2216 if vmin == vmax: 2217 vmin = decade_down(vmin, self._base) 2218 vmax = decade_up(vmax, self._base) 2219 return vmin, vmax 2220 2221 2222class SymmetricalLogLocator(Locator): 2223 """ 2224 Determine the tick locations for symmetric log axes 2225 """ 2226 2227 def __init__(self, transform=None, subs=None, linthresh=None, base=None): 2228 """ 2229 place ticks on the location= base**i*subs[j] 2230 """ 2231 if transform is not None: 2232 self._base = transform.base 2233 self._linthresh = transform.linthresh 2234 elif linthresh is not None and base is not None: 2235 self._base = base 2236 self._linthresh = linthresh 2237 else: 2238 raise ValueError("Either transform, or both linthresh " 2239 "and base, must be provided.") 2240 if subs is None: 2241 self._subs = [1.0] 2242 else: 2243 self._subs = subs 2244 self.numticks = 15 2245 2246 def set_params(self, subs=None, numticks=None): 2247 """Set parameters within this locator.""" 2248 if numticks is not None: 2249 self.numticks = numticks 2250 if subs is not None: 2251 self._subs = subs 2252 2253 def __call__(self): 2254 'Return the locations of the ticks' 2255 # Note, these are untransformed coordinates 2256 vmin, vmax = self.axis.get_view_interval() 2257 return self.tick_values(vmin, vmax) 2258 2259 def tick_values(self, vmin, vmax): 2260 b = self._base 2261 t = self._linthresh 2262 2263 if vmax < vmin: 2264 vmin, vmax = vmax, vmin 2265 2266 # The domain is divided into three sections, only some of 2267 # which may actually be present. 2268 # 2269 # <======== -t ==0== t ========> 2270 # aaaaaaaaa bbbbb ccccccccc 2271 # 2272 # a) and c) will have ticks at integral log positions. The 2273 # number of ticks needs to be reduced if there are more 2274 # than self.numticks of them. 2275 # 2276 # b) has a tick at 0 and only 0 (we assume t is a small 2277 # number, and the linear segment is just an implementation 2278 # detail and not interesting.) 2279 # 2280 # We could also add ticks at t, but that seems to usually be 2281 # uninteresting. 2282 # 2283 # "simple" mode is when the range falls entirely within (-t, 2284 # t) -- it should just display (vmin, 0, vmax) 2285 2286 has_a = has_b = has_c = False 2287 if vmin < -t: 2288 has_a = True 2289 if vmax > -t: 2290 has_b = True 2291 if vmax > t: 2292 has_c = True 2293 elif vmin < 0: 2294 if vmax > 0: 2295 has_b = True 2296 if vmax > t: 2297 has_c = True 2298 else: 2299 return [vmin, vmax] 2300 elif vmin < t: 2301 if vmax > t: 2302 has_b = True 2303 has_c = True 2304 else: 2305 return [vmin, vmax] 2306 else: 2307 has_c = True 2308 2309 def get_log_range(lo, hi): 2310 lo = np.floor(np.log(lo) / np.log(b)) 2311 hi = np.ceil(np.log(hi) / np.log(b)) 2312 return lo, hi 2313 2314 # First, calculate all the ranges, so we can determine striding 2315 if has_a: 2316 if has_b: 2317 a_range = get_log_range(t, -vmin + 1) 2318 else: 2319 a_range = get_log_range(-vmax, -vmin + 1) 2320 else: 2321 a_range = (0, 0) 2322 2323 if has_c: 2324 if has_b: 2325 c_range = get_log_range(t, vmax + 1) 2326 else: 2327 c_range = get_log_range(vmin, vmax + 1) 2328 else: 2329 c_range = (0, 0) 2330 2331 total_ticks = (a_range[1] - a_range[0]) + (c_range[1] - c_range[0]) 2332 if has_b: 2333 total_ticks += 1 2334 stride = max(total_ticks // (self.numticks - 1), 1) 2335 2336 decades = [] 2337 if has_a: 2338 decades.extend(-1 * (b ** (np.arange(a_range[0], a_range[1], 2339 stride)[::-1]))) 2340 2341 if has_b: 2342 decades.append(0.0) 2343 2344 if has_c: 2345 decades.extend(b ** (np.arange(c_range[0], c_range[1], stride))) 2346 2347 # Add the subticks if requested 2348 if self._subs is None: 2349 subs = np.arange(2.0, b) 2350 else: 2351 subs = np.asarray(self._subs) 2352 2353 if len(subs) > 1 or subs[0] != 1.0: 2354 ticklocs = [] 2355 for decade in decades: 2356 if decade == 0: 2357 ticklocs.append(decade) 2358 else: 2359 ticklocs.extend(subs * decade) 2360 else: 2361 ticklocs = decades 2362 2363 return self.raise_if_exceeds(np.array(ticklocs)) 2364 2365 def view_limits(self, vmin, vmax): 2366 'Try to choose the view limits intelligently' 2367 b = self._base 2368 if vmax < vmin: 2369 vmin, vmax = vmax, vmin 2370 2371 if rcParams['axes.autolimit_mode'] == 'round_numbers': 2372 if not is_decade(abs(vmin), b): 2373 if vmin < 0: 2374 vmin = -decade_up(-vmin, b) 2375 else: 2376 vmin = decade_down(vmin, b) 2377 if not is_decade(abs(vmax), b): 2378 if vmax < 0: 2379 vmax = -decade_down(-vmax, b) 2380 else: 2381 vmax = decade_up(vmax, b) 2382 2383 if vmin == vmax: 2384 if vmin < 0: 2385 vmin = -decade_up(-vmin, b) 2386 vmax = -decade_down(-vmax, b) 2387 else: 2388 vmin = decade_down(vmin, b) 2389 vmax = decade_up(vmax, b) 2390 2391 result = mtransforms.nonsingular(vmin, vmax) 2392 return result 2393 2394 2395class LogitLocator(Locator): 2396 """ 2397 Determine the tick locations for logit axes 2398 """ 2399 2400 def __init__(self, minor=False): 2401 """ 2402 place ticks on the logit locations 2403 """ 2404 self.minor = minor 2405 2406 def set_params(self, minor=None): 2407 """Set parameters within this locator.""" 2408 if minor is not None: 2409 self.minor = minor 2410 2411 def __call__(self): 2412 'Return the locations of the ticks' 2413 vmin, vmax = self.axis.get_view_interval() 2414 return self.tick_values(vmin, vmax) 2415 2416 def tick_values(self, vmin, vmax): 2417 # dummy axis has no axes attribute 2418 if hasattr(self.axis, 'axes') and self.axis.axes.name == 'polar': 2419 raise NotImplementedError('Polar axis cannot be logit scaled yet') 2420 2421 vmin, vmax = self.nonsingular(vmin, vmax) 2422 vmin = np.log10(vmin / (1 - vmin)) 2423 vmax = np.log10(vmax / (1 - vmax)) 2424 2425 decade_min = np.floor(vmin) 2426 decade_max = np.ceil(vmax) 2427 2428 # major ticks 2429 if not self.minor: 2430 ticklocs = [] 2431 if (decade_min <= -1): 2432 expo = np.arange(decade_min, min(0, decade_max + 1)) 2433 ticklocs.extend(list(10**expo)) 2434 if (decade_min <= 0) and (decade_max >= 0): 2435 ticklocs.append(0.5) 2436 if (decade_max >= 1): 2437 expo = -np.arange(max(1, decade_min), decade_max + 1) 2438 ticklocs.extend(list(1 - 10**expo)) 2439 2440 # minor ticks 2441 else: 2442 ticklocs = [] 2443 if (decade_min <= -2): 2444 expo = np.arange(decade_min, min(-1, decade_max)) 2445 newticks = np.outer(np.arange(2, 10), 10**expo).ravel() 2446 ticklocs.extend(list(newticks)) 2447 if (decade_min <= 0) and (decade_max >= 0): 2448 ticklocs.extend([0.2, 0.3, 0.4, 0.6, 0.7, 0.8]) 2449 if (decade_max >= 2): 2450 expo = -np.arange(max(2, decade_min), decade_max + 1) 2451 newticks = 1 - np.outer(np.arange(2, 10), 10**expo).ravel() 2452 ticklocs.extend(list(newticks)) 2453 2454 return self.raise_if_exceeds(np.array(ticklocs)) 2455 2456 def nonsingular(self, vmin, vmax): 2457 initial_range = (1e-7, 1 - 1e-7) 2458 if not np.isfinite(vmin) or not np.isfinite(vmax): 2459 return initial_range # no data plotted yet 2460 2461 if vmin > vmax: 2462 vmin, vmax = vmax, vmin 2463 2464 # what to do if a window beyond ]0, 1[ is chosen 2465 if self.axis is not None: 2466 minpos = self.axis.get_minpos() 2467 if not np.isfinite(minpos): 2468 return initial_range # again, no data plotted 2469 else: 2470 minpos = 1e-7 # should not occur in normal use 2471 2472 # NOTE: for vmax, we should query a property similar to get_minpos, but 2473 # related to the maximal, less-than-one data point. Unfortunately, 2474 # Bbox._minpos is defined very deep in the BBox and updated with data, 2475 # so for now we use 1 - minpos as a substitute. 2476 2477 if vmin <= 0: 2478 vmin = minpos 2479 if vmax >= 1: 2480 vmax = 1 - minpos 2481 if vmin == vmax: 2482 return 0.1 * vmin, 1 - 0.1 * vmin 2483 2484 return vmin, vmax 2485 2486 2487class AutoLocator(MaxNLocator): 2488 """ 2489 Dynamically find major tick positions. This is actually a subclass 2490 of `~matplotlib.ticker.MaxNLocator`, with parameters *nbins = 'auto'* 2491 and *steps = [1, 2, 2.5, 5, 10]*. 2492 """ 2493 def __init__(self): 2494 """ 2495 To know the values of the non-public parameters, please have a 2496 look to the defaults of `~matplotlib.ticker.MaxNLocator`. 2497 """ 2498 if rcParams['_internal.classic_mode']: 2499 nbins = 9 2500 steps = [1, 2, 5, 10] 2501 else: 2502 nbins = 'auto' 2503 steps = [1, 2, 2.5, 5, 10] 2504 MaxNLocator.__init__(self, nbins=nbins, steps=steps) 2505 2506 2507class AutoMinorLocator(Locator): 2508 """ 2509 Dynamically find minor tick positions based on the positions of 2510 major ticks. The scale must be linear with major ticks evenly spaced. 2511 """ 2512 def __init__(self, n=None): 2513 """ 2514 *n* is the number of subdivisions of the interval between 2515 major ticks; e.g., n=2 will place a single minor tick midway 2516 between major ticks. 2517 2518 If *n* is omitted or None, it will be set to 5 or 4. 2519 """ 2520 self.ndivs = n 2521 2522 def __call__(self): 2523 'Return the locations of the ticks' 2524 if self.axis.get_scale() == 'log': 2525 warnings.warn('AutoMinorLocator does not work with logarithmic ' 2526 'scale') 2527 return [] 2528 2529 majorlocs = self.axis.get_majorticklocs() 2530 try: 2531 majorstep = majorlocs[1] - majorlocs[0] 2532 except IndexError: 2533 # Need at least two major ticks to find minor tick locations 2534 # TODO: Figure out a way to still be able to display minor 2535 # ticks without two major ticks visible. For now, just display 2536 # no ticks at all. 2537 return [] 2538 2539 if self.ndivs is None: 2540 x = int(np.round(10 ** (np.log10(majorstep) % 1))) 2541 if x in [1, 5, 10]: 2542 ndivs = 5 2543 else: 2544 ndivs = 4 2545 else: 2546 ndivs = self.ndivs 2547 2548 minorstep = majorstep / ndivs 2549 2550 vmin, vmax = self.axis.get_view_interval() 2551 if vmin > vmax: 2552 vmin, vmax = vmax, vmin 2553 2554 t0 = majorlocs[0] 2555 tmin = ((vmin - t0) // minorstep + 1) * minorstep 2556 tmax = ((vmax - t0) // minorstep + 1) * minorstep 2557 locs = np.arange(tmin, tmax, minorstep) + t0 2558 mod = np.abs((locs - t0) % majorstep) 2559 cond1 = mod > minorstep / 10.0 2560 cond2 = ~np.isclose(mod, majorstep, atol=0) 2561 locs = locs.compress(cond1 & cond2) 2562 2563 return self.raise_if_exceeds(np.array(locs)) 2564 2565 def tick_values(self, vmin, vmax): 2566 raise NotImplementedError('Cannot get tick locations for a ' 2567 '%s type.' % type(self)) 2568 2569 2570class OldAutoLocator(Locator): 2571 """ 2572 On autoscale this class picks the best MultipleLocator to set the 2573 view limits and the tick locs. 2574 2575 """ 2576 def __init__(self): 2577 self._locator = LinearLocator() 2578 2579 def __call__(self): 2580 'Return the locations of the ticks' 2581 self.refresh() 2582 return self.raise_if_exceeds(self._locator()) 2583 2584 def tick_values(self, vmin, vmax): 2585 raise NotImplementedError('Cannot get tick locations for a ' 2586 '%s type.' % type(self)) 2587 2588 def refresh(self): 2589 'refresh internal information based on current lim' 2590 vmin, vmax = self.axis.get_view_interval() 2591 vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) 2592 d = abs(vmax - vmin) 2593 self._locator = self.get_locator(d) 2594 2595 def view_limits(self, vmin, vmax): 2596 'Try to choose the view limits intelligently' 2597 2598 d = abs(vmax - vmin) 2599 self._locator = self.get_locator(d) 2600 return self._locator.view_limits(vmin, vmax) 2601 2602 def get_locator(self, d): 2603 'pick the best locator based on a distance' 2604 d = abs(d) 2605 if d <= 0: 2606 locator = MultipleLocator(0.2) 2607 else: 2608 2609 try: 2610 ld = math.log10(d) 2611 except OverflowError: 2612 raise RuntimeError('AutoLocator illegal data interval range') 2613 2614 fld = math.floor(ld) 2615 base = 10 ** fld 2616 2617 #if ld==fld: base = 10**(fld-1) 2618 #else: base = 10**fld 2619 2620 if d >= 5 * base: 2621 ticksize = base 2622 elif d >= 2 * base: 2623 ticksize = base / 2.0 2624 else: 2625 ticksize = base / 5.0 2626 locator = MultipleLocator(ticksize) 2627 2628 return locator 2629