Coverage for /usr/lib/python3/dist-packages/matplotlib/ticker.py: 43%

1246 statements  

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

1""" 

2Tick locating and formatting 

3============================ 

4 

5This module contains classes for configuring tick locating and formatting. 

6Generic tick locators and formatters are provided, as well as domain specific 

7custom ones. 

8 

9Although the locators know nothing about major or minor ticks, they are used 

10by the Axis class to support major and minor tick locating and formatting. 

11 

12Tick locating 

13------------- 

14 

15The Locator class is the base class for all tick locators. The locators 

16handle autoscaling of the view limits based on the data limits, and the 

17choosing of tick locations. A useful semi-automatic tick locator is 

18`MultipleLocator`. It is initialized with a base, e.g., 10, and it picks 

19axis limits and ticks that are multiples of that base. 

20 

21The Locator subclasses defined here are: 

22 

23======================= ======================================================= 

24`AutoLocator` `MaxNLocator` with simple defaults. This is the default 

25 tick locator for most plotting. 

26`MaxNLocator` Finds up to a max number of intervals with ticks at 

27 nice locations. 

28`LinearLocator` Space ticks evenly from min to max. 

29`LogLocator` Space ticks logarithmically from min to max. 

30`MultipleLocator` Ticks and range are a multiple of base; either integer 

31 or float. 

32`FixedLocator` Tick locations are fixed. 

33`IndexLocator` Locator for index plots (e.g., where 

34 ``x = range(len(y))``). 

35`NullLocator` No ticks. 

36`SymmetricalLogLocator` Locator for use with the symlog norm; works like 

37 `LogLocator` for the part outside of the threshold and 

38 adds 0 if inside the limits. 

39`AsinhLocator` Locator for use with the asinh norm, attempting to 

40 space ticks approximately uniformly. 

41`LogitLocator` Locator for logit scaling. 

42`AutoMinorLocator` Locator for minor ticks when the axis is linear and the 

43 major ticks are uniformly spaced. Subdivides the major 

44 tick interval into a specified number of minor 

45 intervals, defaulting to 4 or 5 depending on the major 

46 interval. 

47======================= ======================================================= 

48 

49There are a number of locators specialized for date locations - see 

50the :mod:`.dates` module. 

51 

52You can define your own locator by deriving from Locator. You must 

53override the ``__call__`` method, which returns a sequence of locations, 

54and you will probably want to override the autoscale method to set the 

55view limits from the data limits. 

56 

57If you want to override the default locator, use one of the above or a custom 

58locator and pass it to the x- or y-axis instance. The relevant methods are:: 

59 

60 ax.xaxis.set_major_locator(xmajor_locator) 

61 ax.xaxis.set_minor_locator(xminor_locator) 

62 ax.yaxis.set_major_locator(ymajor_locator) 

63 ax.yaxis.set_minor_locator(yminor_locator) 

64 

65The default minor locator is `NullLocator`, i.e., no minor ticks on by default. 

66 

67.. note:: 

68 `Locator` instances should not be used with more than one 

69 `~matplotlib.axis.Axis` or `~matplotlib.axes.Axes`. So instead of:: 

70 

71 locator = MultipleLocator(5) 

72 ax.xaxis.set_major_locator(locator) 

73 ax2.xaxis.set_major_locator(locator) 

74 

75 do the following instead:: 

76 

77 ax.xaxis.set_major_locator(MultipleLocator(5)) 

78 ax2.xaxis.set_major_locator(MultipleLocator(5)) 

79 

80Tick formatting 

81--------------- 

82 

83Tick formatting is controlled by classes derived from Formatter. The formatter 

84operates on a single tick value and returns a string to the axis. 

85 

86========================= ===================================================== 

87`NullFormatter` No labels on the ticks. 

88`FixedFormatter` Set the strings manually for the labels. 

89`FuncFormatter` User defined function sets the labels. 

90`StrMethodFormatter` Use string `format` method. 

91`FormatStrFormatter` Use an old-style sprintf format string. 

92`ScalarFormatter` Default formatter for scalars: autopick the format 

93 string. 

94`LogFormatter` Formatter for log axes. 

95`LogFormatterExponent` Format values for log axis using 

96 ``exponent = log_base(value)``. 

97`LogFormatterMathtext` Format values for log axis using 

98 ``exponent = log_base(value)`` using Math text. 

99`LogFormatterSciNotation` Format values for log axis using scientific notation. 

100`LogitFormatter` Probability formatter. 

101`EngFormatter` Format labels in engineering notation. 

102`PercentFormatter` Format labels as a percentage. 

103========================= ===================================================== 

104 

105You can derive your own formatter from the Formatter base class by 

106simply overriding the ``__call__`` method. The formatter class has 

107access to the axis view and data limits. 

108 

109To control the major and minor tick label formats, use one of the 

110following methods:: 

111 

112 ax.xaxis.set_major_formatter(xmajor_formatter) 

113 ax.xaxis.set_minor_formatter(xminor_formatter) 

114 ax.yaxis.set_major_formatter(ymajor_formatter) 

115 ax.yaxis.set_minor_formatter(yminor_formatter) 

116 

117In addition to a `.Formatter` instance, `~.Axis.set_major_formatter` and 

118`~.Axis.set_minor_formatter` also accept a ``str`` or function. ``str`` input 

119will be internally replaced with an autogenerated `.StrMethodFormatter` with 

120the input ``str``. For function input, a `.FuncFormatter` with the input 

121function will be generated and used. 

122 

123See :doc:`/gallery/ticks/major_minor_demo` for an example of setting major 

124and minor ticks. See the :mod:`matplotlib.dates` module for more information 

125and examples of using date locators and formatters. 

126""" 

127 

128import itertools 

129import logging 

130import locale 

131import math 

132from numbers import Integral 

133 

134import numpy as np 

135 

136import matplotlib as mpl 

137from matplotlib import _api, cbook 

138from matplotlib import transforms as mtransforms 

139 

140_log = logging.getLogger(__name__) 

141 

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

143 'NullFormatter', 'FuncFormatter', 'FormatStrFormatter', 

144 'StrMethodFormatter', 'ScalarFormatter', 'LogFormatter', 

145 'LogFormatterExponent', 'LogFormatterMathtext', 

146 'LogFormatterSciNotation', 

147 'LogitFormatter', 'EngFormatter', 'PercentFormatter', 

148 'Locator', 'IndexLocator', 'FixedLocator', 'NullLocator', 

149 'LinearLocator', 'LogLocator', 'AutoLocator', 

150 'MultipleLocator', 'MaxNLocator', 'AutoMinorLocator', 

151 'SymmetricalLogLocator', 'AsinhLocator', 'LogitLocator') 

152 

153 

154class _DummyAxis: 

155 __name__ = "dummy" 

156 

157 # Once the deprecation elapses, replace dataLim and viewLim by plain 

158 # _view_interval and _data_interval private tuples. 

159 dataLim = _api.deprecate_privatize_attribute( 

160 "3.6", alternative="get_data_interval() and set_data_interval()") 

161 viewLim = _api.deprecate_privatize_attribute( 

162 "3.6", alternative="get_view_interval() and set_view_interval()") 

163 

164 def __init__(self, minpos=0): 

165 self._dataLim = mtransforms.Bbox.unit() 

166 self._viewLim = mtransforms.Bbox.unit() 

167 self._minpos = minpos 

168 

169 def get_view_interval(self): 

170 return self._viewLim.intervalx 

171 

172 def set_view_interval(self, vmin, vmax): 

173 self._viewLim.intervalx = vmin, vmax 

174 

175 def get_minpos(self): 

176 return self._minpos 

177 

178 def get_data_interval(self): 

179 return self._dataLim.intervalx 

180 

181 def set_data_interval(self, vmin, vmax): 

182 self._dataLim.intervalx = vmin, vmax 

183 

184 def get_tick_space(self): 

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

186 return 9 

187 

188 

189class TickHelper: 

190 axis = None 

191 

192 def set_axis(self, axis): 

193 self.axis = axis 

194 

195 def create_dummy_axis(self, **kwargs): 

196 if self.axis is None: 

197 self.axis = _DummyAxis(**kwargs) 

198 

199 @_api.deprecated("3.5", alternative="`.Axis.set_view_interval`") 

200 def set_view_interval(self, vmin, vmax): 

201 self.axis.set_view_interval(vmin, vmax) 

202 

203 @_api.deprecated("3.5", alternative="`.Axis.set_data_interval`") 

204 def set_data_interval(self, vmin, vmax): 

205 self.axis.set_data_interval(vmin, vmax) 

206 

207 @_api.deprecated( 

208 "3.5", 

209 alternative="`.Axis.set_view_interval` and `.Axis.set_data_interval`") 

210 def set_bounds(self, vmin, vmax): 

211 self.set_view_interval(vmin, vmax) 

212 self.set_data_interval(vmin, vmax) 

213 

214 

215class Formatter(TickHelper): 

216 """ 

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

218 """ 

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

220 # individual ones 

221 locs = [] 

222 

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

224 """ 

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

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

227 """ 

228 raise NotImplementedError('Derived must override') 

229 

230 def format_ticks(self, values): 

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

232 self.set_locs(values) 

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

234 

235 def format_data(self, value): 

236 """ 

237 Return the full string representation of the value with the 

238 position unspecified. 

239 """ 

240 return self.__call__(value) 

241 

242 def format_data_short(self, value): 

243 """ 

244 Return a short string version of the tick value. 

245 

246 Defaults to the position-independent long value. 

247 """ 

248 return self.format_data(value) 

249 

250 def get_offset(self): 

251 return '' 

252 

253 def set_locs(self, locs): 

254 """ 

255 Set the locations of the ticks. 

256 

257 This method is called before computing the tick labels because some 

258 formatters need to know all tick locations to do so. 

259 """ 

