Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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 

167import itertools 

168import logging 

169import locale 

170import math 

171import numpy as np 

172from matplotlib import rcParams 

173from matplotlib import cbook 

174from matplotlib import transforms as mtransforms 

175 

176_log = logging.getLogger(__name__) 

177 

178__all__ = ('TickHelper', 'Formatter', 'FixedFormatter', 

179 'NullFormatter', 'FuncFormatter', 'FormatStrFormatter', 

180 'StrMethodFormatter', 'ScalarFormatter', 'LogFormatter', 

181 'LogFormatterExponent', 'LogFormatterMathtext', 

182 'IndexFormatter', 'LogFormatterSciNotation', 

183 'LogitFormatter', 'EngFormatter', 'PercentFormatter', 

184 'OldScalarFormatter', 

185 'Locator', 'IndexLocator', 'FixedLocator', 'NullLocator', 

186 'LinearLocator', 'LogLocator', 'AutoLocator', 

187 'MultipleLocator', 'MaxNLocator', 'AutoMinorLocator', 

188 'SymmetricalLogLocator', 'LogitLocator', 'OldAutoLocator') 

189 

190 

191class _DummyAxis: 

192 def __init__(self, minpos=0): 

193 self.dataLim = mtransforms.Bbox.unit() 

194 self.viewLim = mtransforms.Bbox.unit() 

195 self._minpos = minpos 

196 

197 def get_view_interval(self): 

198 return self.viewLim.intervalx 

199 

200 def set_view_interval(self, vmin, vmax): 

201 self.viewLim.intervalx = vmin, vmax 

202 

203 def get_minpos(self): 

204 return self._minpos 

205 

206 def get_data_interval(self): 

207 return self.dataLim.intervalx 

208 

209 def set_data_interval(self, vmin, vmax): 

210 self.dataLim.intervalx = vmin, vmax 

211 

212 def get_tick_space(self): 

213 # Just use the long-standing default of nbins==9 

214 return 9 

215 

216 

217class TickHelper: 

218 axis = None 

219 

220 def set_axis(self, axis): 

221 self.axis = axis 

222 

223 def create_dummy_axis(self, **kwargs): 

224 if self.axis is None: 

225 self.axis = _DummyAxis(**kwargs) 

226 

227 def set_view_interval(self, vmin, vmax): 

228 self.axis.set_view_interval(vmin, vmax) 

229 

230 def set_data_interval(self, vmin, vmax): 

231 self.axis.set_data_interval(vmin, vmax) 

232 

233 def set_bounds(self, vmin, vmax): 

234 self.set_view_interval(vmin, vmax) 

235 self.set_data_interval(vmin, vmax) 

236 

237 

238class Formatter(TickHelper): 

239 """ 

240 Create a string based on a tick value and location. 

241 """ 

242 # some classes want to see all the locs to help format 

243 # individual ones 

244 locs = [] 

245 

246 def __call__(self, x, pos=None): 

247 """ 

248 Return the format for tick value *x* at position pos. 

249 ``pos=None`` indicates an unspecified location. 

250 """ 

251 raise NotImplementedError('Derived must override') 

252 

253 def format_ticks(self, values): 

254 """Return the tick labels for all the ticks at once.""" 

255 self.set_locs(values) 

256 return [self(value, i) for i, value in enumerate(values)] 

257 

258 def format_data(self, value): 

259 """ 

260 Returns the full string representation of the value with the 

261 position unspecified. 

262 """ 

263 return self.__call__(value) 

264 

265 def format_data_short(self, value): 

266 """ 

267 Return a short string version of the tick value. 

268 

269 Defaults to the position-independent long value. 

270 """ 

271 return self.format_data(value) 

272 

273 def get_offset(self): 

274 return '' 

275 

276 def set_locs(self, locs): 

277 self.locs = locs 

278 

279 def fix_minus(self, s): 

280 """ 

281 Some classes may want to replace a hyphen for minus with the 

282 proper unicode symbol (U+2212) for typographical correctness. 

283 The default is to not replace it. 

284 

285 Note, if you use this method, e.g., in :meth:`format_data` or 

286 call, you probably don't want to use it for 

287 :meth:`format_data_short` since the toolbar uses this for 

288 interactive coord reporting and I doubt we can expect GUIs 

289 across platforms will handle the unicode correctly. So for 

290 now the classes that override :meth:`fix_minus` should have an 

291 explicit :meth:`format_data_short` method 

292 """ 

293 return s 

294 

295 def _set_locator(self, locator): 

296 """Subclasses may want to override this to set a locator.""" 

297 pass 

298 

299 

300class IndexFormatter(Formatter): 

301 """ 

302 Format the position x to the nearest i-th label where ``i = int(x + 0.5)``. 

303 Positions where ``i < 0`` or ``i > len(list)`` have no tick labels. 

304 

305 Parameters 

306 ---------- 

307 labels : list 

308 List of labels. 

309 """ 

310 def __init__(self, labels): 

311 self.labels = labels 

312 self.n = len(labels) 

313 

314 def __call__(self, x, pos=None): 

315 """ 

316 Return the format for tick value *x* at position pos. 

317 

318 The position is ignored and the value is rounded to the nearest 

319 integer, which is used to look up the label. 

320 """ 

321 i = int(x + 0.5) 

322 if i < 0 or i >= self.n: 

323 return '' 

324 else: 

325 return self.labels[i] 

326 

327 

328class NullFormatter(Formatter): 

329 """ 

330 Always return the empty string. 

331 """ 

332 def __call__(self, x, pos=None): 

333 """ 

334 Returns an empty string for all inputs. 

335 """ 

336 return '' 

337 

338 

339class FixedFormatter(Formatter): 

340 """ 

341 Return fixed strings for tick labels based only on position, not value. 

342 """ 

343 def __init__(self, seq): 

344 """ 

345 Set the sequence of strings that will be used for labels. 

346 """ 

347 self.seq = seq 

348 self.offset_string = '' 

349 

350 def __call__(self, x, pos=None): 

351 """ 

352 Returns the label that matches the position regardless of the 

353 value. 

354 

355 For positions ``pos < len(seq)``, return ``seq[i]`` regardless of 

356 *x*. Otherwise return empty string. ``seq`` is the sequence of 

357 strings that this object was initialized with. 

358 """ 

359 if pos is None or pos >= len(self.seq): 

360 return '' 

361 else: 

362 return self.seq[pos] 

363 

364 def get_offset(self): 

365 return self.offset_string 

366 

367 def set_offset_string(self, ofs): 

368 self.offset_string = ofs 

369 

370 

371class FuncFormatter(Formatter): 

372 """ 

373 Use a user-defined function for formatting. 

374 

375 The function should take in two inputs (a tick value ``x`` and a 

376 position ``pos``), and return a string containing the corresponding 

377 tick label. 

378 """ 

379 def __init__(self, func): 

380 self.func = func 

381 

382 def __call__(self, x, pos=None): 

383 """ 

384 Return the value of the user defined function. 

385 

386 *x* and *pos* are passed through as-is. 

387 """ 

388 return self.func(x, pos) 

389 

390 

391class FormatStrFormatter(Formatter): 

392 """ 

393 Use an old-style ('%' operator) format string to format the tick. 

394 

395 The format string should have a single variable format (%) in it. 

396 It will be applied to the value (not the position) of the tick. 

397 """ 

398 def __init__(self, fmt): 

399 self.fmt = fmt 

400 

401 def __call__(self, x, pos=None): 

402 """ 

403 Return the formatted label string. 

404 

405 Only the value *x* is formatted. The position is ignored. 

406 """ 

407 return self.fmt % x 

408 

409 

410class StrMethodFormatter(Formatter): 

411 """ 

412 Use a new-style format string (as used by `str.format()`) 

413 to format the tick. 

414 

415 The field used for the value must be labeled *x* and the field used 

416 for the position must be labeled *pos*. 

417 """ 

418 def __init__(self, fmt): 

419 self.fmt = fmt 

420 

421 def __call__(self, x, pos=None): 

422 """ 

423 Return the formatted label string. 

424 

425 *x* and *pos* are passed to `str.format` as keyword arguments 

426 with those exact names. 

427 """ 

428 return self.fmt.format(x=x, pos=pos) 

429 

430 

431class OldScalarFormatter(Formatter): 

432 """ 

433 Tick location is a plain old number. 

434 """ 

435 

436 def __call__(self, x, pos=None): 

437 """ 

438 Return the format for tick val *x* based on the width of the axis. 

439 

440 The position *pos* is ignored. 

441 """ 

442 xmin, xmax = self.axis.get_view_interval() 

443 # If the number is not too big and it's an int, format it as an int. 

444 if abs(x) < 1e4 and x == int(x): 

445 return '%d' % x 

446 d = abs(xmax - xmin) 

447 fmt = ('%1.3e' if d < 1e-2 else 

448 '%1.3f' if d <= 1 else 

449 '%1.2f' if d <= 10 else 

450 '%1.1f' if d <= 1e5 else 

451 '%1.1e') 

452 s = fmt % x 

453 tup = s.split('e') 

454 if len(tup) == 2: 

455 mantissa = tup[0].rstrip('0').rstrip('.') 

456 sign = tup[1][0].replace('+', '') 

457 exponent = tup[1][1:].lstrip('0') 

458 s = '%se%s%s' % (mantissa, sign, exponent) 

459 else: 

460 s = s.rstrip('0').rstrip('.') 

461 return s 

462 

463 @cbook.deprecated("3.1") 

464 def pprint_val(self, x, d): 

465 """ 

466 Formats the value *x* based on the size of the axis range *d*. 

467 """ 

468 # If the number is not too big and it's an int, format it as an int. 

469 if abs(x) < 1e4 and x == int(x): 

470 return '%d' % x 

471 

472 if d < 1e-2: 

473 fmt = '%1.3e' 

474 elif d < 1e-1: 

475 fmt = '%1.3f' 

476 elif d > 1e5: 

477 fmt = '%1.1e' 

478 elif d > 10: 

479 fmt = '%1.1f' 

480 elif d > 1: 

481 fmt = '%1.2f' 

482 else: 

483 fmt = '%1.3f' 

484 s = fmt % x 

485 tup = s.split('e') 

486 if len(tup) == 2: 

487 mantissa = tup[0].rstrip('0').rstrip('.') 

488 sign = tup[1][0].replace('+', '') 

489 exponent = tup[1][1:].lstrip('0') 

490 s = '%se%s%s' % (mantissa, sign, exponent) 

491 else: 

492 s = s.rstrip('0').rstrip('.') 

493 return s 

494 

495 

496class ScalarFormatter(Formatter): 

497 """ 

498 Format tick values as a number. 

499 

500 Tick value is interpreted as a plain old number. If 

501 ``useOffset==True`` and the data range is much smaller than the data 

502 average, then an offset will be determined such that the tick labels 

503 are meaningful. Scientific notation is used for ``data < 10^-n`` or 

504 ``data >= 10^m``, where ``n`` and ``m`` are the power limits set 

505 using ``set_powerlimits((n, m))``. The defaults for these are 

506 controlled by the ``axes.formatter.limits`` rc parameter. 

507 """ 

508 def __init__(self, useOffset=None, useMathText=None, useLocale=None): 

509 # useOffset allows plotting small data ranges with large offsets: for 

510 # example: [1+1e-9, 1+2e-9, 1+3e-9] useMathText will render the offset 

511 # and scientific notation in mathtext 

512 

513 if useOffset is None: 

514 useOffset = rcParams['axes.formatter.useoffset'] 

515 self._offset_threshold = rcParams['axes.formatter.offset_threshold'] 

516 self.set_useOffset(useOffset) 

517 self._usetex = rcParams['text.usetex'] 

518 if useMathText is None: 

519 useMathText = rcParams['axes.formatter.use_mathtext'] 

520 self.set_useMathText(useMathText) 

521 self.orderOfMagnitude = 0 

522 self.format = '' 

523 self._scientific = True 

524 self._powerlimits = rcParams['axes.formatter.limits'] 

525 if useLocale is None: 

526 useLocale = rcParams['axes.formatter.use_locale'] 

527 self._useLocale = useLocale 

528 

529 def get_useOffset(self): 

530 return self._useOffset 

531 

532 def set_useOffset(self, val): 

533 if val in [True, False]: 

534 self.offset = 0 

535 self._useOffset = val 

536 else: 

537 self._useOffset = False 

538 self.offset = val 

539 

540 useOffset = property(fget=get_useOffset, fset=set_useOffset) 

541 

542 def get_useLocale(self): 

543 return self._useLocale 

544 

545 def set_useLocale(self, val): 

546 if val is None: 

547 self._useLocale = rcParams['axes.formatter.use_locale'] 

548 else: 

549 self._useLocale = val 

550 

551 useLocale = property(fget=get_useLocale, fset=set_useLocale) 

552 

553 def get_useMathText(self): 

554 return self._useMathText 

555 

556 def set_useMathText(self, val): 

557 if val is None: 

558 self._useMathText = rcParams['axes.formatter.use_mathtext'] 

559 else: 

560 self._useMathText = val 

561 

562 useMathText = property(fget=get_useMathText, fset=set_useMathText) 

563 