260 self.locs = locs 

261 

262 @staticmethod 

263 def fix_minus(s): 

264 """ 

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

266 Unicode symbol (U+2212) for typographical correctness. This is a 

267 helper method to perform such a replacement when it is enabled via 

268 :rc:`axes.unicode_minus`. 

269 """ 

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

271 if mpl.rcParams['axes.unicode_minus'] 

272 else s) 

273 

274 def _set_locator(self, locator): 

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

276 pass 

277 

278 

279class NullFormatter(Formatter): 

280 """Always return the empty string.""" 

281 

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

283 # docstring inherited 

284 return '' 

285 

286 

287class FixedFormatter(Formatter): 

288 """ 

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

290 

291 .. note:: 

292 `.FixedFormatter` should only be used together with `.FixedLocator`. 

293 Otherwise, the labels may end up in unexpected positions. 

294 """ 

295 

296 def __init__(self, seq): 

297 """Set the sequence *seq* of strings that will be used for labels.""" 

298 self.seq = seq 

299 self.offset_string = '' 

300 

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

302 """ 

303 Return the label that matches the position, regardless of the value. 

304 

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

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

307 strings that this object was initialized with. 

308 """ 

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

310 return '' 

311 else: 

312 return self.seq[pos] 

313 

314 def get_offset(self): 

315 return self.offset_string 

316 

317 def set_offset_string(self, ofs): 

318 self.offset_string = ofs 

319 

320 

321class FuncFormatter(Formatter): 

322 """ 

323 Use a user-defined function for formatting. 

324 

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

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

327 tick label. 

328 """ 

329 

330 def __init__(self, func): 

331 self.func = func 

332 self.offset_string = "" 

333 

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

335 """ 

336 Return the value of the user defined function. 

337 

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

339 """ 

340 return self.func(x, pos) 

341 

342 def get_offset(self): 

343 return self.offset_string 

344 

345 def set_offset_string(self, ofs): 

346 self.offset_string = ofs 

347 

348 

349class FormatStrFormatter(Formatter): 

350 """ 

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

352 

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

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

355 

356 Negative numeric values will use a dash, not a Unicode minus; use mathtext 

357 to get a Unicode minus by wrapping the format specifier with $ (e.g. 

358 "$%g$"). 

359 """ 

360 def __init__(self, fmt): 

361 self.fmt = fmt 

362 

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

364 """ 

365 Return the formatted label string. 

366 

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

368 """ 

369 return self.fmt % x 

370 

371 

372class StrMethodFormatter(Formatter): 

373 """ 

374 Use a new-style format string (as used by `str.format`) to format the tick. 

375 

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

377 for the tick position must be labeled *pos*. 

378 """ 

379 def __init__(self, fmt): 

380 self.fmt = fmt 

381 

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

383 """ 

384 Return the formatted label string. 

385 

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

387 with those exact names. 

388 """ 

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

390 

391 

392class ScalarFormatter(Formatter): 

393 """ 

394 Format tick values as a number. 

395 

396 Parameters 

397 ---------- 

398 useOffset : bool or float, default: :rc:`axes.formatter.useoffset` 

399 Whether to use offset notation. See `.set_useOffset`. 

400 useMathText : bool, default: :rc:`axes.formatter.use_mathtext` 

401 Whether to use fancy math formatting. See `.set_useMathText`. 

402 useLocale : bool, default: :rc:`axes.formatter.use_locale`. 

403 Whether to use locale settings for decimal sign and positive sign. 

404 See `.set_useLocale`. 

405 

406 Notes 

407 ----- 

408 In addition to the parameters above, the formatting of scientific vs. 

409 floating point representation can be configured via `.set_scientific` 

410 and `.set_powerlimits`). 

411 

412 **Offset notation and scientific notation** 

413 

414 Offset notation and scientific notation look quite similar at first sight. 

415 Both split some information from the formatted tick values and display it 

416 at the end of the axis. 

417 

418 - The scientific notation splits up the order of magnitude, i.e. a 

419 multiplicative scaling factor, e.g. ``1e6``. 

420 

421 - The offset notation separates an additive constant, e.g. ``+1e6``. The 

422 offset notation label is always prefixed with a ``+`` or ``-`` sign 

423 and is thus distinguishable from the order of magnitude label. 

424 

425 The following plot with x limits ``1_000_000`` to ``1_000_010`` illustrates 

426 the different formatting. Note the labels at the right edge of the x axis. 

427 

428 .. plot:: 

429 

430 lim = (1_000_000, 1_000_010) 

431 

432 fig, (ax1, ax2, ax3) = plt.subplots(3, 1, gridspec_kw={'hspace': 2}) 

433 ax1.set(title='offset_notation', xlim=lim) 

434 ax2.set(title='scientific notation', xlim=lim) 

435 ax2.xaxis.get_major_formatter().set_useOffset(False) 

436 ax3.set(title='floating point notation', xlim=lim) 

437 ax3.xaxis.get_major_formatter().set_useOffset(False) 

438 ax3.xaxis.get_major_formatter().set_scientific(False) 

439 

440 """ 

441 

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

443 if useOffset is None: 

444 useOffset = mpl.rcParams['axes.formatter.useoffset'] 

445 self._offset_threshold = \ 

446 mpl.rcParams['axes.formatter.offset_threshold'] 

447 self.set_useOffset(useOffset) 

448 self._usetex = mpl.rcParams['text.usetex'] 

449 self.set_useMathText(useMathText) 

450 self.orderOfMagnitude = 0 

451 self.format = '' 

452 self._scientific = True 

453 self._powerlimits = mpl.rcParams['axes.formatter.limits'] 

454 self.set_useLocale(useLocale) 

455 

456 def get_useOffset(self): 

457 """ 

458 Return whether automatic mode for offset notation is active. 

459 

460 This returns True if ``set_useOffset(True)``; it returns False if an 

461 explicit offset was set, e.g. ``set_useOffset(1000)``. 

462 

463 See Also 

464 -------- 

465 ScalarFormatter.set_useOffset 

466 """ 

467 return self._useOffset 

468 

469 def set_useOffset(self, val): 

470 """ 

471 Set whether to use offset notation. 

472 

473 When formatting a set numbers whose value is large compared to their 

474 range, the formatter can separate an additive constant. This can 

475 shorten the formatted numbers so that they are less likely to overlap 

476 when drawn on an axis. 

477 

478 Parameters 

479 ---------- 

480 val : bool or float 

481 - If False, do not use offset notation. 

482 - If True (=automatic mode), use offset notation if it can make 

483 the residual numbers significantly shorter. The exact behavior 

484 is controlled by :rc:`axes.formatter.offset_threshold`. 

485 - If a number, force an offset of the given value. 

486 

487 Examples 

488 -------- 

489 With active offset notation, the values 

490 

491 ``100_000, 100_002, 100_004, 100_006, 100_008`` 

492 

493 will be formatted as ``0, 2, 4, 6, 8`` plus an offset ``+1e5``, which 

494 is written to the edge of the axis. 

495 """ 

496 if val in [True, False]: 

497 self.offset = 0 

498 self._useOffset = val 

499 else: 

500 self._useOffset = False 

501 self.offset = val 

502 

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

504 

505 def get_useLocale(self): 

506 """ 

507 Return whether locale settings are used for formatting. 

508 

509 See Also 

510 -------- 

511 ScalarFormatter.set_useLocale 

512 """ 

513 return self._useLocale 

514 

515 def set_useLocale(self, val): 

516 """ 

517 Set whether to use locale settings for decimal sign and positive sign. 

518 

519 Parameters 

520 ---------- 

521 val : bool or None 

522 *None* resets to :rc:`axes.formatter.use_locale`. 

523 """ 

524 if val is None: 

525 self._useLocale = mpl.rcParams['axes.formatter.use_locale'] 

526 else: 

527 self._useLocale = val 

528 

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

530 

531 def _format_maybe_minus_and_locale(self, fmt, arg): 

532 """ 

533 Format *arg* with *fmt*, applying Unicode minus and locale if desired. 

534 """ 

535 return self.fix_minus(locale.format_string(fmt, (arg,), True) 

536 if self._useLocale else fmt % arg) 

537 

538 def get_useMathText(self): 

539 """ 

540 Return whether to use fancy math formatting. 

541 

542 See Also 

543 -------- 

544 ScalarFormatter.set_useMathText 

545 """ 

546 return self._useMathText 

547 

548 def set_useMathText(self, val): 

549 r""" 

550 Set whether to use fancy math formatting. 

551 

552 If active, scientific notation is formatted as :math:`1.2 \times 10^3`. 

553 

554 Parameters 

555 ---------- 

556 val : bool or None 

557 *None* resets to :rc:`axes.formatter.use_mathtext`. 

558 """ 

559 if val is None: 

560 self._useMathText = mpl.rcParams['axes.formatter.use_mathtext'] 

561 if self._useMathText is False: 

562 try: 

563 from matplotlib import font_manager 

564 ufont = font_manager.findfont( 

565 font_manager.FontProperties( 

566 mpl.rcParams["font.family"] 

567 ), 

568 fallback_to_default=False, 

569 ) 

570 except ValueError: 

571 ufont = None 

572 

573 if ufont == str(cbook._get_data_path("fonts/ttf/cmr10.ttf")): 

574 _api.warn_external( 

575 "cmr10 font should ideally be used with " 

576 "mathtext, set axes.formatter.use_mathtext to True" 

577 ) 

578 else: 

579 self._useMathText = val 

580 

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

582 

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

584 """ 

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

586 """ 

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

588 return '' 

589 else: 

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

591 if abs(xp) < 1e-8: 

592 xp = 0 

593 return self._format_maybe_minus_and_locale(self.format, xp) 

594 

595 def set_scientific(self, b): 

596 """ 

597 Turn scientific notation on or off. 

598 

599 See Also 

600 -------- 

601 ScalarFormatter.set_powerlimits 

602 """ 

603 self._scientific = bool(b) 

604 

605 def set_powerlimits(self, lims): 

606 r""" 

607 Set size thresholds for scientific notation. 

608 

609 Parameters 

610 ---------- 

611 lims : (int, int) 

612 A tuple *(min_exp, max_exp)* containing the powers of 10 that 

613 determine the switchover threshold. For a number representable as 

614 :math:`a \times 10^\mathrm{exp}` with :math:`1 <= |a| < 10`, 

615 scientific notation will be used if ``exp <= min_exp`` or 

616 ``exp >= max_exp``. 

617 

618 The default limits are controlled by :rc:`axes.formatter.limits`. 

619 

620 In particular numbers with *exp* equal to the thresholds are 

621 written in scientific notation. 

622 

623 Typically, *min_exp* will be negative and *max_exp* will be 

624 positive. 

625 

626 For example, ``formatter.set_powerlimits((-3, 4))`` will provide 

627 the following formatting: 

628 :math:`1 \times 10^{-3}, 9.9 \times 10^{-3}, 0.01,` 

629 :math:`9999, 1 \times 10^4`. 

630 

631 See Also 

632 -------- 

633 ScalarFormatter.set_scientific 

634 """ 

635 if len(lims) != 2: 

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

637 self._powerlimits = lims 

638 

639 def format_data_short(self, value): 

640 # docstring inherited 

641 if isinstance(value, np.ma.MaskedArray) and value.mask: 

642 return "" 

643 if isinstance(value, Integral): 

644 fmt = "%d" 

645 else: 

646 if getattr(self.axis, "__name__", "") in ["xaxis", "yaxis"]: 

647 if self.axis.__name__ == "xaxis": 

648 axis_trf = self.axis.axes.get_xaxis_transform() 

649 axis_inv_trf = axis_trf.inverted() 

650 screen_xy = axis_trf.transform((value, 0)) 

651 neighbor_values = axis_inv_trf.transform( 

652 screen_xy + [[-1, 0], [+1, 0]])[:, 0] 

653 else: # yaxis: 

654 axis_trf = self.axis.axes.get_yaxis_transform() 

655 axis_inv_trf = axis_trf.inverted() 

656 screen_xy = axis_trf.transform((0, value)) 

657 neighbor_values = axis_inv_trf.transform( 

658 screen_xy + [[0, -1], [0, +1]])[:, 1] 

659 delta = abs(neighbor_values - value).max() 

660 else: 

661 # Rough approximation: no more than 1e4 divisions. 

662 a, b = self.axis.get_view_interval() 

663 delta = (b - a) / 1e4 

664 fmt = "%-#.{}g".format(cbook._g_sig_digits(value, delta)) 

665 return self._format_maybe_minus_and_locale(fmt, value) 

666 

667 def format_data(self, value): 

668 # docstring inherited 

669 e = math.floor(math.log10(abs(value))) 

670 s = round(value / 10**e, 10) 

671 exponent = self._format_maybe_minus_and_locale("%d", e) 

672 significand = self._format_maybe_minus_and_locale( 

673 "%d" if s % 1 == 0 else "%1.10g", s) 

674 if e == 0: 

675 return significand 

676 elif self._useMathText or self._usetex: 

677 exponent = "10^{%s}" % exponent 

678 return (exponent if s == 1 # reformat 1x10^y as 10^y 

679 else rf"{significand} \times {exponent}") 

680 else: 

681 return f"{significand}e{exponent}" 

682 

683 def get_offset(self): 

684 """ 

685 Return scientific notation, plus offset. 

686 """ 

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

688 return '' 

689 s = '' 

690 if self.orderOfMagnitude or self.offset: 

691 offsetStr = '' 

692 sciNotStr = '' 

693 if self.offset: 

694 offsetStr = self.format_data(self.offset) 

695 if self.offset > 0: 

696 offsetStr = '+' + offsetStr 

697 if self.orderOfMagnitude: 

698 if self._usetex or self._useMathText: 

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

700 else: 

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

702 if self._useMathText or self._usetex: 

703 if sciNotStr != '': 

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

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

706 else: 

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

708 

709 return self.fix_minus(s) 

710 

711 def set_locs(self, locs): 

712 # docstring inherited 

713 self.locs = locs 

714 if len(self.locs) > 0: 

715 if self._useOffset: 

716 self._compute_offset() 

717 self._set_order_of_magnitude() 

718 self._set_format() 

719 

720 def _compute_offset(self): 

721 locs = self.locs 

722 # Restrict to visible ticks. 

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

724 locs = np.asarray(locs) 

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

726 if not len(locs): 

727 self.offset = 0 

728 return 

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

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

731 # the same sign. 

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

733 self.offset = 0 

734 return 

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

736 # zero so we work on absolute values). 

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

738 sign = math.copysign(1, lmin) 

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

740 # equal up to that precision? 

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

742 # accuracy issues. 

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

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

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

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

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

748 # (relative to the span). 

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

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

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

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

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

754 n = self._offset_threshold - 1 

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

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

757 else 0) 

758 

759 def _set_order_of_magnitude(self): 

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

761 # if using a numerical offset, find the exponent after applying the 

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

763 if not self._scientific: 

764 self.orderOfMagnitude = 0 

765 return 

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

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

768 self.orderOfMagnitude = self._powerlimits[0] 

769 return 

770 # restrict to visible ticks 

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

772 locs = np.asarray(self.locs) 

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

774 locs = np.abs(locs) 

775 if not len(locs): 

776 self.orderOfMagnitude = 0 

777 return 

778 if self.offset: 

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

780 else: 

781 val = locs.max() 

782 if val == 0: 

783 oom = 0 

784 else: 

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

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

787 self.orderOfMagnitude = oom 

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

789 self.orderOfMagnitude = oom 

790 else: 

791 self.orderOfMagnitude = 0 

792 

793 def _set_format(self): 

794 # set the format string to format all the ticklabels 

795 if len(self.locs) < 2: 

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

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

798 else: 

799 _locs = self.locs 

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

801 loc_range = np.ptp(locs) 

802 # Curvilinear coordinates can yield two identical points. 

803 if loc_range == 0: 

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

805 # Both points might be zero. 

806 if loc_range == 0: 

807 loc_range = 1 

808 if len(self.locs) < 2: 

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

810 locs = locs[:-2] 

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

812 # first estimate: 

813 sigfigs = max(0, 3 - loc_range_oom) 

814 # refined estimate: 

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

816 while sigfigs >= 0: 

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

818 sigfigs -= 1 

819 else: 

820 break 

821 sigfigs += 1 

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

823 if self._usetex or self._useMathText: 

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

825 

826 

827class LogFormatter(Formatter): 

828 """ 

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

830 

831 It may be instantiated directly, or subclassed. 

832 

833 Parameters 

834 ---------- 

835 base : float, default: 10. 

836 Base of the logarithm used in all calculations. 

837 

838 labelOnlyBase : bool, default: False 

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

840 This is normally True for major ticks and False for 

841 minor ticks. 

842 

843 minor_thresholds : (subset, all), default: (1, 0.4) 

844 If labelOnlyBase is False, these two numbers control 

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

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

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

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

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

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

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

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

853 be labeled. 

854 

855 linthresh : None or float, default: None 

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

857 parameter must be supplied here. 

858 

859 Notes 

860 ----- 

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

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

863 

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

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

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

867 at intermediate locations. For this situation, disable the 

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

869 so that all ticks will be labeled. 

870 

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

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

873 "classic" style. 

874 

875 Examples 

876 -------- 

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

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

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

880 

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

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

883 """ 

884 

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

886 minor_thresholds=None, 

887 linthresh=None): 

888 

889 self.set_base(base) 

890 self.set_label_minor(labelOnlyBase) 

891 if minor_thresholds is None: 

892 if mpl.rcParams['_internal.classic_mode']: 

893 minor_thresholds = (0, 0) 

894 else: 

895 minor_thresholds = (1, 0.4) 

896 self.minor_thresholds = minor_thresholds 

897 self._sublabels = None 

898 self._linthresh = linthresh 

899 

900 @_api.deprecated("3.6", alternative='set_base()') 

901 def base(self, base): 

902 """ 

903 Change the *base* for labeling. 

904 

905 .. warning:: 

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

907 """ 

908 self.set_base(base) 

909 

910 def set_base(self, base): 

911 """ 

912 Change the *base* for labeling. 

913 

914 .. warning:: 

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

916 """ 

917 self._base = float(base) 

918 

919 @_api.deprecated("3.6", alternative='set_label_minor()') 

920 def label_minor(self, labelOnlyBase): 

921 """ 

922 Switch minor tick labeling on or off. 

923 

924 Parameters 

925 ---------- 

926 labelOnlyBase : bool 

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

928 """ 

929 self.set_label_minor(labelOnlyBase) 

930 

931 def set_label_minor(self, labelOnlyBase): 

932 """ 

933 Switch minor tick labeling on or off. 

934 

935 Parameters 

936 ---------- 

937 labelOnlyBase : bool 

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

939 """ 

940 self.labelOnlyBase = labelOnlyBase 

941 

942 def set_locs(self, locs=None): 

943 """ 

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

945 

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

947 """ 

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

949 self._sublabels = None 

950 return 

951 

952 # Handle symlog case: 

953 linthresh = self._linthresh 

954 if linthresh is None: 

955 try: 

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

957 except AttributeError: 

958 pass 

959 

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

961 if vmin > vmax: 

962 vmin, vmax = vmax, vmin 

963 

964 if linthresh is None and vmin <= 0: 

965 # It's probably a colorbar with 