564 def fix_minus(self, s): 

565 """ 

566 Replace hyphens with a unicode minus. 

567 """ 

568 if rcParams['text.usetex'] or not rcParams['axes.unicode_minus']: 

569 return s 

570 else: 

571 return s.replace('-', '\N{MINUS SIGN}') 

572 

573 def __call__(self, x, pos=None): 

574 """ 

575 Return the format for tick value *x* at position *pos*. 

576 """ 

577 if len(self.locs) == 0: 

578 return '' 

579 else: 

580 xp = (x - self.offset) / (10. ** self.orderOfMagnitude) 

581 if np.abs(xp) < 1e-8: 

582 xp = 0 

583 if self._useLocale: 

584 s = locale.format_string(self.format, (xp,)) 

585 else: 

586 s = self.format % xp 

587 return self.fix_minus(s) 

588 

589 def set_scientific(self, b): 

590 """ 

591 Turn scientific notation on or off. 

592 

593 See Also 

594 -------- 

595 ScalarFormatter.set_powerlimits 

596 """ 

597 self._scientific = bool(b) 

598 

599 def set_powerlimits(self, lims): 

600 """ 

601 Sets size thresholds for scientific notation. 

602 

603 Parameters 

604 ---------- 

605 lims : (min_exp, max_exp) 

606 A tuple containing the powers of 10 that determine the switchover 

607 threshold. Numbers below ``10**min_exp`` and above ``10**max_exp`` 

608 will be displayed in scientific notation. 

609 

610 For example, ``formatter.set_powerlimits((-3, 4))`` sets the 

611 pre-2007 default in which scientific notation is used for 

612 numbers less than 1e-3 or greater than 1e4. 

613 

614 See Also 

615 -------- 

616 ScalarFormatter.set_scientific 

617 """ 

618 if len(lims) != 2: 

619 raise ValueError("'lims' must be a sequence of length 2") 

620 self._powerlimits = lims 

621 

622 def format_data_short(self, value): 

623 """ 

624 Return a short formatted string representation of a number. 

625 """ 

626 if self._useLocale: 

627 return locale.format_string('%-12g', (value,)) 

628 elif isinstance(value, np.ma.MaskedArray) and value.mask: 

629 return '' 

630 else: 

631 return '%-12g' % value 

632 

633 def format_data(self, value): 

634 """ 

635 Return a formatted string representation of a number. 

636 """ 

637 if self._useLocale: 

638 s = locale.format_string('%1.10e', (value,)) 

639 else: 

640 s = '%1.10e' % value 

641 s = self._formatSciNotation(s) 

642 return self.fix_minus(s) 

643 

644 def get_offset(self): 

645 """ 

646 Return scientific notation, plus offset. 

647 """ 

648 if len(self.locs) == 0: 

649 return '' 

650 s = '' 

651 if self.orderOfMagnitude or self.offset: 

652 offsetStr = '' 

653 sciNotStr = '' 

654 if self.offset: 

655 offsetStr = self.format_data(self.offset) 

656 if self.offset > 0: 

657 offsetStr = '+' + offsetStr 

658 if self.orderOfMagnitude: 

659 if self._usetex or self._useMathText: 

660 sciNotStr = self.format_data(10 ** self.orderOfMagnitude) 

661 else: 

662 sciNotStr = '1e%d' % self.orderOfMagnitude 

663 if self._useMathText or self._usetex: 

664 if sciNotStr != '': 

665 sciNotStr = r'\times\mathdefault{%s}' % sciNotStr 

666 s = r'$%s\mathdefault{%s}$' % (sciNotStr, offsetStr) 

667 else: 

668 s = ''.join((sciNotStr, offsetStr)) 

669 

670 return self.fix_minus(s) 

671 

672 def set_locs(self, locs): 

673 """ 

674 Set the locations of the ticks. 

675 """ 

676 self.locs = locs 

677 if len(self.locs) > 0: 

678 if self._useOffset: 

679 self._compute_offset() 

680 self._set_order_of_magnitude() 

681 self._set_format() 

682 

683 def _compute_offset(self): 

684 locs = self.locs 

685 # Restrict to visible ticks. 

686 vmin, vmax = sorted(self.axis.get_view_interval()) 

687 locs = np.asarray(locs) 

688 locs = locs[(vmin <= locs) & (locs <= vmax)] 

689 if not len(locs): 

690 self.offset = 0 

691 return 

692 lmin, lmax = locs.min(), locs.max() 

693 # Only use offset if there are at least two ticks and every tick has 

694 # the same sign. 

695 if lmin == lmax or lmin <= 0 <= lmax: 

696 self.offset = 0 

697 return 

698 # min, max comparing absolute values (we want division to round towards 

699 # zero so we work on absolute values). 

700 abs_min, abs_max = sorted([abs(float(lmin)), abs(float(lmax))]) 

701 sign = math.copysign(1, lmin) 

702 # What is the smallest power of ten such that abs_min and abs_max are 

703 # equal up to that precision? 

704 # Note: Internally using oom instead of 10 ** oom avoids some numerical 

705 # accuracy issues. 

706 oom_max = np.ceil(math.log10(abs_max)) 

707 oom = 1 + next(oom for oom in itertools.count(oom_max, -1) 

708 if abs_min // 10 ** oom != abs_max // 10 ** oom) 

709 if (abs_max - abs_min) / 10 ** oom <= 1e-2: 

710 # Handle the case of straddling a multiple of a large power of ten 

711 # (relative to the span). 

712 # What is the smallest power of ten such that abs_min and abs_max 

713 # are no more than 1 apart at that precision? 

714 oom = 1 + next(oom for oom in itertools.count(oom_max, -1) 

715 if abs_max // 10 ** oom - abs_min // 10 ** oom > 1) 

716 # Only use offset if it saves at least _offset_threshold digits. 

717 n = self._offset_threshold - 1 

718 self.offset = (sign * (abs_max // 10 ** oom) * 10 ** oom 

719 if abs_max // 10 ** oom >= 10**n 

720 else 0) 

721 

722 def _set_order_of_magnitude(self): 

723 # if scientific notation is to be used, find the appropriate exponent 

724 # if using an numerical offset, find the exponent after applying the 

725 # offset. When lower power limit = upper <> 0, use provided exponent. 

726 if not self._scientific: 

727 self.orderOfMagnitude = 0 

728 return 

729 if self._powerlimits[0] == self._powerlimits[1] != 0: 

730 # fixed scaling when lower power limit = upper <> 0. 

731 self.orderOfMagnitude = self._powerlimits[0] 

732 return 

733 # restrict to visible ticks 

734 vmin, vmax = sorted(self.axis.get_view_interval()) 

735 locs = np.asarray(self.locs) 

736 locs = locs[(vmin <= locs) & (locs <= vmax)] 

737 locs = np.abs(locs) 

738 if not len(locs): 

739 self.orderOfMagnitude = 0 

740 return 

741 if self.offset: 

742 oom = math.floor(math.log10(vmax - vmin)) 

743 else: 

744 if locs[0] > locs[-1]: 

745 val = locs[0] 

746 else: 

747 val = locs[-1] 

748 if val == 0: 

749 oom = 0 

750 else: 

751 oom = math.floor(math.log10(val)) 

752 if oom <= self._powerlimits[0]: 

753 self.orderOfMagnitude = oom 

754 elif oom >= self._powerlimits[1]: 

755 self.orderOfMagnitude = oom 

756 else: 

757 self.orderOfMagnitude = 0 

758 

759 def _set_format(self): 

760 # set the format string to format all the ticklabels 

761 if len(self.locs) < 2: 

762 # Temporarily augment the locations with the axis end points. 

763 _locs = [*self.locs, *self.axis.get_view_interval()] 

764 else: 

765 _locs = self.locs 

766 locs = (np.asarray(_locs) - self.offset) / 10. ** self.orderOfMagnitude 

767 loc_range = np.ptp(locs) 

768 # Curvilinear coordinates can yield two identical points. 

769 if loc_range == 0: 

770 loc_range = np.max(np.abs(locs)) 

771 # Both points might be zero. 

772 if loc_range == 0: 

773 loc_range = 1 

774 if len(self.locs) < 2: 

775 # We needed the end points only for the loc_range calculation. 

776 locs = locs[:-2] 

777 loc_range_oom = int(math.floor(math.log10(loc_range))) 

778 # first estimate: 

779 sigfigs = max(0, 3 - loc_range_oom) 

780 # refined estimate: 

781 thresh = 1e-3 * 10 ** loc_range_oom 

782 while sigfigs >= 0: 

783 if np.abs(locs - np.round(locs, decimals=sigfigs)).max() < thresh: 

784 sigfigs -= 1 

785 else: 

786 break 

787 sigfigs += 1 

788 self.format = '%1.' + str(sigfigs) + 'f' 

789 if self._usetex or self._useMathText: 

790 self.format = r'$\mathdefault{%s}$' % self.format 

791 

792 @cbook.deprecated("3.1") 

793 def pprint_val(self, x): 

794 xp = (x - self.offset) / (10. ** self.orderOfMagnitude) 

795 if np.abs(xp) < 1e-8: 

796 xp = 0 

797 if self._useLocale: 

798 return locale.format_string(self.format, (xp,)) 

799 else: 

800 return self.format % xp 

801 

802 def _formatSciNotation(self, s): 

803 # transform 1e+004 into 1e4, for example 

804 if self._useLocale: 

805 decimal_point = locale.localeconv()['decimal_point'] 

806 positive_sign = locale.localeconv()['positive_sign'] 

807 else: 

808 decimal_point = '.' 

809 positive_sign = '+' 

810 tup = s.split('e') 

811 try: 

812 significand = tup[0].rstrip('0').rstrip(decimal_point) 

813 sign = tup[1][0].replace(positive_sign, '') 

814 exponent = tup[1][1:].lstrip('0') 

815 if self._useMathText or self._usetex: 

816 if significand == '1' and exponent != '': 

817 # reformat 1x10^y as 10^y 

818 significand = '' 

819 if exponent: 

820 exponent = '10^{%s%s}' % (sign, exponent) 

821 if significand and exponent: 

822 return r'%s{\times}%s' % (significand, exponent) 

823 else: 

824 return r'%s%s' % (significand, exponent) 

825 else: 

826 s = ('%se%s%s' % (significand, sign, exponent)).rstrip('e') 

827 return s 

828 except IndexError: 

829 return s 

830 

831 

832class LogFormatter(Formatter): 

833 """ 

834 Base class for formatting ticks on a log or symlog scale. 

835 

836 It may be instantiated directly, or subclassed. 

837 

838 Parameters 

839 ---------- 

840 base : float, optional, default: 10. 

841 Base of the logarithm used in all calculations. 

842 

843 labelOnlyBase : bool, optional, default: False 

844 If True, label ticks only at integer powers of base. 

845 This is normally True for major ticks and False for 

846 minor ticks. 

847 

848 minor_thresholds : (subset, all), optional, default: (1, 0.4) 

849 If labelOnlyBase is False, these two numbers control 

850 the labeling of ticks that are not at integer powers of 

851 base; normally these are the minor ticks. The controlling 

852 parameter is the log of the axis data range. In the typical 

853 case where base is 10 it is the number of decades spanned 

854 by the axis, so we can call it 'numdec'. If ``numdec <= all``, 

855 all minor ticks will be labeled. If ``all < numdec <= subset``, 

856 then only a subset of minor ticks will be labeled, so as to 

857 avoid crowding. If ``numdec > subset`` then no minor ticks will 

858 be labeled. 

859 

860 linthresh : None or float, optional, default: None 

861 If a symmetric log scale is in use, its ``linthresh`` 

862 parameter must be supplied here. 

863 

864 Notes 

865 ----- 

866 The `set_locs` method must be called to enable the subsetting 

867 logic controlled by the ``minor_thresholds`` parameter. 

868 

869 In some cases such as the colorbar, there is no distinction between 

870 major and minor ticks; the tick locations might be set manually, 

871 or by a locator that puts ticks at integer powers of base and 

872 at intermediate locations. For this situation, disable the 

873 minor_thresholds logic by using ``minor_thresholds=(np.inf, np.inf)``, 

874 so that all ticks will be labeled. 

875 

876 To disable labeling of minor ticks when 'labelOnlyBase' is False, 

877 use ``minor_thresholds=(0, 0)``. This is the default for the 

878 "classic" style. 

879 

880 Examples 

881 -------- 

882 To label a subset of minor ticks when the view limits span up 

883 to 2 decades, and all of the ticks when zoomed in to 0.5 decades 

884 or less, use ``minor_thresholds=(2, 0.5)``. 

885 

886 To label all minor ticks when the view limits span up to 1.5 

887 decades, use ``minor_thresholds=(1.5, 1.5)``. 

888 

889 """ 

890 def __init__(self, base=10.0, labelOnlyBase=False, 

891 minor_thresholds=None, 

892 linthresh=None): 

893 

894 self._base = float(base) 

895 self.labelOnlyBase = labelOnlyBase 

896 if minor_thresholds is None: 

897 if rcParams['_internal.classic_mode']: 

898 minor_thresholds = (0, 0) 

899 else: 

900 minor_thresholds = (1, 0.4) 

901 self.minor_thresholds = minor_thresholds 

902 self._sublabels = None 

903 self._linthresh = linthresh 

904 

905 def base(self, base): 

906 """ 

907 Change the *base* for labeling. 

908 

909 .. warning:: 

910 Should always match the base used for :class:`LogLocator` 

911 

912 """ 

913 self._base = base 

914 

915 def label_minor(self, labelOnlyBase): 

916 """ 

917 Switch minor tick labeling on or off. 

918 

919 Parameters 

920 ---------- 

921 labelOnlyBase : bool 

922 If True, label ticks only at integer powers of base. 

923 

924 """ 

925 self.labelOnlyBase = labelOnlyBase 

926 

927 def set_locs(self, locs=None): 

928 """ 

929 Use axis view limits to control which ticks are labeled. 

930 

931 The *locs* parameter is ignored in the present algorithm. 

932 

933 """ 

934 if np.isinf(self.minor_thresholds[0]): 

935 self._sublabels = None 

936 return 

937 

938 # Handle symlog case: 

939 linthresh = self._linthresh 

940 if linthresh is None: 

941 try: 

942 linthresh = self.axis.get_transform().linthresh 

943 except AttributeError: 

944 pass 

945 

946 vmin, vmax = self.axis.get_view_interval() 

947 if vmin > vmax: 

948 vmin, vmax = vmax, vmin 

949 

950 if linthresh is None and vmin <= 0: 

951 # It's probably a colorbar with 

952 # a format kwarg setting a LogFormatter in the manner 

953 # that worked with 1.5.x, but that doesn't work now. 

954 self._sublabels = {1} # label powers of base 

955 return 

956 

957 b = self._base 

958 if linthresh is not None: # symlog 

959 # Only compute the number of decades in the logarithmic part of the 

960 # axis 

961 numdec = 0 

962 if vmin < -linthresh: 

963 rhs = min(vmax, -linthresh) 

964 numdec += math.log(vmin / rhs) / math.log(b) 

965 if vmax > linthresh: 

966 lhs = max(vmin, linthresh) 

967 numdec += math.log(vmax / lhs) / math.log(b) 

968 else: 

969 vmin = math.log(vmin) / math.log(b) 

970 vmax = math.log(vmax) / math.log(b) 

971 numdec = abs(vmax - vmin) 

972 

973 if numdec > self.minor_thresholds[0]: 

974 # Label only bases 

975 self._sublabels = {1} 

976 elif numdec > self.minor_thresholds[1]: 

977 # Add labels between bases at log-spaced coefficients; 

978 # include base powers in case the locations include 

979 # "major" and "minor" points, as in colorbar. 

980 c = np.logspace(0, 1, int(b)//2 + 1, base=b) 

981 self._sublabels = set(np.round(c)) 

982 # For base 10, this yields (1, 2, 3, 4, 6, 10). 

983 else: 

984 # Label all integer multiples of base**n. 

985 self._sublabels = set(np.arange(1, b + 1)) 

986 

987 def _num_to_string(self, x, vmin, vmax): 

988 if x > 10000: 

989 s = '%1.0e' % x 

990 elif x < 1: 

991 s = '%1.0e' % x 

992 else: 

993 s = self._pprint_val(x, vmax - vmin) 

994 return s 

995 

996 def __call__(self, x, pos=None): 

997 """ 

998 Return the format for tick val *x*. 

999 """ 

1000 if x == 0.0: # Symlog 

1001 return '0' 

1002 

1003 x = abs(x) 

1004 b = self._base 

1005 # only label the decades 

1006 fx = math.log(x) / math.log(b) 

1007 is_x_decade = is_close_to_int(fx) 

1008 exponent = round(fx) if is_x_decade else np.floor(fx) 

1009 coeff = round(x / b ** exponent) 

1010 

1011 if self.labelOnlyBase and not is_x_decade: 

1012 return '' 

1013 if self._sublabels is not None and coeff not in self._sublabels: 

1014 return '' 

1015 

1016 vmin, vmax = self.axis.get_view_interval() 

1017 vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) 

1018 s = self._num_to_string(x, vmin, vmax) 

1019 return self.fix_minus(s) 

1020 

1021 def format_data(self, value): 

1022 b = self.labelOnlyBase 

1023 self.labelOnlyBase = False 

1024 value = cbook.strip_math(self.__call__(value)) 

1025 self.labelOnlyBase = b 

1026 return value 

1027 

1028 def format_data_short(self, value): 

1029 """ 

1030 Return a short formatted string representation of a number. 

1031 """ 

1032 return '%-12g' % value 

1033 

1034 @cbook.deprecated("3.1") 

1035 def pprint_val(self, *args, **kwargs): 

1036 return self._pprint_val(*args, **kwargs) 

1037 

1038 def _pprint_val(self, x, d): 

1039 # If the number is not too big and it's an int, format it as an int. 

1040 if abs(x) < 1e4 and x == int(x): 

1041 return '%d' % x 

1042 fmt = ('%1.3e' if d < 1e-2 else 

1043 '%1.3f' if d <= 1 else 

1044 '%1.2f' if d <= 10 else 

1045 '%1.1f' if d <= 1e5 else 

1046 '%1.1e') 

1047 s = fmt % x 

1048 tup = s.split('e') 

1049 if len(tup) == 2: 

1050 mantissa = tup[0].rstrip('0').rstrip('.') 

1051 exponent = int(tup[1]) 

1052 if exponent: 

1053 s = '%se%d' % (mantissa, exponent) 

1054 else: 

1055 s = mantissa 

1056 else: 

1057 s = s.rstrip('0').rstrip('.') 

1058 return s 

1059 

1060 

1061class LogFormatterExponent(LogFormatter): 

1062 """ 

1063 Format values for log axis using ``exponent = log_base(value)``. 

1064 """ 

1065 def _num_to_string(self, x, vmin, vmax): 

1066 fx = math.log(x) / math.log(self._base) 

1067 if abs(fx) > 10000: 

1068 s = '%1.0g' % fx 

1069 elif abs(fx) < 1: 

1070 s = '%1.0g' % fx 

1071 else: 

1072 fd = math.log(vmax - vmin) / math.log(self._base) 

1073 s = self._pprint_val(fx, fd) 

1074 return s 

1075 

1076 

1077class LogFormatterMathtext(LogFormatter): 

1078 """ 

1079 Format values for log axis using ``exponent = log_base(value)``. 

1080 """ 

1081 

1082 def _non_decade_format(self, sign_string, base, fx, usetex): 

1083 'Return string for non-decade locations' 

1084 return r'$\mathdefault{%s%s^{%.2f}}$' % (sign_string, base, fx) 

1085 

1086 def __call__(self, x, pos=None): 

1087 """ 

1088 Return the format for tick value *x*. 

1089 

1090 The position *pos* is ignored. 

1091 """ 

1092 usetex = rcParams['text.usetex'] 

1093 min_exp = rcParams['axes.formatter.min_exponent'] 

1094 

1095 if x == 0: # Symlog 

1096 return r'$\mathdefault{0}$' 

1097 

1098 sign_string = '-' if x < 0 else '' 

1099 x = abs(x) 

1100 b = self._base 

1101 

1102 # only label the decades 

1103 fx = math.log(x) / math.log(b) 

1104 is_x_decade = is_close_to_int(fx) 

1105 exponent = round(fx) if is_x_decade else np.floor(fx) 

1106 coeff = round(x / b ** exponent) 

1107 if is_x_decade: 

1108 fx = round(fx) 

1109 

1110 if self.labelOnlyBase and not is_x_decade: 

1111 return '' 

1112 if self._sublabels is not None and coeff not in self._sublabels: 

1113 return '' 

1114 

1115 # use string formatting of the base if it is not an integer 

1116 if b % 1 == 0.0: 

1117 base = '%d' % b 

1118 else: 

1119 base = '%s' % b 

1120 

1121 if abs(fx) < min_exp: 

1122 return r'$\mathdefault{%s%g}$' % (sign_string, x) 

1123 elif not is_x_decade: 

1124 return self._non_decade_format(sign_string, base, fx, usetex) 

1125 else: 

1126 return r'$\mathdefault{%s%s^{%d}}$' % (sign_string, base, fx) 

1127 

1128 

1129class LogFormatterSciNotation(LogFormatterMathtext): 

1130 """ 

1131 Format values following scientific notation in a logarithmic axis. 

1132 """ 

1133 

1134 def _non_decade_format(self, sign_string, base, fx, usetex): 

1135 'Return string for non-decade locations' 

1136 b = float(base) 

1137 exponent = math.floor(fx) 

1138 coeff = b ** fx / b ** exponent 

1139 if is_close_to_int(coeff): 

1140 coeff = round(coeff) 

1141 return r'$\mathdefault{%s%g\times%s^{%d}}$' \ 

1142 % (sign_string, coeff, base, exponent) 

1143 

1144 

1145class LogitFormatter(Formatter): 

1146 """ 

1147 Probability formatter (using Math text). 

1148 """ 

1149 

1150 def __init__( 

1151 self, 

1152 *, 

1153 use_overline=False, 

1154 one_half=r"\frac{1}{2}", 

1155 minor=False, 

1156 minor_threshold=25, 

1157 minor_number=6, 

1158 ): 

1159 r""" 

1160 Parameters 

1161 ---------- 

1162 use_overline : bool, default: False 

1163 If x > 1/2, with x = 1-v, indicate if x should be displayed as 

1164 $\overline{v}$. The default is to display $1-v$. 

1165 

1166 one_half : str, default: r"\frac{1}{2}" 

1167 The string used to represent 1/2. 

1168 

1169 minor : bool, default: False 

1170 Indicate if the formatter is formatting minor ticks or not. 

1171 Basically minor ticks are not labelled, except when only few ticks 

1172 are provided, ticks with most space with neighbor ticks are 

1173 labelled. See other parameters to change the default behavior. 

1174 

1175 minor_threshold : int, default: 25 

1176 Maximum number of locs for labelling some minor ticks. This 

1177 parameter have no effect if minor is False. 

1178 

1179 minor_number : int, default: 6 

1180 Number of ticks which are labelled when the number of ticks is 

1181 below the threshold. 

1182 """ 

1183 self._use_overline = use_overline 

1184 self._one_half = one_half 

1185 self._minor = minor 

1186 self._labelled = set() 

1187 self._minor_threshold = minor_threshold 

1188 self._minor_number = minor_number 

1189 

1190 def use_overline(self, use_overline): 

1191 r""" 

1192 Switch display mode with overline for labelling p>1/2. 

1193 

1194 Parameters 

1195 ---------- 

1196 use_overline : bool, default: False 

1197 If x > 1/2, with x = 1-v, indicate if x should be displayed as 

1198 $\overline{v}$. The default is to display $1-v$. 

1199 """ 

1200 self._use_overline = use_overline 

1201 

1202 def set_one_half(self, one_half): 

1203 r""" 

1204 Set the way one half is displayed. 

1205 

1206 one_half : str, default: r"\frac{1}{2}" 

1207 The string used to represent 1/2. 

1208 """ 

1209 self._one_half = one_half 

1210 

1211 def set_minor_threshold(self, minor_threshold): 

1212 """ 

1213 Set the threshold for labelling minors ticks. 

1214 

1215 Parameters 

1216 ---------- 

1217 minor_threshold : int 

1218 Maximum number of locations for labelling some minor ticks. This 

1219 parameter have no effect if minor is False. 

1220 """ 

1221 self._minor_threshold = minor_threshold 

1222 

1223 def set_minor_number(self, minor_number): 

1224 """ 

1225 Set the number of minor ticks to label when some minor ticks are 

1226 labelled. 

1227 

1228 Parameters 

1229 ---------- 

1230 minor_number : int 

1231 Number of ticks which are labelled when the number of ticks is 

1232 below the threshold. 

1233 """ 

1234 self._minor_number = minor_number 

1235 

1236 def set_locs(self, locs): 

1237 self.locs = np.array(locs) 

1238 self._labelled.clear() 

1239 

1240 if not self._minor: 

1241 return None 

1242 if all( 

1243 is_decade(x, rtol=1e-7) 

1244 or is_decade(1 - x, rtol=1e-7) 

1245 or (is_close_to_int(2 * x) and int(np.round(2 * x)) == 1) 

1246 for x in locs 

1247 ): 

1248 # minor ticks are subsample from ideal, so no label 

1249 return None 

1250 if len(locs) < self._minor_threshold: 

1251 if len(locs) < self._minor_number: 

1252 self._labelled.update(locs) 

1253 else: 

1254 # we do not have a lot of minor ticks, so only few decades are 

1255 # displayed, then we choose some (spaced) minor ticks to label. 

1256 # Only minor ticks are known, we assume it is sufficient to 

1257 # choice which ticks are displayed. 

1258 # For each ticks we compute the distance between the ticks and 

1259 # the previous, and between the ticks and the next one. Ticks 

1260 # with smallest minimum are chosen. As tiebreak, the ticks 

1261 # with smallest sum is chosen. 

1262 diff = np.diff(-np.log(1 / self.locs - 1)) 

1263 space_pessimistic = np.minimum( 

1264 np.concatenate(((np.inf,), diff)), 

1265 np.concatenate((diff, (np.inf,))), 

1266 ) 

1267 space_sum = ( 

1268 np.concatenate(((0,), diff)) 

1269 + np.concatenate((diff, (0,))) 

1270 ) 

1271 good_minor = sorted( 

1272 range(len(self.locs)), 

1273 key=lambda i: (space_pessimistic[i], space_sum[i]), 

1274 )[-self._minor_number:] 

1275 self._labelled.update(locs[i] for i in good_minor) 

1276 

1277 def _format_value(self, x, locs, sci_notation=True): 

1278 if sci_notation: 

1279 exponent = math.floor(np.log10(x)) 

1280 min_precision = 0 

1281 else: 

1282 exponent = 0 

1283 min_precision = 1 

1284 value = x * 10 ** (-exponent) 

1285 if len(locs) < 2: 

1286 precision = min_precision 

1287 else: 

1288 diff = np.sort(np.abs(locs - x))[1] 

1289 precision = -np.log10(diff) + exponent 

1290 precision = ( 

1291 int(np.round(precision)) 

1292 if is_close_to_int(precision) 

1293 else math.ceil(precision) 

1294 ) 

1295 if precision < min_precision: 

1296 precision = min_precision 

1297 mantissa = r"%.*f" % (precision, value) 

1298 if not sci_notation: 

1299 return mantissa 

1300 s = r"%s\cdot10^{%d}" % (mantissa, exponent) 

1301 return s 

1302 

1303 def _one_minus(self, s): 

1304 if self._use_overline: 

1305 return r"\overline{%s}" % s 

1306 else: 

1307 return "1-{}".format(s) 

1308 

1309 def __call__(self, x, pos=None): 

1310 if self._minor and x not in self._labelled: 

1311 return "" 

1312 if x <= 0 or x >= 1: 

1313 return "" 

1314 if is_close_to_int(2 * x) and round(2 * x) == 1: 

1315 s = self._one_half 

1316 elif x < 0.5 and is_decade(x, rtol=1e-7): 

1317 exponent = round(np.log10(x)) 

1318 s = "10^{%d}" % exponent 

1319 elif x > 0.5 and is_decade(1 - x, rtol=1e-7): 

1320 exponent = round(np.log10(1 - x)) 

1321 s = self._one_minus("10^{%d}" % exponent) 

1322 elif x < 0.1: 

1323 s = self._format_value(x, self.locs) 

1324 elif x > 0.9: 

1325 s = self._one_minus(self._format_value(1-x, 1-self.locs)) 

1326 else: 

1327 s = self._format_value(x, self.locs, sci_notation=False) 

1328 return r"$\mathdefault{%s}$" % s 

1329 

1330 def format_data_short(self, value): 

1331 """ 

1332 Return a short formatted string representation of a number. 

1333 """ 

1334 # thresholds choosen for use scienfic notation if and only if exponent 

1335 # is less or equal than -2. 

1336 if value < 0.1: 

1337 return "{:e}".format(value) 

1338 if value < 0.9: 

1339 return "{:f}".format(value) 

1340 return "1-{:e}".format(1 - value) 

1341 

1342 

1343class EngFormatter(Formatter): 

1344 """ 

1345 Formats axis values using engineering prefixes to represent powers 

1346 of 1000, plus a specified unit, e.g., 10 MHz instead of 1e7. 

1347 """ 

1348 

1349 # The SI engineering prefixes 

1350 ENG_PREFIXES = { 

1351 -24: "y", 

1352 -21: "z", 

1353 -18: "a", 

1354 -15: "f", 

1355 -12: "p", 

1356 -9: "n", 

1357 -6: "\N{MICRO SIGN}", 

1358 -3: "m", 

1359 0: "", 

1360 3: "k", 

1361 6: "M", 

1362 9: "G", 

1363 12: "T", 

1364 15: "P", 

1365 18: "E", 

1366 21: "Z", 

1367 24: "Y" 

1368 } 

1369 

1370 def __init__(self, unit="", places=None, sep=" ", *, usetex=None, 

1371 useMathText=None): 

1372 r""" 

1373 Parameters 

1374 ---------- 

1375 unit : str (default: "") 

1376 Unit symbol to use, suitable for use with single-letter 

1377 representations of powers of 1000. For example, 'Hz' or 'm'. 

1378 

1379 places : int (default: None) 

1380 Precision with which to display the number, specified in 

1381 digits after the decimal point (there will be between one 

1382 and three digits before the decimal point). If it is None, 

1383 the formatting falls back to the floating point format '%g', 

1384 which displays up to 6 *significant* digits, i.e. the equivalent 

1385 value for *places* varies between 0 and 5 (inclusive). 

1386 

1387 sep : str (default: " ") 

1388 Separator used between the value and the prefix/unit. For 

1389 example, one get '3.14 mV' if ``sep`` is " " (default) and 

1390 '3.14mV' if ``sep`` is "". Besides the default behavior, some 

1391 other useful options may be: 

1392 

1393 * ``sep=""`` to append directly the prefix/unit to the value; 

1394 * ``sep="\N{THIN SPACE}"`` (``U+2009``); 

1395 * ``sep="\N{NARROW NO-BREAK SPACE}"`` (``U+202F``); 

1396 * ``sep="\N{NO-BREAK SPACE}"`` (``U+00A0``). 

1397 

1398 usetex : bool (default: None) 

1399 To enable/disable the use of TeX's math mode for rendering the 

1400 numbers in the formatter. 

1401 

1402 useMathText : bool (default: None) 

1403 To enable/disable the use mathtext for rendering the numbers in 

1404 the formatter. 

1405 """ 

1406 self.unit = unit 

1407 self.places = places 

1408 self.sep = sep 

1409 self.set_usetex(usetex) 

1410 self.set_useMathText(useMathText) 

1411 

1412 def get_usetex(self): 

1413 return self._usetex 

1414 

1415 def set_usetex(self, val): 

1416 if val is None: 

1417 self._usetex = rcParams['text.usetex'] 

1418 else: 

1419 self._usetex = val 

1420 

1421 usetex = property(fget=get_usetex, fset=set_usetex) 

1422 

1423 def get_useMathText(self): 

1424 return self._useMathText 

1425 

1426 def set_useMathText(self, val): 

1427 if val is None: 

1428 self._useMathText = rcParams['axes.formatter.use_mathtext'] 

1429 else: 

1430 self._useMathText = val 

1431 

1432 useMathText = property(fget=get_useMathText, fset=set_useMathText) 

1433 

1434 def fix_minus(self, s): 

1435 """ 

1436 Replace hyphens with a unicode minus. 

1437 """ 

1438 return ScalarFormatter.fix_minus(self, s) 

1439 

1440 def __call__(self, x, pos=None): 

1441 s = "%s%s" % (self.format_eng(x), self.unit) 

1442 # Remove the trailing separator when there is neither prefix nor unit 

1443 if self.sep and s.endswith(self.sep): 

1444 s = s[:-len(self.sep)] 

1445 return self.fix_minus(s) 

1446 

1447 def format_eng(self, num): 

1448 """ 

1449 Formats a number in engineering notation, appending a letter 

1450 representing the power of 1000 of the original number. 

1451 Some examples: 

1452 

1453 >>> format_eng(0) # for self.places = 0 

1454 '0' 

1455 

1456 >>> format_eng(1000000) # for self.places = 1 

1457 '1.0 M' 

1458 

1459 >>> format_eng("-1e-6") # for self.places = 2 

1460 '-1.00 \N{MICRO SIGN}' 

1461 """ 

1462 sign = 1 

1463 fmt = "g" if self.places is None else ".{:d}f".format(self.places) 

1464 

1465 if num < 0: 

1466 sign = -1 

1467 num = -num 

1468 

1469 if num != 0: 

1470 pow10 = int(math.floor(math.log10(num) / 3) * 3) 

1471 else: 

1472 pow10 = 0 

1473 # Force num to zero, to avoid inconsistencies like 

1474 # format_eng(-0) = "0" and format_eng(0.0) = "0" 

1475 # but format_eng(-0.0) = "-0.0" 

1476 num = 0.0 

1477 

1478 pow10 = np.clip(pow10, min(self.ENG_PREFIXES), max(self.ENG_PREFIXES)) 

1479 

1480 mant = sign * num / (10.0 ** pow10) 

1481 # Taking care of the cases like 999.9..., which may be rounded to 1000 

1482 # instead of 1 k. Beware of the corner case of values that are beyond 

1483 # the range of SI prefixes (i.e. > 'Y'). 

1484 if (abs(float(format(mant, fmt))) >= 1000 

1485 and pow10 < max(self.ENG_PREFIXES)): 

1486 mant /= 1000 

1487 pow10 += 3 

1488 

1489 prefix = self.ENG_PREFIXES[int(pow10)] 

1490 if self._usetex or self._useMathText: 

1491 formatted = "${mant:{fmt}}${sep}{prefix}".format( 

1492 mant=mant, sep=self.sep, prefix=prefix, fmt=fmt) 

1493 else: 

1494 formatted = "{mant:{fmt}}{sep}{prefix}".format( 

1495 mant=mant, sep=self.sep, prefix=prefix, fmt=fmt) 

1496 

1497 return formatted 

1498 

1499 

1500class PercentFormatter(Formatter): 

1501 """ 

1502 Format numbers as a percentage. 

1503 

1504 Parameters 

1505 ---------- 

1506 xmax : float 

1507 Determines how the number is converted into a percentage. 

1508 *xmax* is the data value that corresponds to 100%. 

1509 Percentages are computed as ``x / xmax * 100``. So if the data is 

1510 already scaled to be percentages, *xmax* will be 100. Another common 

1511 situation is where *xmax* is 1.0. 

1512 

1513 decimals : None or int 

1514 The number of decimal places to place after the point. 

1515 If *None* (the default), the number will be computed automatically. 

1516 

1517 symbol : str or None 

1518 A string that will be appended to the label. It may be 

1519 *None* or empty to indicate that no symbol should be used. LaTeX 

1520 special characters are escaped in *symbol* whenever latex mode is 

1521 enabled, unless *is_latex* is *True*. 

1522 

1523 is_latex : bool 

1524 If *False*, reserved LaTeX characters in *symbol* will be escaped. 

1525 """ 

1526 def __init__(self, xmax=100, decimals=None, symbol='%', is_latex=False): 

1527 self.xmax = xmax + 0.0 

1528 self.decimals = decimals 

1529 self._symbol = symbol 

1530 self._is_latex = is_latex 

1531 

1532 def __call__(self, x, pos=None): 

1533 """ 

1534 Formats the tick as a percentage with the appropriate scaling. 

1535 """ 

1536 ax_min, ax_max = self.axis.get_view_interval() 

1537 display_range = abs(ax_max - ax_min) 

1538 

1539 return self.fix_minus(self.format_pct(x, display_range)) 

1540 

1541 def format_pct(self, x, display_range): 

1542 """ 

1543 Formats the number as a percentage number with the correct 

1544 number of decimals and adds the percent symbol, if any. 

1545 

1546 If `self.decimals` is `None`, the number of digits after the 

1547 decimal point is set based on the `display_range` of the axis 

1548 as follows: 

1549 

1550 +---------------+----------+------------------------+ 

1551 | display_range | decimals | sample | 

1552 +---------------+----------+------------------------+ 

1553 | >50 | 0 | ``x = 34.5`` => 35% | 

1554 +---------------+----------+------------------------+ 

1555 | >5 | 1 | ``x = 34.5`` => 34.5% | 

1556 +---------------+----------+------------------------+ 

1557 | >0.5 | 2 | ``x = 34.5`` => 34.50% | 

1558 +---------------+----------+------------------------+ 

1559 | ... | ... | ... | 

1560 +---------------+----------+------------------------+ 

1561 

1562 This method will not be very good for tiny axis ranges or 

1563 extremely large ones. It assumes that the values on the chart 

1564 are percentages displayed on a reasonable scale. 

1565 """ 

1566 x = self.convert_to_pct(x) 

1567 if self.decimals is None: 

1568 # conversion works because display_range is a difference 

1569 scaled_range = self.convert_to_pct(display_range) 

1570 if scaled_range <= 0: 

1571 decimals = 0 

1572 else: 

1573 # Luckily Python's built-in ceil rounds to +inf, not away from 

1574 # zero. This is very important since the equation for decimals 

1575 # starts out as `scaled_range > 0.5 * 10**(2 - decimals)` 

1576 # and ends up with `decimals > 2 - log10(2 * scaled_range)`. 

1577 decimals = math.ceil(2.0 - math.log10(2.0 * scaled_range)) 

1578 if decimals > 5: 

1579 decimals = 5 

1580 elif decimals < 0: 

1581 decimals = 0 

1582 else: 

1583 decimals = self.decimals 

1584 s = '{x:0.{decimals}f}'.format(x=x, decimals=int(decimals)) 

1585 

1586 return s + self.symbol 

1587 

1588 def convert_to_pct(self, x): 

1589 return 100.0 * (x / self.xmax) 

1590 

1591 @property 

1592 def symbol(self): 

1593 r""" 

1594 The configured percent symbol as a string. 

1595 

1596 If LaTeX is enabled via :rc:`text.usetex`, the special characters 

1597 ``{'#', '$', '%', '&', '~', '_', '^', '\', '{', '}'}`` are 

1598 automatically escaped in the string. 

1599 """ 

1600 symbol = self._symbol 

1601 if not symbol: 

1602 symbol = '' 

1603 elif rcParams['text.usetex'] and not self._is_latex: 

1604 # Source: http://www.personal.ceu.hu/tex/specchar.htm 

1605 # Backslash must be first for this to work correctly since 

1606 # it keeps getting added in 

1607 for spec in r'\#$%&~_^{}': 

1608 symbol = symbol.replace(spec, '\\' + spec) 

1609 return symbol 

1610 

1611 @symbol.setter 

1612 def symbol(self, symbol): 

1613 self._symbol = symbol 

1614 

1615 

1616class Locator(TickHelper): 

1617 """ 

1618 Determine the tick locations; 

1619 

1620 Note that the same locator should not be used across multiple 

1621 `~matplotlib.axis.Axis` because the locator stores references to the Axis 

1622 data and view limits. 

1623 """ 

1624 

1625 # Some automatic tick locators can generate so many ticks they 

1626 # kill the machine when you try and render them. 

1627 # This parameter is set to cause locators to raise an error if too 

1628 # many ticks are generated. 

1629 MAXTICKS = 1000 

1630 

1631 def tick_values(self, vmin, vmax): 

1632 """ 

1633 Return the values of the located ticks given **vmin** and **vmax**. 

1634 

1635 .. note:: 

1636 To get tick locations with the vmin and vmax values defined 

1637 automatically for the associated :attr:`axis` simply call 

1638 the Locator instance:: 

1639 

1640 >>> print(type(loc)) 

1641 <type 'Locator'> 

1642 >>> print(loc()) 

1643 [1, 2, 3, 4] 

1644 

1645 """ 

1646 raise NotImplementedError('Derived must override') 

1647 

1648 def set_params(self, **kwargs): 

1649 """ 

1650 Do nothing, and raise a warning. Any locator class not supporting the 

1651 set_params() function will call this. 

1652 """ 

1653 cbook._warn_external( 

1654 "'set_params()' not defined for locator of type " + 

1655 str(type(self))) 

1656 

1657 def __call__(self): 

1658 """Return the locations of the ticks.""" 

1659 # note: some locators return data limits, other return view limits, 

1660 # hence there is no *one* interface to call self.tick_values. 

1661 raise NotImplementedError('Derived must override') 

1662 

1663 def raise_if_exceeds(self, locs): 

1664 """ 

1665 Log at WARNING level if *locs* is longer than `Locator.MAXTICKS`. 

1666 

1667 This is intended to be called immediately before returning *locs* from 

1668 ``__call__`` to inform users in case their Locator returns a huge 

1669 number of ticks, causing Matplotlib to run out of memory. 

1670 

1671 The "strange" name of this method dates back to when it would raise an 

1672 exception instead of emitting a log. 

1673 """ 

1674 if len(locs) >= self.MAXTICKS: 

1675 _log.warning( 

1676 "Locator attempting to generate %s ticks ([%s, ..., %s]), " 

1677 "which exceeds Locator.MAXTICKS (%s).", 

1678 len(locs), locs[0], locs[-1], self.MAXTICKS) 

1679 return locs 

1680 

1681 def nonsingular(self, v0, v1): 

1682 """ 

1683 Adjust a range as needed to avoid singularities. 

1684 

1685 This method gets called during autoscaling, with ``(v0, v1)`` set to 

1686 the data limits on the axes if the axes contains any data, or 

1687 ``(-inf, +inf)`` if not. 

1688 

1689 - If ``v0 == v1`` (possibly up to some floating point slop), this 

1690 method returns an expanded interval around this value. 

1691 - If ``(v0, v1) == (-inf, +inf)``, this method returns appropriate 

1692 default view limits. 

1693 - Otherwise, ``(v0, v1)`` is returned without modification. 

1694 """ 

1695 return mtransforms.nonsingular(v0, v1, expander=.05) 

1696 

1697 def view_limits(self, vmin, vmax): 

1698 """ 

1699 Select a scale for the range from vmin to vmax. 

1700 

1701 Subclasses should override this method to change locator behaviour. 

1702 """ 

1703 return mtransforms.nonsingular(vmin, vmax) 

1704 

1705 @cbook.deprecated("3.2") 

1706 def autoscale(self): 

1707 """Autoscale the view limits.""" 

1708 return self.view_limits(*self.axis.get_view_interval()) 

1709 

1710 def pan(self, numsteps): 

1711 """Pan numticks (can be positive or negative)""" 

1712 ticks = self() 

1713 numticks = len(ticks) 

1714 

1715 vmin, vmax = self.axis.get_view_interval() 

1716 vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) 

1717 if numticks > 2: 

1718 step = numsteps * abs(ticks[0] - ticks[1]) 

1719 else: 

1720 d = abs(vmax - vmin) 

1721 step = numsteps * d / 6. 

1722 

1723 vmin += step 

1724 vmax += step 

1725 self.axis.set_view_interval(vmin, vmax, ignore=True) 

1726 

1727 def zoom(self, direction): 

1728 "Zoom in/out on axis; if direction is >0 zoom in, else zoom out" 

1729 

1730 vmin, vmax = self.axis.get_view_interval() 

1731 vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) 

1732 interval = abs(vmax - vmin) 

1733 step = 0.1 * interval * direction 

1734 self.axis.set_view_interval(vmin + step, vmax - step, ignore=True) 

1735 

1736 def refresh(self): 

1737 """Refresh internal information based on current limits.""" 

1738 pass 

1739 

1740 

1741class IndexLocator(Locator): 

1742 """ 

1743 Place a tick on every multiple of some base number of points 

1744 plotted, e.g., on every 5th point. It is assumed that you are doing 

1745 index plotting; i.e., the axis is 0, len(data). This is mainly 

1746 useful for x ticks. 

1747 """ 

1748 def __init__(self, base, offset): 

1749 """Place ticks every *base* data point, starting at *offset*.""" 

1750 self._base = base 

1751 self.offset = offset 

1752 

1753 def set_params(self, base=None, offset=None): 

1754 """Set parameters within this locator""" 

1755 if base is not None: 

1756 self._base = base 

1757 if offset is not None: 

1758 self.offset = offset 

1759 

1760 def __call__(self): 

1761 """Return the locations of the ticks""" 

1762 dmin, dmax = self.axis.get_data_interval() 

1763 return self.tick_values(dmin, dmax) 

1764 

1765 def tick_values(self, vmin, vmax): 

1766 return self.raise_if_exceeds( 

1767 np.arange(vmin + self.offset, vmax + 1, self._base)) 

1768 

1769 

1770class FixedLocator(Locator): 

1771 """ 

1772 Tick locations are fixed. If nbins is not None, 

1773 the array of possible positions will be subsampled to 

1774 keep the number of ticks <= nbins +1. 

1775 The subsampling will be done so as to include the smallest 

1776 absolute value; for example, if zero is included in the 

1777 array of possibilities, then it is guaranteed to be one of 

1778 the chosen ticks. 

1779 """ 

1780 

1781 def __init__(self, locs, nbins=None): 

1782 self.locs = np.asarray(locs) 

1783 self.nbins = max(nbins, 2) if nbins is not None else None 

1784 

1785 def set_params(self, nbins=None): 

1786 """Set parameters within this locator.""" 

1787 if nbins is not None: 

1788 self.nbins = nbins 

1789 

1790 def __call__(self): 

1791 return self.tick_values(None, None) 

1792 

1793 def tick_values(self, vmin, vmax): 

1794 """" 

1795 Return the locations of the ticks. 

1796 

1797 .. note:: 

1798 

1799 Because the values are fixed, vmin and vmax are not used in this 

1800 method. 

1801 

1802 """ 

1803 if self.nbins is None: 

1804 return self.locs 

1805 step = max(int(np.ceil(len(self.locs) / self.nbins)), 1) 

1806 ticks = self.locs[::step] 

1807 for i in range(1, step): 

1808 ticks1 = self.locs[i::step] 

1809 if np.abs(ticks1).min() < np.abs(ticks).min(): 

1810 ticks = ticks1 

1811 return self.raise_if_exceeds(ticks) 

1812 

1813 

1814class NullLocator(Locator): 

1815 """ 

1816 No ticks 

1817 """ 

1818 

1819 def __call__(self): 

1820 return self.tick_values(None, None) 

1821 

1822 def tick_values(self, vmin, vmax): 

1823 """" 

1824 Return the locations of the ticks. 

1825 

1826 .. note:: 

1827 

1828 Because the values are Null, vmin and vmax are not used in this 

1829 method. 

1830 """ 

1831 return [] 

1832 

1833 

1834class LinearLocator(Locator): 

1835 """ 

1836 Determine the tick locations 

1837 

1838 The first time this function is called it will try to set the 

1839 number of ticks to make a nice tick partitioning. Thereafter the 

1840 number of ticks will be fixed so that interactive navigation will 

1841 be nice 

1842 

1843 """ 

1844 def __init__(self, numticks=None, presets=None): 

1845 """ 

1846 Use presets to set locs based on lom. A dict mapping vmin, vmax->locs 

1847 """ 

1848 self.numticks = numticks 

1849 if presets is None: 

1850 self.presets = {} 

1851 else: 

1852 self.presets = presets 

1853 

1854 @property 

1855 def numticks(self): 

1856 # Old hard-coded default. 

1857 return self._numticks if self._numticks is not None else 11 

1858 

1859 @numticks.setter 

1860 def numticks(self, numticks): 

1861 self._numticks = numticks 

1862 

1863 def set_params(self, numticks=None, presets=None): 

1864 """Set parameters within this locator.""" 

1865 if presets is not None: 

1866 self.presets = presets 

1867 if numticks is not None: 

1868 self.numticks = numticks 

1869 

1870 def __call__(self): 

1871 'Return the locations of the ticks' 

1872 vmin, vmax = self.axis.get_view_interval() 

1873 return self.tick_values(vmin, vmax) 

1874 

1875 def tick_values(self, vmin, vmax): 

1876 vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) 