966 # a format kwarg setting a LogFormatter in the manner 

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

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

969 return 

970 

971 b = self._base 

972 if linthresh is not None: # symlog 

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

974 # axis 

975 numdec = 0 

976 if vmin < -linthresh: 

977 rhs = min(vmax, -linthresh) 

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

979 if vmax > linthresh: 

980 lhs = max(vmin, linthresh) 

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

982 else: 

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

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

985 numdec = abs(vmax - vmin) 

986 

987 if numdec > self.minor_thresholds[0]: 

988 # Label only bases 

989 self._sublabels = {1} 

990 elif numdec > self.minor_thresholds[1]: 

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

992 # include base powers in case the locations include 

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

994 c = np.geomspace(1, b, int(b)//2 + 1) 

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

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

997 else: 

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

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

1000 

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

1002 if x > 10000: 

1003 s = '%1.0e' % x 

1004 elif x < 1: 

1005 s = '%1.0e' % x 

1006 else: 

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

1008 return s 

1009 

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

1011 # docstring inherited 

1012 if x == 0.0: # Symlog 

1013 return '0' 

1014 

1015 x = abs(x) 

1016 b = self._base 

1017 # only label the decades 

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

1019 is_x_decade = _is_close_to_int(fx) 

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

1021 coeff = round(b ** (fx - exponent)) 

1022 

1023 if self.labelOnlyBase and not is_x_decade: 

1024 return '' 

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

1026 return '' 

1027 

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

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

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

1031 return self.fix_minus(s) 

1032 

1033 def format_data(self, value): 

1034 with cbook._setattr_cm(self, labelOnlyBase=False): 

1035 return cbook.strip_math(self.__call__(value)) 

1036 

1037 def format_data_short(self, value): 

1038 # docstring inherited 

1039 return '%-12g' % value 

1040 

1041 def _pprint_val(self, x, d): 

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

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

1044 return '%d' % x 

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

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

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

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

1049 '%1.1e') 

1050 s = fmt % x 

1051 tup = s.split('e') 

1052 if len(tup) == 2: 

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

1054 exponent = int(tup[1]) 

1055 if exponent: 

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

1057 else: 

1058 s = mantissa 

1059 else: 

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

1061 return s 

1062 

1063 

1064class LogFormatterExponent(LogFormatter): 

1065 """ 

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

1067 """ 

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

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

1070 if abs(fx) > 10000: 

1071 s = '%1.0g' % fx 

1072 elif abs(fx) < 1: 

1073 s = '%1.0g' % fx 

1074 else: 

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

1076 s = self._pprint_val(fx, fd) 

1077 return s 

1078 

1079 

1080class LogFormatterMathtext(LogFormatter): 

1081 """ 

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

1083 """ 

1084 

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

1086 """Return string for non-decade locations.""" 

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

1088 

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

1090 # docstring inherited 

1091 usetex = mpl.rcParams['text.usetex'] 

1092 min_exp = mpl.rcParams['axes.formatter.min_exponent'] 

1093 

1094 if x == 0: # Symlog 

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

1096 

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

1098 x = abs(x) 

1099 b = self._base 

1100 

1101 # only label the decades 

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

1103 is_x_decade = _is_close_to_int(fx) 

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

1105 coeff = round(b ** (fx - exponent)) 

1106 if is_x_decade: 

1107 fx = round(fx) 

1108 

1109 if self.labelOnlyBase and not is_x_decade: 

1110 return '' 

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

1112 return '' 

1113 

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

1115 if b % 1 == 0.0: 

1116 base = '%d' % b 

1117 else: 

1118 base = '%s' % b 

1119 

1120 if abs(fx) < min_exp: 

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

1122 elif not is_x_decade: 

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

1124 else: 

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

1126 

1127 

1128class LogFormatterSciNotation(LogFormatterMathtext): 

1129 """ 

1130 Format values following scientific notation in a logarithmic axis. 

1131 """ 

1132 

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

1134 """Return string for non-decade locations.""" 

1135 b = float(base) 

1136 exponent = math.floor(fx) 

1137 coeff = b ** (fx - exponent) 

1138 if _is_close_to_int(coeff): 

1139 coeff = round(coeff) 

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

1141 % (sign_string, coeff, base, exponent) 

1142 

1143 

1144class LogitFormatter(Formatter): 

1145 """ 

1146 Probability formatter (using Math text). 

1147 """ 

1148 

1149 def __init__( 

1150 self, 

1151 *, 

1152 use_overline=False, 

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

1154 minor=False, 

1155 minor_threshold=25, 

1156 minor_number=6, 

1157 ): 

1158 r""" 

1159 Parameters 

1160 ---------- 

1161 use_overline : bool, default: False 

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

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

1164 

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

1166 The string used to represent 1/2. 

1167 

1168 minor : bool, default: False 

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

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

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

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

1173 

1174 minor_threshold : int, default: 25 

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

1176 parameter have no effect if minor is False. 

1177 

1178 minor_number : int, default: 6 

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

1180 below the threshold. 

1181 """ 

1182 self._use_overline = use_overline 

1183 self._one_half = one_half 

1184 self._minor = minor 

1185 self._labelled = set() 

1186 self._minor_threshold = minor_threshold 

1187 self._minor_number = minor_number 

1188 

1189 def use_overline(self, use_overline): 

1190 r""" 

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

1192 

1193 Parameters 

1194 ---------- 

1195 use_overline : bool, default: False 

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

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

1198 """ 

1199 self._use_overline = use_overline 

1200 

1201 def set_one_half(self, one_half): 

1202 r""" 

1203 Set the way one half is displayed. 

1204 

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

1206 The string used to represent 1/2. 

1207 """ 

1208 self._one_half = one_half 

1209 

1210 def set_minor_threshold(self, minor_threshold): 

1211 """ 

1212 Set the threshold for labelling minors ticks. 

1213 

1214 Parameters 

1215 ---------- 

1216 minor_threshold : int 

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

1218 parameter have no effect if minor is False. 

1219 """ 

1220 self._minor_threshold = minor_threshold 

1221 

1222 def set_minor_number(self, minor_number): 

1223 """ 

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

1225 labelled. 

1226 

1227 Parameters 

1228 ---------- 

1229 minor_number : int 

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

1231 below the threshold. 

1232 """ 

1233 self._minor_number = minor_number 

1234 

1235 def set_locs(self, locs): 

1236 self.locs = np.array(locs) 

1237 self._labelled.clear() 

1238 

1239 if not self._minor: 

1240 return None 