1877 if vmax < vmin: 

1878 vmin, vmax = vmax, vmin 

1879 

1880 if (vmin, vmax) in self.presets: 

1881 return self.presets[(vmin, vmax)] 

1882 

1883 if self.numticks == 0: 

1884 return [] 

1885 ticklocs = np.linspace(vmin, vmax, self.numticks) 

1886 

1887 return self.raise_if_exceeds(ticklocs) 

1888 

1889 def view_limits(self, vmin, vmax): 

1890 'Try to choose the view limits intelligently' 

1891 

1892 if vmax < vmin: 

1893 vmin, vmax = vmax, vmin 

1894 

1895 if vmin == vmax: 

1896 vmin -= 1 

1897 vmax += 1 

1898 

1899 if rcParams['axes.autolimit_mode'] == 'round_numbers': 

1900 exponent, remainder = divmod( 

1901 math.log10(vmax - vmin), math.log10(max(self.numticks - 1, 1))) 

1902 exponent -= (remainder < .5) 

1903 scale = max(self.numticks - 1, 1) ** (-exponent) 

1904 vmin = math.floor(scale * vmin) / scale 

1905 vmax = math.ceil(scale * vmax) / scale 

1906 

1907 return mtransforms.nonsingular(vmin, vmax) 

1908 

1909 

1910class MultipleLocator(Locator): 

1911 """ 

1912 Set a tick on each integer multiple of a base within the view interval. 

1913 """ 

1914 

1915 def __init__(self, base=1.0): 

1916 self._edge = _Edge_integer(base, 0) 

1917 

1918 def set_params(self, base): 

1919 """Set parameters within this locator.""" 

1920 if base is not None: 

1921 self._edge = _Edge_integer(base, 0) 

1922 

1923 def __call__(self): 

1924 'Return the locations of the ticks' 

1925 vmin, vmax = self.axis.get_view_interval() 

1926 return self.tick_values(vmin, vmax) 

1927 

1928 def tick_values(self, vmin, vmax): 

1929 if vmax < vmin: 

1930 vmin, vmax = vmax, vmin 

1931 step = self._edge.step 

1932 vmin = self._edge.ge(vmin) * step 

1933 n = (vmax - vmin + 0.001 * step) // step 

1934 locs = vmin - step + np.arange(n + 3) * step 

1935 return self.raise_if_exceeds(locs) 

1936 

1937 def view_limits(self, dmin, dmax): 

1938 """ 

1939 Set the view limits to the nearest multiples of base that 

1940 contain the data. 

1941 """ 

1942 if rcParams['axes.autolimit_mode'] == 'round_numbers': 

1943 vmin = self._edge.le(dmin) * self._edge.step 

1944 vmax = self._edge.ge(dmax) * self._edge.step 

1945 if vmin == vmax: 

1946 vmin -= 1 

1947 vmax += 1 

1948 else: 

1949 vmin = dmin 

1950 vmax = dmax 

1951 

1952 return mtransforms.nonsingular(vmin, vmax) 

1953 

1954 

1955def scale_range(vmin, vmax, n=1, threshold=100): 