1241 if all( 

1242 _is_decade(x, rtol=1e-7) 

1243 or _is_decade(1 - x, rtol=1e-7) 

1244 or (_is_close_to_int(2 * x) and 

1245 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(math.log10(x)) 

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

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

1320 exponent = round(math.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 # docstring inherited 

1332 # Thresholds chosen to use scientific notation iff exponent <= -2. 

1333 if value < 0.1: 

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

1335 if value < 0.9: 

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

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

1338 

1339 

1340class EngFormatter(Formatter): 

1341 """ 

1342 Format axis values using engineering prefixes to represent powers 

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

1344 """ 

1345 

1346 # The SI engineering prefixes 

1347 ENG_PREFIXES = { 

1348 -24: "y", 

1349 -21: "z", 

1350 -18: "a", 

1351 -15: "f", 

1352 -12: "p", 

1353 -9: "n", 

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

1355 -3: "m", 

1356 0: "", 

1357 3: "k", 

1358 6: "M", 

1359 9: "G", 

1360 12: "T", 

1361 15: "P", 

1362 18: "E", 

1363 21: "Z", 

1364 24: "Y" 

1365 } 

1366 

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

1368 useMathText=None): 

1369 r""" 

1370 Parameters 

1371 ---------- 

1372 unit : str, default: "" 

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

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

1375 

1376 places : int, default: None 

1377 Precision with which to display the number, specified in 

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

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

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

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

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

1383 

1384 sep : str, default: " " 

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

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

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

1388 other useful options may be: 

1389 

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

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

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

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

1394 

1395 usetex : bool, default: :rc:`text.usetex` 

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

1397 numbers in the formatter. 

1398 

1399 useMathText : bool, default: :rc:`axes.formatter.use_mathtext` 

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

1401 the formatter. 

1402 """ 

1403 self.unit = unit 

1404 self.places = places 

1405 self.sep = sep 

1406 self.set_usetex(usetex) 

1407 self.set_useMathText(useMathText) 

1408 

1409 def get_usetex(self): 

1410 return self._usetex 

1411 

1412 def set_usetex(self, val): 

1413 if val is None: 

1414 self._usetex = mpl.rcParams['text.usetex'] 

1415 else: 

1416 self._usetex = val 

1417 

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

1419 

1420 def get_useMathText(self): 

1421 return self._useMathText 

1422 

1423 def set_useMathText(self, val): 

1424 if val is None: 

1425 self._useMathText = mpl.rcParams['axes.formatter.use_mathtext'] 

1426 else: 

1427 self._useMathText = val 

1428 

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

1430 

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

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

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

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

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

1436 return self.fix_minus(s) 

1437 

1438 def format_eng(self, num): 

1439 """ 

1440 Format a number in engineering notation, appending a letter 

1441 representing the power of 1000 of the original number. 

1442 Some examples: 

1443 

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

1445 '0' 

1446 

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

1448 '1.0 M' 

1449 

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

1451 '-1.00 \N{MICRO SIGN}' 

1452 """ 

1453 sign = 1 

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

1455 

1456 if num < 0: 

1457 sign = -1 

1458 num = -num 

1459 

1460 if num != 0: 

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

1462 else: 

1463 pow10 = 0 

1464 # Force num to zero, to avoid inconsistencies like 

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

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

1467 num = 0.0 

1468 

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

1470 

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

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

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

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

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

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

1477 mant /= 1000 

1478 pow10 += 3 

1479 

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

1481 if self._usetex or self._useMathText: 

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

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

1484 else: 

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

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

1487 

1488 return formatted 

1489 

1490 

1491class PercentFormatter(Formatter): 

1492 """ 

1493 Format numbers as a percentage. 

1494 

1495 Parameters 

1496 ---------- 

1497 xmax : float 

1498 Determines how the number is converted into a percentage. 

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

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

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

1502 situation is where *xmax* is 1.0. 

1503 

1504 decimals : None or int 

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

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

1507 

1508 symbol : str or None 

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

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

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

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

1513 

1514 is_latex : bool 

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

1516 """ 

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

1518 self.xmax = xmax + 0.0 

1519 self.decimals = decimals 

1520 self._symbol = symbol 

1521 self._is_latex = is_latex 

1522 

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

1524 """Format the tick as a percentage with the appropriate scaling.""" 

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

1526 display_range = abs(ax_max - ax_min) 

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

1528 

1529 def format_pct(self, x, display_range): 

1530 """ 

1531 Format the number as a percentage number with the correct 

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

1533 

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

1535 decimal point is set based on the *display_range* of the axis 

1536 as follows: 

1537 

1538 +---------------+----------+------------------------+ 

1539 | display_range | decimals | sample | 

1540 +---------------+----------+------------------------+ 

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

1542 +---------------+----------+------------------------+ 

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

1544 +---------------+----------+------------------------+ 

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

1546 +---------------+----------+------------------------+ 

1547 | ... | ... | ... | 

1548 +---------------+----------+------------------------+ 

1549 

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

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

1552 are percentages displayed on a reasonable scale. 

1553 """ 

1554 x = self.convert_to_pct(x) 

1555 if self.decimals is None: 

1556 # conversion works because display_range is a difference 

1557 scaled_range = self.convert_to_pct(display_range) 

1558 if scaled_range <= 0: 

1559 decimals = 0 

1560 else: 

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

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

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

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

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

1566 if decimals > 5: 

1567 decimals = 5 

1568 elif decimals < 0: 

1569 decimals = 0 

1570 else: 

1571 decimals = self.decimals 

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

1573 

1574 return s + self.symbol 

1575 

1576 def convert_to_pct(self, x): 

1577 return 100.0 * (x / self.xmax) 

1578 

1579 @property 

1580 def symbol(self): 

1581 r""" 

1582 The configured percent symbol as a string. 

1583 

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

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

1586 automatically escaped in the string. 

1587 """ 

1588 symbol = self._symbol 

1589 if not symbol: 

1590 symbol = '' 

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

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

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

1594 # it keeps getting added in 

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

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

1597 return symbol 

1598 

1599 @symbol.setter 

1600 def symbol(self, symbol): 

1601 self._symbol = symbol 

1602 

1603 

1604class Locator(TickHelper): 

1605 """ 

1606 Determine the tick locations; 

1607 

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

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

1610 data and view limits. 

1611 """ 

1612 

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

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

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

1616 # many ticks are generated. 

1617 MAXTICKS = 1000 

1618 

1619 def tick_values(self, vmin, vmax): 

1620 """ 

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

1622 

1623 .. note:: 

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

1625 automatically for the associated ``axis`` simply call 

1626 the Locator instance:: 

1627 

1628 >>> print(type(loc)) 

1629 <type 'Locator'> 

1630 >>> print(loc()) 

1631 [1, 2, 3, 4] 

1632 

1633 """ 

1634 raise NotImplementedError('Derived must override') 

1635 

1636 def set_params(self, **kwargs): 

1637 """ 

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

1639 set_params() function will call this. 

1640 """ 

1641 _api.warn_external( 

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

1643 str(type(self))) 

1644 

1645 def __call__(self): 

1646 """Return the locations of the ticks.""" 

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

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

1649 raise NotImplementedError('Derived must override') 

1650 

1651 def raise_if_exceeds(self, locs): 

1652 """ 

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

1654 

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

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

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

1658 

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

1660 exception instead of emitting a log. 

1661 """ 

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

1663 _log.warning( 

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

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

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

1667 return locs 

1668 

1669 def nonsingular(self, v0, v1): 

1670 """ 

1671 Adjust a range as needed to avoid singularities. 

1672 

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

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

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

1676 

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

1678 method returns an expanded interval around this value. 

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

1680 default view limits. 

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

1682 """ 

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

1684 

1685 def view_limits(self, vmin, vmax): 

1686 """ 

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

1688 

1689 Subclasses should override this method to change locator behaviour. 

1690 """ 

1691 return mtransforms.nonsingular(vmin, vmax) 

1692 

1693 

1694class IndexLocator(Locator): 

1695 """ 

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

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

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

1699 useful for x ticks. 

1700 """ 

1701 def __init__(self, base, offset): 

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

1703 self._base = base 

1704 self.offset = offset 

1705 

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

1707 """Set parameters within this locator""" 

1708 if base is not None: 

1709 self._base = base 

1710 if offset is not None: 

1711 self.offset = offset 

1712 

1713 def __call__(self): 

1714 """Return the locations of the ticks""" 

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

1716 return self.tick_values(dmin, dmax) 

1717 

1718 def tick_values(self, vmin, vmax): 

1719 return self.raise_if_exceeds( 

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

1721 

1722 

1723class FixedLocator(Locator): 

1724 """ 

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

1726 the array of possible positions will be subsampled to 

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

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

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

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

1731 the chosen ticks. 

1732 """ 

1733 

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

1735 self.locs = np.asarray(locs) 

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

1737 

1738 def set_params(self, nbins=None): 

1739 """Set parameters within this locator.""" 

1740 if nbins is not None: 

1741 self.nbins = nbins 

1742 

1743 def __call__(self): 

1744 return self.tick_values(None, None) 

1745 

1746 def tick_values(self, vmin, vmax): 

1747 """ 

1748 Return the locations of the ticks. 

1749 

1750 .. note:: 

1751 

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

1753 method. 

1754 

1755 """ 

1756 if self.nbins is None: 

1757 return self.locs 

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

1759 ticks = self.locs[::step] 

1760 for i in range(1, step): 

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

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

1763 ticks = ticks1 

1764 return self.raise_if_exceeds(ticks) 

1765 

1766 

1767class NullLocator(Locator): 

1768 """ 

1769 No ticks 

1770 """ 

1771 

1772 def __call__(self): 

1773 return self.tick_values(None, None) 

1774 

1775 def tick_values(self, vmin, vmax): 

1776 """ 

1777 Return the locations of the ticks. 

1778 

1779 .. note:: 

1780 

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

1782 method. 

1783 """ 

1784 return [] 

1785 

1786 

1787class LinearLocator(Locator): 

1788 """ 

1789 Determine the tick locations 

1790 

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

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

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

1794 be nice 

1795 

1796 """ 

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

1798 """ 

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

1800 """ 

1801 self.numticks = numticks 

1802 if presets is None: 

1803 self.presets = {} 

1804 else: 

1805 self.presets = presets 

1806 

1807 @property 

1808 def numticks(self): 

1809 # Old hard-coded default. 

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

1811 

1812 @numticks.setter 

1813 def numticks(self, numticks): 

1814 self._numticks = numticks 

1815 

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

1817 """Set parameters within this locator.""" 

1818 if presets is not None: 

1819 self.presets = presets 

1820 if numticks is not None: 

1821 self.numticks = numticks 

1822 

1823 def __call__(self): 

1824 """Return the locations of the ticks.""" 

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

1826 return self.tick_values(vmin, vmax) 

1827 

1828 def tick_values(self, vmin, vmax): 

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

1830 if vmax < vmin: 

1831 vmin, vmax = vmax, vmin 

1832 

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

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

1835 

1836 if self.numticks == 0: 

1837 return [] 

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

1839 

1840 return self.raise_if_exceeds(ticklocs) 

1841 

1842 def view_limits(self, vmin, vmax): 

1843 """Try to choose the view limits intelligently.""" 

1844 

1845 if vmax < vmin: 

1846 vmin, vmax = vmax, vmin 

1847 

1848 if vmin == vmax: 

1849 vmin -= 1 

1850 vmax += 1 

1851 

1852 if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers': 

1853 exponent, remainder = divmod( 

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

1855 exponent -= (remainder < .5) 

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

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

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

1859 

1860 return mtransforms.nonsingular(vmin, vmax) 

1861 

1862 

1863class MultipleLocator(Locator): 

1864 """ 

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

1866 """ 

1867 

1868 def __init__(self, base=1.0): 

1869 self._edge = _Edge_integer(base, 0) 

1870 

1871 def set_params(self, base): 

1872 """Set parameters within this locator.""" 

1873 if base is not None: 

1874 self._edge = _Edge_integer(base, 0) 

1875 

1876 def __call__(self): 

1877 """Return the locations of the ticks.""" 

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

1879 return self.tick_values(vmin, vmax) 

1880 

1881 def tick_values(self, vmin, vmax): 

1882 if vmax < vmin: 

1883 vmin, vmax = vmax, vmin 

1884 step = self._edge.step 

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

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

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

1888 return self.raise_if_exceeds(locs) 

1889 

1890 def view_limits(self, dmin, dmax): 

1891 """ 

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

1893 contain the data. 

1894 """ 

1895 if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers': 

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

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

1898 if vmin == vmax: 

1899 vmin -= 1 

1900 vmax += 1 

1901 else: 

1902 vmin = dmin 

1903 vmax = dmax 

1904 

1905 return mtransforms.nonsingular(vmin, vmax) 

1906 

1907 

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

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

1910 meanv = (vmax + vmin) / 2 

1911 if abs(meanv) / dv < threshold: 

1912 offset = 0 

1913 else: 

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

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

1916 return scale, offset 

1917 

1918 

1919class _Edge_integer: 

1920 """ 

1921 Helper for MaxNLocator, MultipleLocator, etc. 

1922 

1923 Take floating point precision limitations into account when calculating 

1924 tick locations as integer multiples of a step. 

1925 """ 

1926 def __init__(self, step, offset): 

1927 """ 

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

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

1930 prior to calculating tick locations. 

1931 """ 

1932 if step <= 0: 

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

1934 self.step = step 

1935 self._offset = abs(offset) 

1936 

1937 def closeto(self, ms, edge): 

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

1939 if self._offset > 0: 

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

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

1942 tol = min(0.4999, tol) 

1943 else: 

1944 tol = 1e-10 

1945 return abs(ms - edge) < tol 

1946 

1947 def le(self, x): 

1948 """Return the largest n: n*step <= x.""" 

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

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

1951 return d + 1 

1952 return d 

1953 

1954 def ge(self, x): 

1955 """Return the smallest n: n*step >= x.""" 

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

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

1958 return d 

1959 return d + 1 

1960 

1961 

1962class MaxNLocator(Locator): 

1963 """ 

1964 Find nice tick locations with no more than N being within the view limits. 

1965 Locations beyond the limits are added to support autoscaling. 

1966 """ 

1967 default_params = dict(nbins=10, 

1968 steps=None, 

1969 integer=False, 

1970 symmetric=False, 

1971 prune=None, 

1972 min_n_ticks=2) 

1973 

1974 def __init__(self, nbins=None, **kwargs): 

1975 """ 

1976 Parameters 

1977 ---------- 

1978 nbins : int or 'auto', default: 10 

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

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

1981 automatically determined based on the length of the axis. 

1982 

1983 steps : array-like, optional 

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

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

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

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

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

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

1990 

1991 integer : bool, default: False 

1992 If True, ticks will take only integer values, provided at least 

1993 *min_n_ticks* integers are found within the view limits. 

1994 

1995 symmetric : bool, default: False 

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

1997 

1998 prune : {'lower', 'upper', 'both', None}, default: None 

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

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

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

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

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

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

2005 will be removed. If *prune* is *None*, no ticks will be removed. 

2006 

2007 min_n_ticks : int, default: 2 

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

2009 this minimum number of ticks. 

2010 """ 

2011 if nbins is not None: 

2012 kwargs['nbins'] = nbins 

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

2014 

2015 @staticmethod 

2016 def _validate_steps(steps): 

2017 if not np.iterable(steps): 

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

2019 'of numbers between 1 and 10 inclusive') 

2020 steps = np.asarray(steps) 

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

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

2023 'of numbers between 1 and 10 inclusive') 

2024 if steps[0] != 1: 

2025 steps = np.concatenate([[1], steps]) 

2026 if steps[-1] != 10: 

2027 steps = np.concatenate([steps, [10]]) 

2028 return steps 

2029 

2030 @staticmethod 

2031 def _staircase(steps): 

2032 # Make an extended staircase within which the needed step will be 

2033 # found. This is probably much larger than necessary. 

2034 return np.concatenate([0.1 * steps[:-1], steps, [10 * steps[1]]]) 

2035 

2036 def set_params(self, **kwargs): 

2037 """ 

2038 Set parameters for this locator. 

2039 

2040 Parameters 

2041 ---------- 

2042 nbins : int or 'auto', optional 

2043 see `.MaxNLocator` 

2044 steps : array-like, optional 

2045 see `.MaxNLocator` 

2046 integer : bool, optional 

2047 see `.MaxNLocator` 

2048 symmetric : bool, optional 

2049 see `.MaxNLocator` 

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

2051 see `.MaxNLocator` 

2052 min_n_ticks : int, optional 

2053 see `.MaxNLocator` 

2054 """ 

2055 if 'nbins' in kwargs: 

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

2057 if self._nbins != 'auto': 

2058 self._nbins = int(self._nbins) 

2059 if 'symmetric' in kwargs: 

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

2061 if 'prune' in kwargs: 

2062 prune = kwargs.pop('prune') 

2063 _api.check_in_list(['upper', 'lower', 'both', None], prune=prune) 

2064 self._prune = prune 

2065 if 'min_n_ticks' in kwargs: 

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

2067 if 'steps' in kwargs: 

2068 steps = kwargs.pop('steps') 

2069 if steps is None: 

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

2071 else: 

2072 self._steps = self._validate_steps(steps) 

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

2074 if 'integer' in kwargs: 

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

2076 if kwargs: 

2077 key, _ = kwargs.popitem() 

2078 raise TypeError( 

2079 f"set_params() got an unexpected keyword argument '{key}'") 

2080 

2081 def _raw_ticks(self, vmin, vmax): 

2082 """ 

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

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

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

2086 elsewhere. 

2087 """ 

2088 if self._nbins == 'auto': 

2089 if self.axis is not None: 

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

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

2092 else: 

2093 nbins = 9 

2094 else: 

2095 nbins = self._nbins 

2096 

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

2098 _vmin = vmin - offset 

2099 _vmax = vmax - offset 

2100 raw_step = (_vmax - _vmin) / nbins 

2101 steps = self._extended_steps * scale 

2102 if self._integer: 

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

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

2105 steps = steps[igood] 

2106 

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

2108 

2109 # Classic round_numbers mode may require a larger step. 

2110 if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers': 

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

2112 step = steps[istep] 

2113 best_vmin = (_vmin // step) * step 

2114 best_vmax = best_vmin + step * nbins 

2115 if best_vmax >= _vmax: 

2116 break 

2117 

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

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

2120 step = steps[istep] 

2121 

2122 if (self._integer and 

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

2124 step = max(1, step) 

2125 best_vmin = (_vmin // step) * step 

2126 

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

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

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

2130 # "round_numbers" autolimit mode. 

2131 edge = _Edge_integer(step, offset) 

2132 low = edge.le(_vmin - best_vmin) 

2133 high = edge.ge(_vmax - best_vmin) 

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

2135 # Count only the ticks that will be displayed. 

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

2137 if nticks >= self._min_n_ticks: 

2138 break 

2139 return ticks + offset 

2140 

2141 def __call__(self): 

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

2143 return self.tick_values(vmin, vmax) 

2144 

2145 def tick_values(self, vmin, vmax): 

2146 if self._symmetric: 

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

2148 vmin = -vmax 

2149 vmin, vmax = mtransforms.nonsingular( 

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

2151 locs = self._raw_ticks(vmin, vmax) 

2152 

2153 prune = self._prune 

2154 if prune == 'lower': 

2155 locs = locs[1:] 

2156 elif prune == 'upper': 

2157 locs = locs[:-1] 

2158 elif prune == 'both': 

2159 locs = locs[1:-1] 

2160 return self.raise_if_exceeds(locs) 

2161 

2162 def view_limits(self, dmin, dmax): 

2163 if self._symmetric: 

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

2165 dmin = -dmax 

2166 

2167 dmin, dmax = mtransforms.nonsingular( 

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

2169 

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

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

2172 else: 

2173 return dmin, dmax 

2174 

2175 

2176@_api.deprecated("3.6") 

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

2178 if not np.isfinite(x): 

2179 return False 

2180 if x == 0.0: 

2181 return True 

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

2183 return is_close_to_int(lx, atol=rtol) 

2184 

2185 

2186def _is_decade(x, *, base=10, rtol=None): 

2187 """Return True if *x* is an integer power of *base*.""" 

2188 if not np.isfinite(x): 

2189 return False 

2190 if x == 0.0: 

2191 return True 

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

2193 if rtol is None: 

2194 return np.isclose(lx, np.round(lx)) 

2195 else: 

2196 return np.isclose(lx, np.round(lx), rtol=rtol) 

2197 

2198 

2199def _decade_less_equal(x, base): 

2200 """ 

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

2202 

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

2204 """ 

2205 return (x if x == 0 else 

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

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

2208 

2209 

2210def _decade_greater_equal(x, base): 

2211 """ 

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

2213 

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

2215 """ 

2216 return (x if x == 0 else 

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

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

2219 

2220 

2221def _decade_less(x, base): 

2222 """ 

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

2224 

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

2226 """ 

2227 if x < 0: 

2228 return -_decade_greater(-x, base) 

2229 less = _decade_less_equal(x, base) 

2230 if less == x: 

2231 less /= base 

2232 return less 

2233 

2234 

2235def _decade_greater(x, base): 

2236 """ 

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

2238 

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

2240 """ 

2241 if x < 0: 

2242 return -_decade_less(-x, base) 

2243 greater = _decade_greater_equal(x, base) 

2244 if greater == x: 

2245 greater *= base 

2246 return greater 

2247 

2248 

2249@_api.deprecated("3.6") 

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

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

2252 

2253 

2254def _is_close_to_int(x): 

2255 return math.isclose(x, round(x)) 

2256 

2257 

2258class LogLocator(Locator): 

2259 """ 

2260 Determine the tick locations for log axes 

2261 """ 

2262 

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

2264 """ 

2265 Place ticks at values ``subs[j] * base**n``. 

2266 

2267 Parameters 

2268 ---------- 

2269 base : float, default: 10.0 

2270 The base of the log used, so major ticks are placed at 

2271 ``base**n``, where ``n`` is an integer. 

2272 subs : None or {'auto', 'all'} or sequence of float, default: (1.0,) 

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

2274 to place ticks. The default of ``(1.0, )`` places ticks only at 

2275 integer powers of the base. 

2276 Permitted string values are ``'auto'`` and ``'all'``. 

2277 Both of these use an algorithm based on the axis view 

2278 limits to determine whether and how to put ticks between 

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

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

2281 integer powers are included. A value of None is 

2282 equivalent to ``'auto'``. 

2283 numticks : None or int, default: None 

2284 The maximum number of ticks to allow on a given axis. The default 

2285 of ``None`` will try to choose intelligently as long as this 

2286 Locator has already been assigned to an axis using 

2287 `~.axis.Axis.get_tick_space`, but otherwise falls back to 9. 

2288 """ 

2289 if numticks is None: 

2290 if mpl.rcParams['_internal.classic_mode']: 

2291 numticks = 15 

2292 else: 

2293 numticks = 'auto' 

2294 self._base = float(base) 

2295 self._set_subs(subs) 

2296 self.numdecs = numdecs 

2297 self.numticks = numticks 

2298 

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

2300 """Set parameters within this locator.""" 

2301 if base is not None: 

2302 self._base = float(base) 

2303 if subs is not None: 

2304 self._set_subs(subs) 

2305 if numdecs is not None: 

2306 self.numdecs = numdecs 

2307 if numticks is not None: 

2308 self.numticks = numticks 

2309 

2310 @_api.deprecated("3.6", alternative='set_params(base=...)') 

2311 def base(self, base): 

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

2313 self._base = float(base) 

2314 

2315 @_api.deprecated("3.6", alternative='set_params(subs=...)') 

2316 def subs(self, subs): 

2317 """ 

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

2319 """ 

2320 self._set_subs(subs) 

2321 

2322 def _set_subs(self, subs): 

2323 """ 

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

2325 """ 

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

2327 self._subs = 'auto' 

2328 elif isinstance(subs, str): 

2329 _api.check_in_list(('all', 'auto'), subs=subs) 

2330 self._subs = subs 

2331 else: 

2332 try: 

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

2334 except ValueError as e: 

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

2336 "a sequence of floats, not " 

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

2338 if self._subs.ndim != 1: 

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

2340 "1-dimensional, not " 

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

2342 

2343 def __call__(self): 

2344 """Return the locations of the ticks.""" 

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

2346 return self.tick_values(vmin, vmax) 

2347 

2348 def tick_values(self, vmin, vmax): 

2349 if self.numticks == 'auto': 

2350 if self.axis is not None: 

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

2352 else: 

2353 numticks = 9 

2354 else: 

2355 numticks = self.numticks 

2356 

2357 b = self._base 

2358 # dummy axis has no axes attribute 

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

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

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

2362 ticklocs = b ** decades 

2363 

2364 return ticklocs 

2365 

2366 if vmin <= 0.0: 

2367 if self.axis is not None: 

2368 vmin = self.axis.get_minpos() 

2369 

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

2371 raise ValueError( 

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

2373 "log-scaled.") 

2374 

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

2376 

2377 if vmax < vmin: 

2378 vmin, vmax = vmax, vmin 

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

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

2381 

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

2383 

2384 if isinstance(self._subs, str): 

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

2386 if numdec > 10 or b < 3: 

2387 if self._subs == 'auto': 

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

2389 else: 

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

2391 else: 

2392 subs = np.arange(_first, b) 

2393 else: 

2394 subs = self._subs 

2395 

2396 # Get decades between major ticks. 

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

2398 if mpl.rcParams['_internal.classic_mode'] else 

2399 (numdec + 1) // numticks + 1) 

2400 

2401 # if we have decided that the stride is as big or bigger than 

2402 # the range, clip the stride back to the available range - 1 

2403 # with a floor of 1. This prevents getting axis with only 1 tick 

2404 # visible. 

2405 if stride >= numdec: 

2406 stride = max(1, numdec - 1) 

2407 

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

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

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

2411 

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

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

2414 

2415 if hasattr(self, '_transform'): 

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

2417 if have_subs: 

2418 if stride == 1: 

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

2420 else: 

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

2422 ticklocs = np.array([]) 

2423 else: 

2424 if have_subs: 

2425 if stride == 1: 

2426 ticklocs = np.concatenate( 

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

2428 else: 

2429 ticklocs = np.array([]) 

2430 else: 

2431 ticklocs = b ** decades 

2432 

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

2434 if (len(subs) > 1 

2435 and stride == 1 

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

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

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

2439 # than one minor tick, switch to AutoLocator. 

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

2441 else: 

2442 return self.raise_if_exceeds(ticklocs) 

2443 

2444 def view_limits(self, vmin, vmax): 

2445 """Try to choose the view limits intelligently.""" 

2446 b = self._base 

2447 

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

2449 

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

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

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

2453 

2454 if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers': 

2455 vmin = _decade_less_equal(vmin, self._base) 

2456 vmax = _decade_greater_equal(vmax, self._base) 

2457 

2458 return vmin, vmax 

2459 

2460 def nonsingular(self, vmin, vmax): 

2461 if vmin > vmax: 

2462 vmin, vmax = vmax, vmin 

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

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

2465 elif vmax <= 0: 

2466 _api.warn_external( 

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

2468 "log-scaled.") 

2469 vmin, vmax = 1, 10 

2470 else: 

2471 minpos = self.axis.get_minpos() 

2472 if not np.isfinite(minpos): 

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

2474 if vmin <= 0: 

2475 vmin = minpos 

2476 if vmin == vmax: 

2477 vmin = _decade_less(vmin, self._base) 

2478 vmax = _decade_greater(vmax, self._base) 

2479 return vmin, vmax 

2480 

2481 

2482class SymmetricalLogLocator(Locator): 

2483 """ 

2484 Determine the tick locations for symmetric log axes. 

2485 """ 

2486 

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

2488 """ 

2489 Parameters 

2490 ---------- 

2491 transform : `~.scale.SymmetricalLogTransform`, optional 

2492 If set, defines the *base* and *linthresh* of the symlog transform. 

2493 base, linthresh : float, optional 

2494 The *base* and *linthresh* of the symlog transform, as documented 

2495 for `.SymmetricalLogScale`. These parameters are only used if 

2496 *transform* is not set. 

2497 subs : sequence of float, default: [1] 

2498 The multiples of integer powers of the base where ticks are placed, 

2499 i.e., ticks are placed at 

2500 ``[sub * base**i for i in ... for sub in subs]``. 

2501 

2502 Notes 

2503 ----- 

2504 Either *transform*, or both *base* and *linthresh*, must be given. 

2505 """ 

2506 if transform is not None: 

2507 self._base = transform.base 

2508 self._linthresh = transform.linthresh 

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

2510 self._base = base 

2511 self._linthresh = linthresh 

2512 else: 

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

2514 "and base, must be provided.") 

2515 if subs is None: 

2516 self._subs = [1.0] 

2517 else: 

2518 self._subs = subs 

2519 self.numticks = 15 

2520 

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

2522 """Set parameters within this locator.""" 

2523 if numticks is not None: 

2524 self.numticks = numticks 

2525 if subs is not None: 

2526 self._subs = subs 

2527 

2528 def __call__(self): 

2529 """Return the locations of the ticks.""" 

2530 # Note, these are untransformed coordinates 

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

2532 return self.tick_values(vmin, vmax) 

2533 

2534 def tick_values(self, vmin, vmax): 

2535 base = self._base 

2536 linthresh = self._linthresh 

2537 

2538 if vmax < vmin: 

2539 vmin, vmax = vmax, vmin 

2540 

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

2542 # which may actually be present. 

2543 # 

2544 # <======== -t ==0== t ========> 

2545 # aaaaaaaaa bbbbb ccccccccc 

2546 # 

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

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

2549 # than self.numticks of them. 

2550 # 

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

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

2553 # detail and not interesting.) 

2554 # 

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

2556 # uninteresting. 

2557 # 

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

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

2560 if -linthresh < vmin < vmax < linthresh: 

2561 # only the linear range is present 

2562 return [vmin, vmax] 

2563 

2564 # Lower log range is present 

2565 has_a = (vmin < -linthresh) 

2566 # Upper log range is present 

2567 has_c = (vmax > linthresh) 

2568 

2569 # Check if linear range is present 

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

2571 

2572 def get_log_range(lo, hi): 

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

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

2575 return lo, hi 

2576 

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

2578 a_lo, a_hi = (0, 0) 

2579 if has_a: 

2580 a_upper_lim = min(-linthresh, vmax) 

2581 a_lo, a_hi = get_log_range(abs(a_upper_lim), abs(vmin) + 1) 

2582 

2583 c_lo, c_hi = (0, 0) 

2584 if has_c: 

2585 c_lower_lim = max(linthresh, vmin) 

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

2587 

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

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

2590 if has_b: 

2591 total_ticks += 1 

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

2593 

2594 decades = [] 

2595 if has_a: 

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

2597 stride)[::-1]))) 

2598 

2599 if has_b: 

2600 decades.append(0.0) 

2601 

2602 if has_c: 

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

2604 

2605 # Add the subticks if requested 

2606 if self._subs is None: 

2607 subs = np.arange(2.0, base) 

2608 else: 

2609 subs = np.asarray(self._subs) 

2610 

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

2612 ticklocs = [] 

2613 for decade in decades: 

2614 if decade == 0: 

2615 ticklocs.append(decade) 

2616 else: 

2617 ticklocs.extend(subs * decade) 

2618 else: 

2619 ticklocs = decades 

2620 

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

2622 

2623 def view_limits(self, vmin, vmax): 

2624 """Try to choose the view limits intelligently.""" 

2625 b = self._base 

2626 if vmax < vmin: 

2627 vmin, vmax = vmax, vmin 

2628 

2629 if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers': 

2630 vmin = _decade_less_equal(vmin, b) 

2631 vmax = _decade_greater_equal(vmax, b) 

2632 if vmin == vmax: 

2633 vmin = _decade_less(vmin, b) 

2634 vmax = _decade_greater(vmax, b) 

2635 

2636 result = mtransforms.nonsingular(vmin, vmax) 

2637 return result 

2638 

2639 

2640class AsinhLocator(Locator): 

2641 """ 

2642 An axis tick locator specialized for the inverse-sinh scale 

2643 

2644 This is very unlikely to have any use beyond 

2645 the `~.scale.AsinhScale` class. 

2646 

2647 .. note:: 

2648 

2649 This API is provisional and may be revised in the future 

2650 based on early user feedback. 

2651 """ 

2652 def __init__(self, linear_width, numticks=11, symthresh=0.2, 

2653 base=10, subs=None): 

2654 """ 

2655 Parameters 

2656 ---------- 

2657 linear_width : float 

2658 The scale parameter defining the extent 

2659 of the quasi-linear region. 

2660 numticks : int, default: 11 

2661 The approximate number of major ticks that will fit 

2662 along the entire axis 

2663 symthresh : float, default: 0.2 

2664 The fractional threshold beneath which data which covers 

2665 a range that is approximately symmetric about zero 

2666 will have ticks that are exactly symmetric. 

2667 base : int, default: 10 

2668 The number base used for rounding tick locations 

2669 on a logarithmic scale. If this is less than one, 

2670 then rounding is to the nearest integer multiple 

2671 of powers of ten. 

2672 subs : tuple, default: None 

2673 Multiples of the number base, typically used 

2674 for the minor ticks, e.g. (2, 5) when base=10. 

2675 """ 

2676 super().__init__() 

2677 self.linear_width = linear_width 

2678 self.numticks = numticks 

2679 self.symthresh = symthresh 

2680 self.base = base 

2681 self.subs = subs 

2682 

2683 def set_params(self, numticks=None, symthresh=None, 

2684 base=None, subs=None): 

2685 """Set parameters within this locator.""" 

2686 if numticks is not None: 

2687 self.numticks = numticks 

2688 if symthresh is not None: 

2689 self.symthresh = symthresh 

2690 if base is not None: 

2691 self.base = base 

2692 if subs is not None: 

2693 self.subs = subs if len(subs) > 0 else None 

2694 

2695 def __call__(self): 

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

2697 if (vmin * vmax) < 0 and abs(1 + vmax / vmin) < self.symthresh: 

2698 # Data-range appears to be almost symmetric, so round up: 

2699 bound = max(abs(vmin), abs(vmax)) 

2700 return self.tick_values(-bound, bound) 

2701 else: 

2702 return self.tick_values(vmin, vmax) 

2703 

2704 def tick_values(self, vmin, vmax): 

2705 # Construct a set of "on-screen" locations 

2706 # that are uniformly spaced: 

2707 ymin, ymax = self.linear_width * np.arcsinh(np.array([vmin, vmax]) 

2708 / self.linear_width) 

2709 ys = np.linspace(ymin, ymax, self.numticks) 

2710 zero_dev = np.abs(ys / (ymax - ymin)) 

2711 if (ymin * ymax) < 0: 

2712 # Ensure that the zero tick-mark is included, 

2713 # if the axis straddles zero 

2714 ys = np.hstack([ys[(zero_dev > 0.5 / self.numticks)], 0.0]) 

2715 

2716 # Transform the "on-screen" grid to the data space: 

2717 xs = self.linear_width * np.sinh(ys / self.linear_width) 

2718 zero_xs = (ys == 0) 

2719 

2720 # Round the data-space values to be intuitive base-n numbers, 

2721 # keeping track of positive and negative values separately, 

2722 # but giving careful treatment to the zero value: 

2723 if self.base > 1: 

2724 log_base = math.log(self.base) 

2725 powers = ( 

2726 np.where(zero_xs, 0, np.sign(xs)) * 

2727 np.power(self.base, 

2728 np.where(zero_xs, 0.0, 

2729 np.floor(np.log(np.abs(xs) + zero_xs*1e-6) 

2730 / log_base))) 

2731 ) 

2732 if self.subs: 

2733 qs = np.outer(powers, self.subs).flatten() 

2734 else: 

2735 qs = powers 

2736 else: 

2737 powers = ( 

2738 np.where(xs >= 0, 1, -1) * 

2739 np.power(10, np.where(zero_xs, 0.0, 

2740 np.floor(np.log10(np.abs(xs) 

2741 + zero_xs*1e-6)))) 

2742 ) 

2743 qs = powers * np.round(xs / powers) 

2744 ticks = np.array(sorted(set(qs))) 

2745 

2746 if len(ticks) >= 2: 

2747 return ticks 

2748 else: 

2749 return np.linspace(vmin, vmax, self.numticks) 

2750 

2751 

2752class LogitLocator(MaxNLocator): 

2753 """ 

2754 Determine the tick locations for logit axes 

2755 """ 

2756 

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

2758 """ 

2759 Place ticks on the logit locations 

2760 

2761 Parameters 

2762 ---------- 

2763 nbins : int or 'auto', optional 

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

2765 minor : bool, default: False 

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

2767 """ 

2768 

2769 self._minor = minor 

2770 super().__init__(nbins=nbins, steps=[1, 2, 5, 10]) 

2771 

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

2773 """Set parameters within this locator.""" 

2774 if minor is not None: 

2775 self._minor = minor 

2776 super().set_params(**kwargs) 

2777 

2778 @property 

2779 def minor(self): 

2780 return self._minor 

2781 

2782 @minor.setter 

2783 def minor(self, value): 

2784 self.set_params(minor=value) 

2785 

2786 def tick_values(self, vmin, vmax): 

2787 # dummy axis has no axes attribute 

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

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

2790 

2791 if self._nbins == "auto": 

2792 if self.axis is not None: 

2793 nbins = self.axis.get_tick_space() 

2794 if nbins < 2: 

2795 nbins = 2 

2796 else: 

2797 nbins = 9 

2798 else: 

2799 nbins = self._nbins 

2800 

2801 # We define ideal ticks with their index: 

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

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

2804 def ideal_ticks(x): 

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

2806 

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

2808 binf = int( 

2809 np.floor(np.log10(vmin)) 

2810 if vmin < 0.5 

2811 else 0 

2812 if vmin < 0.9 

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

2814 ) 

2815 bsup = int( 

2816 np.ceil(np.log10(vmax)) 

2817 if vmax <= 0.5 

2818 else 1 

2819 if vmax <= 0.9 

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

2821 ) 

2822 numideal = bsup - binf - 1 

2823 if numideal >= 2: 

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

2825 if numideal > nbins: 

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

2827 # take others for minor ticks 

2828 subsampling_factor = math.ceil(numideal / nbins) 

2829 if self._minor: 

2830 ticklocs = [ 

2831 ideal_ticks(b) 

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

2833 if (b % subsampling_factor) != 0 

2834 ] 

2835 else: 

2836 ticklocs = [ 

2837 ideal_ticks(b) 

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

2839 if (b % subsampling_factor) == 0 

2840 ] 

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

2842 if self._minor: 

2843 ticklocs = [] 

2844 for b in range(binf, bsup): 

2845 if b < -1: 

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

2847 elif b == -1: 

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

2849 elif b == 0: 

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

2851 else: 

2852 ticklocs.extend( 

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

2854 ) 

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

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

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

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

2859 if self._minor: 

2860 return [] 

2861 return super().tick_values(vmin, vmax) 

2862 

2863 def nonsingular(self, vmin, vmax): 

2864 standard_minpos = 1e-7 

2865 initial_range = (standard_minpos, 1 - standard_minpos) 

2866 if vmin > vmax: 

2867 vmin, vmax = vmax, vmin 

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

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

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

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

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

2873 _api.warn_external( 

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

2875 "logit-scaled." 

2876 ) 

2877 vmin, vmax = initial_range 

2878 else: 

2879 minpos = ( 

2880 self.axis.get_minpos() 

2881 if self.axis is not None 

2882 else standard_minpos 

2883 ) 

2884 if not np.isfinite(minpos): 

2885 minpos = standard_minpos # This should never take effect. 

2886 if vmin <= 0: 

2887 vmin = minpos 

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

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

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

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

2892 if vmax >= 1: 

2893 vmax = 1 - minpos 

2894 if vmin == vmax: 

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

2896 

2897 return vmin, vmax 

2898 

2899 

2900class AutoLocator(MaxNLocator): 

2901 """ 

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

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

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

2905 """ 

2906 def __init__(self): 

2907 """ 

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

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

2910 """ 

2911 if mpl.rcParams['_internal.classic_mode']: 

2912 nbins = 9 

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

2914 else: 

2915 nbins = 'auto' 

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

2917 super().__init__(nbins=nbins, steps=steps) 

2918 

2919 

2920class AutoMinorLocator(Locator): 

2921 """ 

2922 Dynamically find minor tick positions based on the positions of 

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

2924 """ 

2925 def __init__(self, n=None): 

2926 """ 

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

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

2929 between major ticks. 

2930 

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

2932 """ 

2933 self.ndivs = n 

2934 

2935 def __call__(self): 

2936 """Return the locations of the ticks.""" 

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

2938 _api.warn_external('AutoMinorLocator does not work with ' 

2939 'logarithmic scale') 

2940 return [] 

2941 

2942 majorlocs = self.axis.get_majorticklocs() 

2943 try: 

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

2945 except IndexError: 

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

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

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

2949 # no ticks at all. 

2950 return [] 

2951 

2952 if self.ndivs is None: 

2953 

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

2955 

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

2957 ndivs = 5 

2958 else: 

2959 ndivs = 4 

2960 else: 

2961 ndivs = self.ndivs 

2962 

2963 minorstep = majorstep / ndivs 

2964 

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

2966 if vmin > vmax: 

2967 vmin, vmax = vmax, vmin 

2968 

2969 t0 = majorlocs[0] 

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

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

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

2973 

2974 return self.raise_if_exceeds(locs) 

2975 

2976 def tick_values(self, vmin, vmax): 

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

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