1956 dv = abs(vmax - vmin) # > 0 as nonsingular is called before. 

1957 meanv = (vmax + vmin) / 2 

1958 if abs(meanv) / dv < threshold: 

1959 offset = 0 

1960 else: 

1961 offset = math.copysign(10 ** (math.log10(abs(meanv)) // 1), meanv) 

1962 scale = 10 ** (math.log10(dv / n) // 1) 

1963 return scale, offset 

1964 

1965 

1966class _Edge_integer: 

1967 """ 

1968 Helper for MaxNLocator, MultipleLocator, etc. 

1969 

1970 Take floating point precision limitations into account when calculating 

1971 tick locations as integer multiples of a step. 

1972 """ 

1973 def __init__(self, step, offset): 

1974 """ 

1975 *step* is a positive floating-point interval between ticks. 

1976 *offset* is the offset subtracted from the data limits 

1977 prior to calculating tick locations. 

1978 """ 

1979 if step <= 0: 

1980 raise ValueError("'step' must be positive") 

1981 self.step = step 

1982 self._offset = abs(offset) 

1983 

1984 def closeto(self, ms, edge): 

1985 # Allow more slop when the offset is large compared to the step. 

1986 if self._offset > 0: 

1987 digits = np.log10(self._offset / self.step) 

1988 tol = max(1e-10, 10 ** (digits - 12)) 

1989 tol = min(0.4999, tol) 

1990 else: 

1991 tol = 1e-10 

1992 return abs(ms - edge) < tol 

1993 

1994 def le(self, x): 

1995 'Return the largest n: n*step <= x.' 

1996 d, m = divmod(x, self.step) 

1997 if self.closeto(m / self.step, 1): 

1998 return (d + 1) 

1999 return d 

2000 

2001 def ge(self, x): 

2002 'Return the smallest n: n*step >= x.' 

2003 d, m = divmod(x, self.step) 

2004 if self.closeto(m / self.step, 0): 

2005 return d 

2006 return (d + 1) 

2007 

2008 

2009class MaxNLocator(Locator): 

2010 """ 

2011 Select no more than N intervals at nice locations. 

2012 """ 

2013 default_params = dict(nbins=10, 

2014 steps=None, 

2015 integer=False, 

2016 symmetric=False, 

2017 prune=None, 

2018 min_n_ticks=2) 

2019 

2020 def __init__(self, *args, **kwargs): 

2021 """ 

2022 Parameters 

2023 ---------- 

2024 nbins : int or 'auto', optional, default: 10 

2025 Maximum number of intervals; one less than max number of 

2026 ticks. If the string `'auto'`, the number of bins will be 

2027 automatically determined based on the length of the axis. 

2028 

2029 steps : array-like, optional 

2030 Sequence of nice numbers starting with 1 and ending with 10; 

2031 e.g., [1, 2, 4, 5, 10], where the values are acceptable 

2032 tick multiples. i.e. for the example, 20, 40, 60 would be 

2033 an acceptable set of ticks, as would 0.4, 0.6, 0.8, because 

2034 they are multiples of 2. However, 30, 60, 90 would not 

2035 be allowed because 3 does not appear in the list of steps. 

2036 

2037 integer : bool, optional, default: False 

2038 If True, ticks will take only integer values, provided 

2039 at least `min_n_ticks` integers are found within the 

2040 view limits. 

2041 

2042 symmetric : bool, optional, default: False 

2043 If True, autoscaling will result in a range symmetric about zero. 

2044 

2045 prune : {'lower', 'upper', 'both', None}, optional, default: None 

2046 Remove edge ticks -- useful for stacked or ganged plots where 

2047 the upper tick of one axes overlaps with the lower tick of the 

2048 axes above it, primarily when :rc:`axes.autolimit_mode` is 

2049 ``'round_numbers'``. If ``prune=='lower'``, the smallest tick will 

2050 be removed. If ``prune == 'upper'``, the largest tick will be 

2051 removed. If ``prune == 'both'``, the largest and smallest ticks 

2052 will be removed. If ``prune == None``, no ticks will be removed. 

2053 

2054 min_n_ticks : int, optional, default: 2 

2055 Relax *nbins* and *integer* constraints if necessary to obtain 

2056 this minimum number of ticks. 

2057 

2058 """ 

2059 if args: 

2060 if 'nbins' in kwargs: 

2061 cbook.deprecated("3.1", 

2062 message='Calling MaxNLocator with positional ' 

2063 'and keyword parameter *nbins* is ' 

2064 'considered an error and will fail ' 

2065 'in future versions of matplotlib.') 

2066 kwargs['nbins'] = args[0] 

2067 if len(args) > 1: 

2068 raise ValueError( 

2069 "Keywords are required for all arguments except 'nbins'") 

2070 self.set_params(**{**self.default_params, **kwargs}) 

2071 

2072 @staticmethod 

2073 def _validate_steps(steps): 

2074 if not np.iterable(steps): 

2075 raise ValueError('steps argument must be an increasing sequence ' 

2076 'of numbers between 1 and 10 inclusive') 

2077 steps = np.asarray(steps) 

2078 if np.any(np.diff(steps) <= 0) or steps[-1] > 10 or steps[0] < 1: 

2079 raise ValueError('steps argument must be an increasing sequence ' 

2080 'of numbers between 1 and 10 inclusive') 

2081 if steps[0] != 1: 

2082 steps = np.hstack((1, steps)) 

2083 if steps[-1] != 10: 

2084 steps = np.hstack((steps, 10)) 

2085 return steps 

2086 

2087 @staticmethod 

2088 def _staircase(steps): 

2089 # Make an extended staircase within which the needed 

2090 # step will be found. This is probably much larger 

2091 # than necessary. 

2092 flights = (0.1 * steps[:-1], steps, 10 * steps[1]) 

2093 return np.hstack(flights) 

2094 

2095 def set_params(self, **kwargs): 

2096 """ 

2097 Set parameters for this locator. 

2098 

2099 Parameters 

2100 ---------- 

2101 nbins : int or 'auto', optional 

2102 see `.MaxNLocator` 

2103 steps : array-like, optional 

2104 see `.MaxNLocator` 

2105 integer : bool, optional 

2106 see `.MaxNLocator` 

2107 symmetric : bool, optional 

2108 see `.MaxNLocator` 

2109 prune : {'lower', 'upper', 'both', None}, optional 

2110 see `.MaxNLocator` 

2111 min_n_ticks : int, optional 

2112 see `.MaxNLocator` 

2113 """ 

2114 if 'nbins' in kwargs: 

2115 self._nbins = kwargs.pop('nbins') 

2116 if self._nbins != 'auto': 

2117 self._nbins = int(self._nbins) 

2118 if 'symmetric' in kwargs: 

2119 self._symmetric = kwargs.pop('symmetric') 

2120 if 'prune' in kwargs: 

2121 prune = kwargs.pop('prune') 

2122 cbook._check_in_list(['upper', 'lower', 'both', None], prune=prune) 

2123 self._prune = prune 

2124 if 'min_n_ticks' in kwargs: 

2125 self._min_n_ticks = max(1, kwargs.pop('min_n_ticks')) 

2126 if 'steps' in kwargs: 

2127 steps = kwargs.pop('steps') 

2128 if steps is None: 

2129 self._steps = np.array([1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10]) 

2130 else: 

2131 self._steps = self._validate_steps(steps) 

2132 self._extended_steps = self._staircase(self._steps) 

2133 if 'integer' in kwargs: 

2134 self._integer = kwargs.pop('integer') 

2135 if kwargs: 

2136 key, _ = kwargs.popitem() 

2137 cbook.warn_deprecated("3.1", 

2138 message="MaxNLocator.set_params got an " 

2139 f"unexpected parameter: {key}") 

2140 

2141 def _raw_ticks(self, vmin, vmax): 

2142 """ 

2143 Generate a list of tick locations including the range *vmin* to 

2144 *vmax*. In some applications, one or both of the end locations 

2145 will not be needed, in which case they are trimmed off 

2146 elsewhere. 

2147 """ 

2148 if self._nbins == 'auto': 

2149 if self.axis is not None: 

2150 nbins = np.clip(self.axis.get_tick_space(), 

2151 max(1, self._min_n_ticks - 1), 9) 

2152 else: 

2153 nbins = 9 

2154 else: 

2155 nbins = self._nbins 

2156 

2157 scale, offset = scale_range(vmin, vmax, nbins) 

2158 _vmin = vmin - offset 

2159 _vmax = vmax - offset 

2160 raw_step = (_vmax - _vmin) / nbins 

2161 steps = self._extended_steps * scale 

2162 if self._integer: 

2163 # For steps > 1, keep only integer values. 

2164 igood = (steps < 1) | (np.abs(steps - np.round(steps)) < 0.001) 

2165 steps = steps[igood] 

2166 

2167 istep = np.nonzero(steps >= raw_step)[0][0] 

2168 

2169 # Classic round_numbers mode may require a larger step. 

2170 if rcParams['axes.autolimit_mode'] == 'round_numbers': 

2171 for istep in range(istep, len(steps)): 

2172 step = steps[istep] 

2173 best_vmin = (_vmin // step) * step 

2174 best_vmax = best_vmin + step * nbins 

2175 if best_vmax >= _vmax: 

2176 break 

2177 

2178 # This is an upper limit; move to smaller steps if necessary. 

2179 for istep in reversed(range(istep + 1)): 

2180 step = steps[istep] 

2181 

2182 if (self._integer and 

2183 np.floor(_vmax) - np.ceil(_vmin) >= self._min_n_ticks - 1): 

2184 step = max(1, step) 

2185 best_vmin = (_vmin // step) * step 

2186 

2187 # Find tick locations spanning the vmin-vmax range, taking into 

2188 # account degradation of precision when there is a large offset. 

2189 # The edge ticks beyond vmin and/or vmax are needed for the 

2190 # "round_numbers" autolimit mode. 

2191 edge = _Edge_integer(step, offset) 

2192 low = edge.le(_vmin - best_vmin) 

2193 high = edge.ge(_vmax - best_vmin) 

2194 ticks = np.arange(low, high + 1) * step + best_vmin 

2195 # Count only the ticks that will be displayed. 

2196 nticks = ((ticks <= _vmax) & (ticks >= _vmin)).sum() 

2197 if nticks >= self._min_n_ticks: 

2198 break 

2199 return ticks + offset 

2200 

2201 def __call__(self): 

2202 vmin, vmax = self.axis.get_view_interval() 

2203 return self.tick_values(vmin, vmax) 

2204 

2205 def tick_values(self, vmin, vmax): 

2206 if self._symmetric: 

2207 vmax = max(abs(vmin), abs(vmax)) 

2208 vmin = -vmax 

2209 vmin, vmax = mtransforms.nonsingular( 

2210 vmin, vmax, expander=1e-13, tiny=1e-14) 

2211 locs = self._raw_ticks(vmin, vmax) 

2212 

2213 prune = self._prune 

2214 if prune == 'lower': 

2215 locs = locs[1:] 

2216 elif prune == 'upper': 

2217 locs = locs[:-1] 

2218 elif prune == 'both': 

2219 locs = locs[1:-1] 

2220 return self.raise_if_exceeds(locs) 

2221 

2222 def view_limits(self, dmin, dmax): 

2223 if self._symmetric: 

2224 dmax = max(abs(dmin), abs(dmax)) 

2225 dmin = -dmax 

2226 

2227 dmin, dmax = mtransforms.nonsingular( 

2228 dmin, dmax, expander=1e-12, tiny=1e-13) 

2229 

2230 if rcParams['axes.autolimit_mode'] == 'round_numbers': 

2231 return self._raw_ticks(dmin, dmax)[[0, -1]] 

2232 else: 

2233 return dmin, dmax 

2234 

2235 

2236@cbook.deprecated("3.1") 

2237def decade_down(x, base=10): 

2238 """Floor x to the nearest lower decade.""" 

2239 if x == 0.0: 

2240 return -base 

2241 lx = np.floor(np.log(x) / np.log(base)) 

2242 return base ** lx 

2243 

2244 

2245@cbook.deprecated("3.1") 

2246def decade_up(x, base=10): 

2247 """Ceil x to the nearest higher decade.""" 

2248 if x == 0.0: 

2249 return base 

2250 lx = np.ceil(np.log(x) / np.log(base)) 

2251 return base ** lx 

2252 

2253 

2254def is_decade(x, base=10, *, rtol=1e-10): 

2255 if not np.isfinite(x): 

2256 return False 

2257 if x == 0.0: 

2258 return True 

2259 lx = np.log(np.abs(x)) / np.log(base) 

2260 return is_close_to_int(lx, atol=rtol) 

2261 

2262 

2263def _decade_less_equal(x, base): 

2264 """ 

2265 Return the largest integer power of *base* that's less or equal to *x*. 

2266 

2267 If *x* is negative, the exponent will be *greater*. 

2268 """ 

2269 return (x if x == 0 else 

2270 -_decade_greater_equal(-x, base) if x < 0 else 

2271 base ** np.floor(np.log(x) / np.log(base))) 

2272 

2273 

2274def _decade_greater_equal(x, base): 

2275 """ 

2276 Return the smallest integer power of *base* that's greater or equal to *x*. 

2277 

2278 If *x* is negative, the exponent will be *smaller*. 

2279 """ 

2280 return (x if x == 0 else 

2281 -_decade_less_equal(-x, base) if x < 0 else 

2282 base ** np.ceil(np.log(x) / np.log(base))) 

2283 

2284 

2285def _decade_less(x, base): 

2286 """ 

2287 Return the largest integer power of *base* that's less than *x*. 

2288 

2289 If *x* is negative, the exponent will be *greater*. 

2290 """ 

2291 if x < 0: 

2292 return -_decade_greater(-x, base) 

2293 less = _decade_less_equal(x, base) 

2294 if less == x: 

2295 less /= base 

2296 return less 

2297 

2298 

2299def _decade_greater(x, base): 

2300 """ 

2301 Return the smallest integer power of *base* that's greater than *x*. 

2302 

2303 If *x* is negative, the exponent will be *smaller*. 

2304 """ 

2305 if x < 0: 

2306 return -_decade_less(-x, base) 

2307 greater = _decade_greater_equal(x, base) 

2308 if greater == x: 

2309 greater *= base 

2310 return greater 

2311 

2312 

2313def is_close_to_int(x, *, atol=1e-10): 

2314 return abs(x - np.round(x)) < atol 

2315 

2316 

2317class LogLocator(Locator): 

2318 """ 

2319 Determine the tick locations for log axes 

2320 """ 

2321 

2322 def __init__(self, base=10.0, subs=(1.0,), numdecs=4, numticks=None): 

2323 """ 

2324 Place ticks on the locations : subs[j] * base**i 

2325 

2326 Parameters 

2327 ---------- 

2328 subs : None, str, or sequence of float, optional, default (1.0,) 

2329 Gives the multiples of integer powers of the base at which 

2330 to place ticks. The default places ticks only at 

2331 integer powers of the base. 

2332 The permitted string values are ``'auto'`` and ``'all'``, 

2333 both of which use an algorithm based on the axis view 

2334 limits to determine whether and how to put ticks between 

2335 integer powers of the base. With ``'auto'``, ticks are 

2336 placed only between integer powers; with ``'all'``, the 

2337 integer powers are included. A value of None is 

2338 equivalent to ``'auto'``. 

2339 

2340 """ 

2341 if numticks is None: 

2342 if rcParams['_internal.classic_mode']: 

2343 numticks = 15 

2344 else: 

2345 numticks = 'auto' 

2346 self.base(base) 

2347 self.subs(subs) 

2348 self.numdecs = numdecs 

2349 self.numticks = numticks 

2350 

2351 def set_params(self, base=None, subs=None, numdecs=None, numticks=None): 

2352 """Set parameters within this locator.""" 

2353 if base is not None: 

2354 self.base(base) 

2355 if subs is not None: 

2356 self.subs(subs) 

2357 if numdecs is not None: 

2358 self.numdecs = numdecs 

2359 if numticks is not None: 

2360 self.numticks = numticks 

2361 

2362 # FIXME: these base and subs functions are contrary to our 

2363 # usual and desired API. 

2364 

2365 def base(self, base): 

2366 """Set the log base (major tick every ``base**i``, i integer).""" 

2367 self._base = float(base) 

2368 

2369 def subs(self, subs): 

2370 """ 

2371 Set the minor ticks for the log scaling every ``base**i*subs[j]``. 

2372 """ 

2373 if subs is None: # consistency with previous bad API 

2374 self._subs = 'auto' 

2375 elif isinstance(subs, str): 

2376 cbook._check_in_list(('all', 'auto'), subs=subs) 

2377 self._subs = subs 

2378 else: 

2379 try: 

2380 self._subs = np.asarray(subs, dtype=float) 

2381 except ValueError as e: 

2382 raise ValueError("subs must be None, 'all', 'auto' or " 

2383 "a sequence of floats, not " 

2384 "{}.".format(subs)) from e 

2385 if self._subs.ndim != 1: 

2386 raise ValueError("A sequence passed to subs must be " 

2387 "1-dimensional, not " 

2388 "{}-dimensional.".format(self._subs.ndim)) 

2389 

2390 def __call__(self): 

2391 'Return the locations of the ticks' 

2392 vmin, vmax = self.axis.get_view_interval() 

2393 return self.tick_values(vmin, vmax) 

2394 

2395 def tick_values(self, vmin, vmax): 

2396 if self.numticks == 'auto': 

2397 if self.axis is not None: 

2398 numticks = np.clip(self.axis.get_tick_space(), 2, 9) 

2399 else: 

2400 numticks = 9 

2401 else: 

2402 numticks = self.numticks 

2403 

2404 b = self._base 

2405 # dummy axis has no axes attribute 

2406 if hasattr(self.axis, 'axes') and self.axis.axes.name == 'polar': 

2407 vmax = math.ceil(math.log(vmax) / math.log(b)) 

2408 decades = np.arange(vmax - self.numdecs, vmax) 

2409 ticklocs = b ** decades 

2410 

2411 return ticklocs 

2412 

2413 if vmin <= 0.0: 

2414 if self.axis is not None: 

2415 vmin = self.axis.get_minpos() 

2416 

2417 if vmin <= 0.0 or not np.isfinite(vmin): 

2418 raise ValueError( 

2419 "Data has no positive values, and therefore can not be " 

2420 "log-scaled.") 

2421 

2422 _log.debug('vmin %s vmax %s', vmin, vmax) 

2423 

2424 if vmax < vmin: 

2425 vmin, vmax = vmax, vmin 

2426 log_vmin = math.log(vmin) / math.log(b) 

2427 log_vmax = math.log(vmax) / math.log(b) 

2428 

2429 numdec = math.floor(log_vmax) - math.ceil(log_vmin) 

2430 

2431 if isinstance(self._subs, str): 

2432 _first = 2.0 if self._subs == 'auto' else 1.0 

2433 if numdec > 10 or b < 3: 

2434 if self._subs == 'auto': 

2435 return np.array([]) # no minor or major ticks 

2436 else: 

2437 subs = np.array([1.0]) # major ticks 

2438 else: 

2439 subs = np.arange(_first, b) 

2440 else: 

2441 subs = self._subs 

2442 

2443 # Get decades between major ticks. 

2444 stride = (max(math.ceil(numdec / (numticks - 1)), 1) 

2445 if rcParams['_internal.classic_mode'] else 

2446 (numdec + 1) // numticks + 1) 

2447 

2448 # Does subs include anything other than 1? Essentially a hack to know 

2449 # whether we're a major or a minor locator. 

2450 have_subs = len(subs) > 1 or (len(subs) == 1 and subs[0] != 1.0) 

2451 

2452 decades = np.arange(math.floor(log_vmin) - stride, 

2453 math.ceil(log_vmax) + 2 * stride, stride) 

2454 

2455 if hasattr(self, '_transform'): 

2456 ticklocs = self._transform.inverted().transform(decades) 

2457 if have_subs: 

2458 if stride == 1: 

2459 ticklocs = np.ravel(np.outer(subs, ticklocs)) 

2460 else: 

2461 # No ticklocs if we have >1 decade between major ticks. 

2462 ticklocs = np.array([]) 

2463 else: 

2464 if have_subs: 

2465 if stride == 1: 

2466 ticklocs = np.concatenate( 

2467 [subs * decade_start for decade_start in b ** decades]) 

2468 else: 

2469 ticklocs = np.array([]) 

2470 else: 

2471 ticklocs = b ** decades 

2472 

2473 _log.debug('ticklocs %r', ticklocs) 

2474 if (len(subs) > 1 

2475 and stride == 1 

2476 and ((vmin <= ticklocs) & (ticklocs <= vmax)).sum() <= 1): 

2477 # If we're a minor locator *that expects at least two ticks per 

2478 # decade* and the major locator stride is 1 and there's no more 

2479 # than one minor tick, switch to AutoLocator. 

2480 return AutoLocator().tick_values(vmin, vmax) 

2481 else: 

2482 return self.raise_if_exceeds(ticklocs) 

2483 

2484 def view_limits(self, vmin, vmax): 

2485 'Try to choose the view limits intelligently' 

2486 b = self._base 

2487 

2488 vmin, vmax = self.nonsingular(vmin, vmax) 

2489 

2490 if self.axis.axes.name == 'polar': 

2491 vmax = math.ceil(math.log(vmax) / math.log(b)) 

2492 vmin = b ** (vmax - self.numdecs) 

2493 

2494 if rcParams['axes.autolimit_mode'] == 'round_numbers': 

2495 vmin = _decade_less_equal(vmin, self._base) 

2496 vmax = _decade_greater_equal(vmax, self._base) 

2497 

2498 return vmin, vmax 

2499 

2500 def nonsingular(self, vmin, vmax): 

2501 if vmin > vmax: 

2502 vmin, vmax = vmax, vmin 

2503 if not np.isfinite(vmin) or not np.isfinite(vmax): 

2504 vmin, vmax = 1, 10 # Initial range, no data plotted yet. 

2505 elif vmax <= 0: 

2506 cbook._warn_external( 

2507 "Data has no positive values, and therefore cannot be " 

2508 "log-scaled.") 

2509 vmin, vmax = 1, 10 

2510 else: 

2511 minpos = self.axis.get_minpos() 

2512 if not np.isfinite(minpos): 

2513 minpos = 1e-300 # This should never take effect. 

2514 if vmin <= 0: 

2515 vmin = minpos 

2516 if vmin == vmax: 

2517 vmin = _decade_less(vmin, self._base) 

2518 vmax = _decade_greater(vmax, self._base) 

2519 return vmin, vmax 

2520 

2521 

2522class SymmetricalLogLocator(Locator): 

2523 """ 

2524 Determine the tick locations for symmetric log axes 

2525 """ 

2526 

2527 def __init__(self, transform=None, subs=None, linthresh=None, base=None): 

2528 """Place ticks on the locations ``base**i*subs[j]``.""" 

2529 if transform is not None: 

2530 self._base = transform.base 

2531 self._linthresh = transform.linthresh 

2532 elif linthresh is not None and base is not None: 

2533 self._base = base 

2534 self._linthresh = linthresh 

2535 else: 

2536 raise ValueError("Either transform, or both linthresh " 

2537 "and base, must be provided.") 

2538 if subs is None: 

2539 self._subs = [1.0] 

2540 else: 

2541 self._subs = subs 

2542 self.numticks = 15 

2543 

2544 def set_params(self, subs=None, numticks=None): 

2545 """Set parameters within this locator.""" 

2546 if numticks is not None: 

2547 self.numticks = numticks 

2548 if subs is not None: 

2549 self._subs = subs 

2550 

2551 def __call__(self): 

2552 """Return the locations of the ticks.""" 

2553 # Note, these are untransformed coordinates 

2554 vmin, vmax = self.axis.get_view_interval() 

2555 return self.tick_values(vmin, vmax) 

2556 

2557 def tick_values(self, vmin, vmax): 

2558 base = self._base 

2559 linthresh = self._linthresh 

2560 

2561 if vmax < vmin: 

2562 vmin, vmax = vmax, vmin 

2563 

2564 # The domain is divided into three sections, only some of 

2565 # which may actually be present. 

2566 # 

2567 # <======== -t ==0== t ========> 

2568 # aaaaaaaaa bbbbb ccccccccc 

2569 # 

2570 # a) and c) will have ticks at integral log positions. The 

2571 # number of ticks needs to be reduced if there are more 

2572 # than self.numticks of them. 

2573 # 

2574 # b) has a tick at 0 and only 0 (we assume t is a small 

2575 # number, and the linear segment is just an implementation 

2576 # detail and not interesting.) 

2577 # 

2578 # We could also add ticks at t, but that seems to usually be 

2579 # uninteresting. 

2580 # 

2581 # "simple" mode is when the range falls entirely within (-t, 

2582 # t) -- it should just display (vmin, 0, vmax) 

2583 if -linthresh < vmin < vmax < linthresh: 

2584 # only the linear range is present 

2585 return [vmin, vmax] 

2586 

2587 # Lower log range is present 

2588 has_a = (vmin < -linthresh) 

2589 # Upper log range is present 

2590 has_c = (vmax > linthresh) 

2591 

2592 # Check if linear range is present 

2593 has_b = (has_a and vmax > -linthresh) or (has_c and vmin < linthresh) 

2594 

2595 def get_log_range(lo, hi): 

2596 lo = np.floor(np.log(lo) / np.log(base)) 

2597 hi = np.ceil(np.log(hi) / np.log(base)) 

2598 return lo, hi 

2599 

2600 # Calculate all the ranges, so we can determine striding 

2601 a_lo, a_hi = (0, 0) 

2602 if has_a: 

2603 a_upper_lim = min(-linthresh, vmax) 

2604 a_lo, a_hi = get_log_range(np.abs(a_upper_lim), np.abs(vmin) + 1) 

2605 

2606 c_lo, c_hi = (0, 0) 

2607 if has_c: 

2608 c_lower_lim = max(linthresh, vmin) 

2609 c_lo, c_hi = get_log_range(c_lower_lim, vmax + 1) 

2610 

2611 # Calculate the total number of integer exponents in a and c ranges 

2612 total_ticks = (a_hi - a_lo) + (c_hi - c_lo) 

2613 if has_b: 

2614 total_ticks += 1 

2615 stride = max(total_ticks // (self.numticks - 1), 1) 

2616 

2617 decades = [] 

2618 if has_a: 

2619 decades.extend(-1 * (base ** (np.arange(a_lo, a_hi, 

2620 stride)[::-1]))) 

2621 

2622 if has_b: 

2623 decades.append(0.0) 

2624 

2625 if has_c: 

2626 decades.extend(base ** (np.arange(c_lo, c_hi, stride))) 

2627 

2628 # Add the subticks if requested 

2629 if self._subs is None: 

2630 subs = np.arange(2.0, base) 

2631 else: 

2632 subs = np.asarray(self._subs) 

2633 

2634 if len(subs) > 1 or subs[0] != 1.0: 

2635 ticklocs = [] 

2636 for decade in decades: 

2637 if decade == 0: 

2638 ticklocs.append(decade) 

2639 else: 

2640 ticklocs.extend(subs * decade) 

2641 else: 

2642 ticklocs = decades 

2643 

2644 return self.raise_if_exceeds(np.array(ticklocs)) 

2645 

2646 def view_limits(self, vmin, vmax): 

2647 'Try to choose the view limits intelligently' 

2648 b = self._base 

2649 if vmax < vmin: 

2650 vmin, vmax = vmax, vmin 

2651 

2652 if rcParams['axes.autolimit_mode'] == 'round_numbers': 

2653 vmin = _decade_less_equal(vmin, b) 

2654 vmax = _decade_greater_equal(vmax, b) 

2655 if vmin == vmax: 

2656 vmin = _decade_less(vmin, b) 

2657 vmax = _decade_greater(vmax, b) 

2658 

2659 result = mtransforms.nonsingular(vmin, vmax) 

2660 return result 

2661 

2662 

2663class LogitLocator(MaxNLocator): 

2664 """ 

2665 Determine the tick locations for logit axes 

2666 """ 

2667 

2668 def __init__(self, minor=False, *, nbins="auto"): 

2669 """ 

2670 Place ticks on the logit locations 

2671 

2672 Parameters 

2673 ---------- 

2674 nbins : int or 'auto', optional 

2675 Number of ticks. Only used if minor is False. 

2676 minor : bool, default: False 

2677 Indicate if this locator is for minor ticks or not. 

2678 """ 

2679 

2680 self._minor = minor 

2681 MaxNLocator.__init__(self, nbins=nbins, steps=[1, 2, 5, 10]) 

2682 

2683 def set_params(self, minor=None, **kwargs): 

2684 """Set parameters within this locator.""" 

2685 if minor is not None: 

2686 self._minor = minor 

2687 MaxNLocator.set_params(self, **kwargs) 

2688 

2689 @property 

2690 def minor(self): 

2691 return self._minor 

2692 

2693 @minor.setter 

2694 def minor(self, value): 

2695 self.set_params(minor=value) 

2696 

2697 def tick_values(self, vmin, vmax): 

2698 # dummy axis has no axes attribute 

2699 if hasattr(self.axis, "axes") and self.axis.axes.name == "polar": 

2700 raise NotImplementedError("Polar axis cannot be logit scaled yet") 

2701 

2702 if self._nbins == "auto": 

2703 if self.axis is not None: 

2704 nbins = self.axis.get_tick_space() 

2705 if nbins < 2: 

2706 nbins = 2 

2707 else: 

2708 nbins = 9 

2709 else: 

2710 nbins = self._nbins 

2711 

2712 # We define ideal ticks with their index: 

2713 # linscale: ... 1e-3 1e-2 1e-1 1/2 1-1e-1 1-1e-2 1-1e-3 ... 

2714 # b-scale : ... -3 -2 -1 0 1 2 3 ... 

2715 def ideal_ticks(x): 

2716 return 10 ** x if x < 0 else 1 - (10 ** (-x)) if x > 0 else 1 / 2 

2717 

2718 vmin, vmax = self.nonsingular(vmin, vmax) 

2719 binf = int( 

2720 np.floor(np.log10(vmin)) 

2721 if vmin < 0.5 

2722 else 0 

2723 if vmin < 0.9 

2724 else -np.ceil(np.log10(1 - vmin)) 

2725 ) 

2726 bsup = int( 

2727 np.ceil(np.log10(vmax)) 

2728 if vmax <= 0.5 

2729 else 1 

2730 if vmax <= 0.9 

2731 else -np.floor(np.log10(1 - vmax)) 

2732 ) 

2733 numideal = bsup - binf - 1 

2734 if numideal >= 2: 

2735 # have 2 or more wanted ideal ticks, so use them as major ticks 

2736 if numideal > nbins: 

2737 # to many ideal ticks, subsampling ideals for major ticks, and 

2738 # take others for minor ticks 

2739 subsampling_factor = math.ceil(numideal / nbins) 

2740 if self._minor: 

2741 ticklocs = [ 

2742 ideal_ticks(b) 

2743 for b in range(binf, bsup + 1) 

2744 if (b % subsampling_factor) != 0 

2745 ] 

2746 else: 

2747 ticklocs = [ 

2748 ideal_ticks(b) 

2749 for b in range(binf, bsup + 1) 

2750 if (b % subsampling_factor) == 0 

2751 ] 

2752 return self.raise_if_exceeds(np.array(ticklocs)) 

2753 if self._minor: 

2754 ticklocs = [] 

2755 for b in range(binf, bsup): 

2756 if b < -1: 

2757 ticklocs.extend(np.arange(2, 10) * 10 ** b) 

2758 elif b == -1: 

2759 ticklocs.extend(np.arange(2, 5) / 10) 

2760 elif b == 0: 

2761 ticklocs.extend(np.arange(6, 9) / 10) 

2762 else: 

2763 ticklocs.extend( 

2764 1 - np.arange(2, 10)[::-1] * 10 ** (-b - 1) 

2765 ) 

2766 return self.raise_if_exceeds(np.array(ticklocs)) 

2767 ticklocs = [ideal_ticks(b) for b in range(binf, bsup + 1)] 

2768 return self.raise_if_exceeds(np.array(ticklocs)) 

2769 # the scale is zoomed so same ticks as linear scale can be used 

2770 if self._minor: 

2771 return [] 

2772 return MaxNLocator.tick_values(self, vmin, vmax) 

2773 

2774 def nonsingular(self, vmin, vmax): 

2775 standard_minpos = 1e-7 

2776 initial_range = (standard_minpos, 1 - standard_minpos) 

2777 if vmin > vmax: 

2778 vmin, vmax = vmax, vmin 

2779 if not np.isfinite(vmin) or not np.isfinite(vmax): 

2780 vmin, vmax = initial_range # Initial range, no data plotted yet. 

2781 elif vmax <= 0 or vmin >= 1: 

2782 # vmax <= 0 occurs when all values are negative 

2783 # vmin >= 1 occurs when all values are greater than one 

2784 cbook._warn_external( 

2785 "Data has no values between 0 and 1, and therefore cannot be " 

2786 "logit-scaled." 

2787 ) 

2788 vmin, vmax = initial_range 

2789 else: 

2790 minpos = ( 

2791 self.axis.get_minpos() 

2792 if self.axis is not None 

2793 else standard_minpos 

2794 ) 

2795 if not np.isfinite(minpos): 

2796 minpos = standard_minpos # This should never take effect. 

2797 if vmin <= 0: 

2798 vmin = minpos 

2799 # NOTE: for vmax, we should query a property similar to get_minpos, 

2800 # but related to the maximal, less-than-one data point. 

2801 # Unfortunately, Bbox._minpos is defined very deep in the BBox and 

2802 # updated with data, so for now we use 1 - minpos as a substitute. 

2803 if vmax >= 1: 

2804 vmax = 1 - minpos 

2805 if vmin == vmax: 

2806 vmin, vmax = 0.1 * vmin, 1 - 0.1 * vmin 

2807 

2808 return vmin, vmax 

2809 

2810 

2811class AutoLocator(MaxNLocator): 

2812 """ 

2813 Dynamically find major tick positions. This is actually a subclass 

2814 of `~matplotlib.ticker.MaxNLocator`, with parameters *nbins = 'auto'* 

2815 and *steps = [1, 2, 2.5, 5, 10]*. 

2816 """ 

2817 def __init__(self): 

2818 """ 

2819 To know the values of the non-public parameters, please have a 

2820 look to the defaults of `~matplotlib.ticker.MaxNLocator`. 

2821 """ 

2822 if rcParams['_internal.classic_mode']: 

2823 nbins = 9 

2824 steps = [1, 2, 5, 10] 

2825 else: 

2826 nbins = 'auto' 

2827 steps = [1, 2, 2.5, 5, 10] 

2828 MaxNLocator.__init__(self, nbins=nbins, steps=steps) 

2829 

2830 

2831class AutoMinorLocator(Locator): 

2832 """ 

2833 Dynamically find minor tick positions based on the positions of 

2834 major ticks. The scale must be linear with major ticks evenly spaced. 

2835 """ 

2836 def __init__(self, n=None): 

2837 """ 

2838 *n* is the number of subdivisions of the interval between 

2839 major ticks; e.g., n=2 will place a single minor tick midway 

2840 between major ticks. 

2841 

2842 If *n* is omitted or None, it will be set to 5 or 4. 

2843 """ 

2844 self.ndivs = n 

2845 

2846 def __call__(self): 

2847 'Return the locations of the ticks' 

2848 if self.axis.get_scale() == 'log': 

2849 cbook._warn_external('AutoMinorLocator does not work with ' 

2850 'logarithmic scale') 

2851 return [] 

2852 

2853 majorlocs = self.axis.get_majorticklocs() 

2854 try: 

2855 majorstep = majorlocs[1] - majorlocs[0] 

2856 except IndexError: 

2857 # Need at least two major ticks to find minor tick locations 

2858 # TODO: Figure out a way to still be able to display minor 

2859 # ticks without two major ticks visible. For now, just display 

2860 # no ticks at all. 

2861 return [] 

2862 

2863 if self.ndivs is None: 

2864 

2865 majorstep_no_exponent = 10 ** (np.log10(majorstep) % 1) 

2866 

2867 if np.isclose(majorstep_no_exponent, [1.0, 2.5, 5.0, 10.0]).any(): 

2868 ndivs = 5 

2869 else: 

2870 ndivs = 4 

2871 else: 

2872 ndivs = self.ndivs 

2873 

2874 minorstep = majorstep / ndivs 

2875 

2876 vmin, vmax = self.axis.get_view_interval() 

2877 if vmin > vmax: 

2878 vmin, vmax = vmax, vmin 

2879 

2880 t0 = majorlocs[0] 

2881 tmin = ((vmin - t0) // minorstep + 1) * minorstep 

2882 tmax = ((vmax - t0) // minorstep + 1) * minorstep 

2883 locs = np.arange(tmin, tmax, minorstep) + t0 

2884 

2885 return self.raise_if_exceeds(locs) 

2886 

2887 def tick_values(self, vmin, vmax): 

2888 raise NotImplementedError('Cannot get tick locations for a ' 

2889 '%s type.' % type(self)) 

2890 

2891 

2892class OldAutoLocator(Locator): 

2893 """ 

2894 On autoscale this class picks the best MultipleLocator to set the 

2895 view limits and the tick locs. 

2896 

2897 """ 

2898 def __init__(self): 

2899 self._locator = LinearLocator() 

2900 

2901 def __call__(self): 

2902 'Return the locations of the ticks' 

2903 self.refresh() 

2904 return self.raise_if_exceeds(self._locator()) 

2905 

2906 def tick_values(self, vmin, vmax): 

2907 raise NotImplementedError('Cannot get tick locations for a ' 

2908 '%s type.' % type(self)) 

2909 

2910 def refresh(self): 

2911 # docstring inherited 

2912 vmin, vmax = self.axis.get_view_interval() 

2913 vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05) 

2914 d = abs(vmax - vmin) 

2915 self._locator = self.get_locator(d) 

2916 

2917 def view_limits(self, vmin, vmax): 

2918 'Try to choose the view limits intelligently' 

2919 

2920 d = abs(vmax - vmin) 

2921 self._locator = self.get_locator(d) 

2922 return self._locator.view_limits(vmin, vmax) 

2923 

2924 def get_locator(self, d): 

2925 """Pick the best locator based on a distance *d*.""" 

2926 d = abs(d) 

2927 if d <= 0: 

2928 locator = MultipleLocator(0.2) 

2929 else: 

2930 

2931 try: 

2932 ld = math.log10(d) 

2933 except OverflowError: 

2934 raise RuntimeError('AutoLocator illegal data interval range') 

2935 

2936 fld = math.floor(ld) 

2937 base = 10 ** fld 

2938 

2939 #if ld==fld: base = 10**(fld-1) 

2940 #else: base = 10**fld 

2941 

2942 if d >= 5 * base: 

2943 ticksize = base 

2944 elif d >= 2 * base: 

2945 ticksize = base / 2.0 

2946 else: 

2947 ticksize = base / 5.0 

2948 locator = MultipleLocator(ticksize) 

2949 

2950 return locator