Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2Scales define the distribution of data values on an axis, e.g. a log scaling. 

3 

4They are attached to an `~.axis.Axis` and hold a `.Transform`, which is 

5responsible for the actual data transformation. 

6 

7See also `.axes.Axes.set_xscale` and the scales examples in the documentation. 

8""" 

9 

10import inspect 

11import textwrap 

12 

13import numpy as np 

14from numpy import ma 

15 

16from matplotlib import cbook, docstring, rcParams 

17from matplotlib.ticker import ( 

18 NullFormatter, ScalarFormatter, LogFormatterSciNotation, LogitFormatter, 

19 NullLocator, LogLocator, AutoLocator, AutoMinorLocator, 

20 SymmetricalLogLocator, LogitLocator) 

21from matplotlib.transforms import Transform, IdentityTransform 

22from matplotlib.cbook import warn_deprecated 

23 

24 

25class ScaleBase: 

26 """ 

27 The base class for all scales. 

28 

29 Scales are separable transformations, working on a single dimension. 

30 

31 Any subclasses will want to override: 

32 

33 - :attr:`name` 

34 - :meth:`get_transform` 

35 - :meth:`set_default_locators_and_formatters` 

36 

37 And optionally: 

38 

39 - :meth:`limit_range_for_scale` 

40 

41 """ 

42 

43 def __init__(self, axis, **kwargs): 

44 r""" 

45 Construct a new scale. 

46 

47 Notes 

48 ----- 

49 The following note is for scale implementors. 

50 

51 For back-compatibility reasons, scales take an `~matplotlib.axis.Axis` 

52 object as first argument. However, this argument should not 

53 be used: a single scale object should be usable by multiple 

54 `~matplotlib.axis.Axis`\es at the same time. 

55 """ 

56 if kwargs: 

57 warn_deprecated( 

58 '3.2.0', 

59 message=( 

60 f"ScaleBase got an unexpected keyword " 

61 f"argument {next(iter(kwargs))!r}. " 

62 'In the future this will raise TypeError') 

63 ) 

64 

65 def get_transform(self): 

66 """ 

67 Return the :class:`~matplotlib.transforms.Transform` object 

68 associated with this scale. 

69 """ 

70 raise NotImplementedError() 

71 

72 def set_default_locators_and_formatters(self, axis): 

73 """ 

74 Set the locators and formatters of *axis* to instances suitable for 

75 this scale. 

76 """ 

77 raise NotImplementedError() 

78 

79 def limit_range_for_scale(self, vmin, vmax, minpos): 

80 """ 

81 Returns the range *vmin*, *vmax*, possibly limited to the 

82 domain supported by this scale. 

83 

84 *minpos* should be the minimum positive value in the data. 

85 This is used by log scales to determine a minimum value. 

86 """ 

87 return vmin, vmax 

88 

89 

90class LinearScale(ScaleBase): 

91 """ 

92 The default linear scale. 

93 """ 

94 

95 name = 'linear' 

96 

97 def __init__(self, axis, **kwargs): 

98 # This method is present only to prevent inheritance of the base class' 

99 # constructor docstring, which would otherwise end up interpolated into 

100 # the docstring of Axis.set_scale. 

101 """ 

102 """ 

103 super().__init__(axis, **kwargs) 

104 

105 def set_default_locators_and_formatters(self, axis): 

106 # docstring inherited 

107 axis.set_major_locator(AutoLocator()) 

108 axis.set_major_formatter(ScalarFormatter()) 

109 axis.set_minor_formatter(NullFormatter()) 

110 # update the minor locator for x and y axis based on rcParams 

111 if (axis.axis_name == 'x' and rcParams['xtick.minor.visible'] 

112 or axis.axis_name == 'y' and rcParams['ytick.minor.visible']): 

113 axis.set_minor_locator(AutoMinorLocator()) 

114 else: 

115 axis.set_minor_locator(NullLocator()) 

116 

117 def get_transform(self): 

118 """ 

119 Return the transform for linear scaling, which is just the 

120 `~matplotlib.transforms.IdentityTransform`. 

121 """ 

122 return IdentityTransform() 

123 

124 

125class FuncTransform(Transform): 

126 """ 

127 A simple transform that takes and arbitrary function for the 

128 forward and inverse transform. 

129 """ 

130 

131 input_dims = output_dims = 1 

132 

133 def __init__(self, forward, inverse): 

134 """ 

135 Parameters 

136 ---------- 

137 forward : callable 

138 The forward function for the transform. This function must have 

139 an inverse and, for best behavior, be monotonic. 

140 It must have the signature:: 

141 

142 def forward(values: array-like) -> array-like 

143 

144 inverse : callable 

145 The inverse of the forward function. Signature as ``forward``. 

146 """ 

147 super().__init__() 

148 if callable(forward) and callable(inverse): 

149 self._forward = forward 

150 self._inverse = inverse 

151 else: 

152 raise ValueError('arguments to FuncTransform must be functions') 

153 

154 def transform_non_affine(self, values): 

155 return self._forward(values) 

156 

157 def inverted(self): 

158 return FuncTransform(self._inverse, self._forward) 

159 

160 

161class FuncScale(ScaleBase): 

162 """ 

163 Provide an arbitrary scale with user-supplied function for the axis. 

164 """ 

165 

166 name = 'function' 

167 

168 def __init__(self, axis, functions): 

169 """ 

170 Parameters 

171 ---------- 

172 axis : `~matplotlib.axis.Axis` 

173 The axis for the scale. 

174 functions : (callable, callable) 

175 two-tuple of the forward and inverse functions for the scale. 

176 The forward function must be monotonic. 

177 

178 Both functions must have the signature:: 

179 

180 def forward(values: array-like) -> array-like 

181 """ 

182 forward, inverse = functions 

183 transform = FuncTransform(forward, inverse) 

184 self._transform = transform 

185 

186 def get_transform(self): 

187 """Return the `.FuncTransform` associated with this scale.""" 

188 return self._transform 

189 

190 def set_default_locators_and_formatters(self, axis): 

191 # docstring inherited 

192 axis.set_major_locator(AutoLocator()) 

193 axis.set_major_formatter(ScalarFormatter()) 

194 axis.set_minor_formatter(NullFormatter()) 

195 # update the minor locator for x and y axis based on rcParams 

196 if (axis.axis_name == 'x' and rcParams['xtick.minor.visible'] 

197 or axis.axis_name == 'y' and rcParams['ytick.minor.visible']): 

198 axis.set_minor_locator(AutoMinorLocator()) 

199 else: 

200 axis.set_minor_locator(NullLocator()) 

201 

202 

203@cbook.deprecated("3.1", alternative="LogTransform") 

204class LogTransformBase(Transform): 

205 input_dims = output_dims = 1 

206 

207 def __init__(self, nonpos='clip'): 

208 Transform.__init__(self) 

209 self._clip = {"clip": True, "mask": False}[nonpos] 

210 

211 def transform_non_affine(self, a): 

212 return LogTransform.transform_non_affine(self, a) 

213 

214 def __str__(self): 

215 return "{}({!r})".format( 

216 type(self).__name__, "clip" if self._clip else "mask") 

217 

218 

219@cbook.deprecated("3.1", alternative="InvertedLogTransform") 

220class InvertedLogTransformBase(Transform): 

221 input_dims = output_dims = 1 

222 

223 def transform_non_affine(self, a): 

224 return ma.power(self.base, a) 

225 

226 def __str__(self): 

227 return "{}()".format(type(self).__name__) 

228 

229 

230@cbook.deprecated("3.1", alternative="LogTransform") 

231class Log10Transform(LogTransformBase): 

232 base = 10.0 

233 

234 def inverted(self): 

235 return InvertedLog10Transform() 

236 

237 

238@cbook.deprecated("3.1", alternative="InvertedLogTransform") 

239class InvertedLog10Transform(InvertedLogTransformBase): 

240 base = 10.0 

241 

242 def inverted(self): 

243 return Log10Transform() 

244 

245 

246@cbook.deprecated("3.1", alternative="LogTransform") 

247class Log2Transform(LogTransformBase): 

248 base = 2.0 

249 

250 def inverted(self): 

251 return InvertedLog2Transform() 

252 

253 

254@cbook.deprecated("3.1", alternative="InvertedLogTransform") 

255class InvertedLog2Transform(InvertedLogTransformBase): 

256 base = 2.0 

257 

258 def inverted(self): 

259 return Log2Transform() 

260 

261 

262@cbook.deprecated("3.1", alternative="LogTransform") 

263class NaturalLogTransform(LogTransformBase): 

264 base = np.e 

265 

266 def inverted(self): 

267 return InvertedNaturalLogTransform() 

268 

269 

270@cbook.deprecated("3.1", alternative="InvertedLogTransform") 

271class InvertedNaturalLogTransform(InvertedLogTransformBase): 

272 base = np.e 

273 

274 def inverted(self): 

275 return NaturalLogTransform() 

276 

277 

278class LogTransform(Transform): 

279 input_dims = output_dims = 1 

280 

281 def __init__(self, base, nonpos='clip'): 

282 Transform.__init__(self) 

283 self.base = base 

284 self._clip = {"clip": True, "mask": False}[nonpos] 

285 

286 def __str__(self): 

287 return "{}(base={}, nonpos={!r})".format( 

288 type(self).__name__, self.base, "clip" if self._clip else "mask") 

289 

290 def transform_non_affine(self, a): 

291 # Ignore invalid values due to nans being passed to the transform. 

292 with np.errstate(divide="ignore", invalid="ignore"): 

293 log = {np.e: np.log, 2: np.log2, 10: np.log10}.get(self.base) 

294 if log: # If possible, do everything in a single call to NumPy. 

295 out = log(a) 

296 else: 

297 out = np.log(a) 

298 out /= np.log(self.base) 

299 if self._clip: 

300 # SVG spec says that conforming viewers must support values up 

301 # to 3.4e38 (C float); however experiments suggest that 

302 # Inkscape (which uses cairo for rendering) runs into cairo's 

303 # 24-bit limit (which is apparently shared by Agg). 

304 # Ghostscript (used for pdf rendering appears to overflow even 

305 # earlier, with the max value around 2 ** 15 for the tests to 

306 # pass. On the other hand, in practice, we want to clip beyond 

307 # np.log10(np.nextafter(0, 1)) ~ -323 

308 # so 1000 seems safe. 

309 out[a <= 0] = -1000 

310 return out 

311 

312 def inverted(self): 

313 return InvertedLogTransform(self.base) 

314 

315 

316class InvertedLogTransform(Transform): 

317 input_dims = output_dims = 1 

318 

319 def __init__(self, base): 

320 Transform.__init__(self) 

321 self.base = base 

322 

323 def __str__(self): 

324 return "{}(base={})".format(type(self).__name__, self.base) 

325 

326 def transform_non_affine(self, a): 

327 return ma.power(self.base, a) 

328 

329 def inverted(self): 

330 return LogTransform(self.base) 

331 

332 

333class LogScale(ScaleBase): 

334 """ 

335 A standard logarithmic scale. Care is taken to only plot positive values. 

336 """ 

337 name = 'log' 

338 

339 # compatibility shim 

340 LogTransformBase = LogTransformBase 

341 Log10Transform = Log10Transform 

342 InvertedLog10Transform = InvertedLog10Transform 

343 Log2Transform = Log2Transform 

344 InvertedLog2Transform = InvertedLog2Transform 

345 NaturalLogTransform = NaturalLogTransform 

346 InvertedNaturalLogTransform = InvertedNaturalLogTransform 

347 LogTransform = LogTransform 

348 InvertedLogTransform = InvertedLogTransform 

349 

350 def __init__(self, axis, **kwargs): 

351 """ 

352 Parameters 

353 ---------- 

354 axis : `~matplotlib.axis.Axis` 

355 The axis for the scale. 

356 basex, basey : float, default: 10 

357 The base of the logarithm. 

358 nonposx, nonposy : {'clip', 'mask'}, default: 'clip' 

359 Determines the behavior for non-positive values. They can either 

360 be masked as invalid, or clipped to a very small positive number. 

361 subsx, subsy : sequence of int, default: None 

362 Where to place the subticks between each major tick. 

363 For example, in a log10 scale: ``[2, 3, 4, 5, 6, 7, 8, 9]`` 

364 will place 8 logarithmically spaced minor ticks between 

365 each major tick. 

366 """ 

367 if axis.axis_name == 'x': 

368 base = kwargs.pop('basex', 10.0) 

369 subs = kwargs.pop('subsx', None) 

370 nonpos = kwargs.pop('nonposx', 'clip') 

371 cbook._check_in_list(['mask', 'clip'], nonposx=nonpos) 

372 else: 

373 base = kwargs.pop('basey', 10.0) 

374 subs = kwargs.pop('subsy', None) 

375 nonpos = kwargs.pop('nonposy', 'clip') 

376 cbook._check_in_list(['mask', 'clip'], nonposy=nonpos) 

377 

378 if kwargs: 

379 raise TypeError(f"LogScale got an unexpected keyword " 

380 f"argument {next(iter(kwargs))!r}") 

381 

382 if base <= 0 or base == 1: 

383 raise ValueError('The log base cannot be <= 0 or == 1') 

384 

385 self._transform = LogTransform(base, nonpos) 

386 self.subs = subs 

387 

388 @property 

389 def base(self): 

390 return self._transform.base 

391 

392 def set_default_locators_and_formatters(self, axis): 

393 # docstring inherited 

394 axis.set_major_locator(LogLocator(self.base)) 

395 axis.set_major_formatter(LogFormatterSciNotation(self.base)) 

396 axis.set_minor_locator(LogLocator(self.base, self.subs)) 

397 axis.set_minor_formatter( 

398 LogFormatterSciNotation(self.base, 

399 labelOnlyBase=(self.subs is not None))) 

400 

401 def get_transform(self): 

402 """Return the `.LogTransform` associated with this scale.""" 

403 return self._transform 

404 

405 def limit_range_for_scale(self, vmin, vmax, minpos): 

406 """Limit the domain to positive values.""" 

407 if not np.isfinite(minpos): 

408 minpos = 1e-300 # Should rarely (if ever) have a visible effect. 

409 

410 return (minpos if vmin <= 0 else vmin, 

411 minpos if vmax <= 0 else vmax) 

412 

413 

414class FuncScaleLog(LogScale): 

415 """ 

416 Provide an arbitrary scale with user-supplied function for the axis and 

417 then put on a logarithmic axes. 

418 """ 

419 

420 name = 'functionlog' 

421 

422 def __init__(self, axis, functions, base=10): 

423 """ 

424 Parameters 

425 ---------- 

426 axis : `matplotlib.axis.Axis` 

427 The axis for the scale. 

428 functions : (callable, callable) 

429 two-tuple of the forward and inverse functions for the scale. 

430 The forward function must be monotonic. 

431 

432 Both functions must have the signature:: 

433 

434 def forward(values: array-like) -> array-like 

435 

436 base : float 

437 logarithmic base of the scale (default = 10) 

438 

439 """ 

440 forward, inverse = functions 

441 self.subs = None 

442 self._transform = FuncTransform(forward, inverse) + LogTransform(base) 

443 

444 @property 

445 def base(self): 

446 return self._transform._b.base # Base of the LogTransform. 

447 

448 def get_transform(self): 

449 """Return the `.Transform` associated with this scale.""" 

450 return self._transform 

451 

452 

453class SymmetricalLogTransform(Transform): 

454 input_dims = output_dims = 1 

455 

456 def __init__(self, base, linthresh, linscale): 

457 Transform.__init__(self) 

458 self.base = base 

459 self.linthresh = linthresh 

460 self.linscale = linscale 

461 self._linscale_adj = (linscale / (1.0 - self.base ** -1)) 

462 self._log_base = np.log(base) 

463 

464 def transform_non_affine(self, a): 

465 abs_a = np.abs(a) 

466 with np.errstate(divide="ignore", invalid="ignore"): 

467 out = np.sign(a) * self.linthresh * ( 

468 self._linscale_adj + 

469 np.log(abs_a / self.linthresh) / self._log_base) 

470 inside = abs_a <= self.linthresh 

471 out[inside] = a[inside] * self._linscale_adj 

472 return out 

473 

474 def inverted(self): 

475 return InvertedSymmetricalLogTransform(self.base, self.linthresh, 

476 self.linscale) 

477 

478 

479class InvertedSymmetricalLogTransform(Transform): 

480 input_dims = output_dims = 1 

481 

482 def __init__(self, base, linthresh, linscale): 

483 Transform.__init__(self) 

484 symlog = SymmetricalLogTransform(base, linthresh, linscale) 

485 self.base = base 

486 self.linthresh = linthresh 

487 self.invlinthresh = symlog.transform(linthresh) 

488 self.linscale = linscale 

489 self._linscale_adj = (linscale / (1.0 - self.base ** -1)) 

490 

491 def transform_non_affine(self, a): 

492 abs_a = np.abs(a) 

493 with np.errstate(divide="ignore", invalid="ignore"): 

494 out = np.sign(a) * self.linthresh * ( 

495 np.power(self.base, 

496 abs_a / self.linthresh - self._linscale_adj)) 

497 inside = abs_a <= self.invlinthresh 

498 out[inside] = a[inside] / self._linscale_adj 

499 return out 

500 

501 def inverted(self): 

502 return SymmetricalLogTransform(self.base, 

503 self.linthresh, self.linscale) 

504 

505 

506class SymmetricalLogScale(ScaleBase): 

507 """ 

508 The symmetrical logarithmic scale is logarithmic in both the 

509 positive and negative directions from the origin. 

510 

511 Since the values close to zero tend toward infinity, there is a 

512 need to have a range around zero that is linear. The parameter 

513 *linthresh* allows the user to specify the size of this range 

514 (-*linthresh*, *linthresh*). 

515 

516 Parameters 

517 ---------- 

518 basex, basey : float 

519 The base of the logarithm. Defaults to 10. 

520 

521 linthreshx, linthreshy : float 

522 Defines the range ``(-x, x)``, within which the plot is linear. 

523 This avoids having the plot go to infinity around zero. Defaults to 2. 

524 

525 subsx, subsy : sequence of int 

526 Where to place the subticks between each major tick. 

527 For example, in a log10 scale: ``[2, 3, 4, 5, 6, 7, 8, 9]`` will place 

528 8 logarithmically spaced minor ticks between each major tick. 

529 

530 linscalex, linscaley : float, optional 

531 This allows the linear range ``(-linthresh, linthresh)`` to be 

532 stretched relative to the logarithmic range. Its value is the number of 

533 decades to use for each half of the linear range. For example, when 

534 *linscale* == 1.0 (the default), the space used for the positive and 

535 negative halves of the linear range will be equal to one decade in 

536 the logarithmic range. 

537 """ 

538 name = 'symlog' 

539 # compatibility shim 

540 SymmetricalLogTransform = SymmetricalLogTransform 

541 InvertedSymmetricalLogTransform = InvertedSymmetricalLogTransform 

542 

543 def __init__(self, axis, **kwargs): 

544 if axis.axis_name == 'x': 

545 base = kwargs.pop('basex', 10.0) 

546 linthresh = kwargs.pop('linthreshx', 2.0) 

547 subs = kwargs.pop('subsx', None) 

548 linscale = kwargs.pop('linscalex', 1.0) 

549 else: 

550 base = kwargs.pop('basey', 10.0) 

551 linthresh = kwargs.pop('linthreshy', 2.0) 

552 subs = kwargs.pop('subsy', None) 

553 linscale = kwargs.pop('linscaley', 1.0) 

554 if kwargs: 

555 warn_deprecated( 

556 '3.2.0', 

557 message=( 

558 f"SymmetricalLogScale got an unexpected keyword " 

559 f"argument {next(iter(kwargs))!r}. " 

560 'In the future this will raise TypeError') 

561 ) 

562 # raise TypeError(f"SymmetricalLogScale got an unexpected keyword " 

563 # f"argument {next(iter(kwargs))!r}") 

564 

565 if base <= 1.0: 

566 raise ValueError("'basex/basey' must be larger than 1") 

567 if linthresh <= 0.0: 

568 raise ValueError("'linthreshx/linthreshy' must be positive") 

569 if linscale <= 0.0: 

570 raise ValueError("'linscalex/linthreshy' must be positive") 

571 

572 self._transform = SymmetricalLogTransform(base, linthresh, linscale) 

573 self.base = base 

574 self.linthresh = linthresh 

575 self.linscale = linscale 

576 self.subs = subs 

577 

578 def set_default_locators_and_formatters(self, axis): 

579 # docstring inherited 

580 axis.set_major_locator(SymmetricalLogLocator(self.get_transform())) 

581 axis.set_major_formatter(LogFormatterSciNotation(self.base)) 

582 axis.set_minor_locator(SymmetricalLogLocator(self.get_transform(), 

583 self.subs)) 

584 axis.set_minor_formatter(NullFormatter()) 

585 

586 def get_transform(self): 

587 """Return the `.SymmetricalLogTransform` associated with this scale.""" 

588 return self._transform 

589 

590 

591class LogitTransform(Transform): 

592 input_dims = output_dims = 1 

593 

594 def __init__(self, nonpos='mask'): 

595 Transform.__init__(self) 

596 cbook._check_in_list(['mask', 'clip'], nonpos=nonpos) 

597 self._nonpos = nonpos 

598 self._clip = {"clip": True, "mask": False}[nonpos] 

599 

600 def transform_non_affine(self, a): 

601 """logit transform (base 10), masked or clipped""" 

602 with np.errstate(divide="ignore", invalid="ignore"): 

603 out = np.log10(a / (1 - a)) 

604 if self._clip: # See LogTransform for choice of clip value. 

605 out[a <= 0] = -1000 

606 out[1 <= a] = 1000 

607 return out 

608 

609 def inverted(self): 

610 return LogisticTransform(self._nonpos) 

611 

612 def __str__(self): 

613 return "{}({!r})".format(type(self).__name__, self._nonpos) 

614 

615 

616class LogisticTransform(Transform): 

617 input_dims = output_dims = 1 

618 

619 def __init__(self, nonpos='mask'): 

620 Transform.__init__(self) 

621 self._nonpos = nonpos 

622 

623 def transform_non_affine(self, a): 

624 """logistic transform (base 10)""" 

625 return 1.0 / (1 + 10**(-a)) 

626 

627 def inverted(self): 

628 return LogitTransform(self._nonpos) 

629 

630 def __str__(self): 

631 return "{}({!r})".format(type(self).__name__, self._nonpos) 

632 

633 

634class LogitScale(ScaleBase): 

635 """ 

636 Logit scale for data between zero and one, both excluded. 

637 

638 This scale is similar to a log scale close to zero and to one, and almost 

639 linear around 0.5. It maps the interval ]0, 1[ onto ]-infty, +infty[. 

640 """ 

641 name = 'logit' 

642 

643 def __init__( 

644 self, 

645 axis, 

646 nonpos='mask', 

647 *, 

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

649 use_overline=False, 

650 ): 

651 r""" 

652 Parameters 

653 ---------- 

654 axis : `matplotlib.axis.Axis` 

655 Currently unused. 

656 nonpos : {'mask', 'clip'} 

657 Determines the behavior for values beyond the open interval ]0, 1[. 

658 They can either be masked as invalid, or clipped to a number very 

659 close to 0 or 1. 

660 use_overline : bool, default: False 

661 Indicate the usage of survival notation (\overline{x}) in place of 

662 standard notation (1-x) for probability close to one. 

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

664 The string used for ticks formatter to represent 1/2. 

665 """ 

666 self._transform = LogitTransform(nonpos) 

667 self._use_overline = use_overline 

668 self._one_half = one_half 

669 

670 def get_transform(self): 

671 """Return the `.LogitTransform` associated with this scale.""" 

672 return self._transform 

673 

674 def set_default_locators_and_formatters(self, axis): 

675 # docstring inherited 

676 # ..., 0.01, 0.1, 0.5, 0.9, 0.99, ... 

677 axis.set_major_locator(LogitLocator()) 

678 axis.set_major_formatter( 

679 LogitFormatter( 

680 one_half=self._one_half, 

681 use_overline=self._use_overline 

682 ) 

683 ) 

684 axis.set_minor_locator(LogitLocator(minor=True)) 

685 axis.set_minor_formatter( 

686 LogitFormatter( 

687 minor=True, 

688 one_half=self._one_half, 

689 use_overline=self._use_overline 

690 ) 

691 ) 

692 

693 def limit_range_for_scale(self, vmin, vmax, minpos): 

694 """ 

695 Limit the domain to values between 0 and 1 (excluded). 

696 """ 

697 if not np.isfinite(minpos): 

698 minpos = 1e-7 # Should rarely (if ever) have a visible effect. 

699 return (minpos if vmin <= 0 else vmin, 

700 1 - minpos if vmax >= 1 else vmax) 

701 

702 

703_scale_mapping = { 

704 'linear': LinearScale, 

705 'log': LogScale, 

706 'symlog': SymmetricalLogScale, 

707 'logit': LogitScale, 

708 'function': FuncScale, 

709 'functionlog': FuncScaleLog, 

710 } 

711 

712 

713def get_scale_names(): 

714 """Return the names of the available scales.""" 

715 return sorted(_scale_mapping) 

716 

717 

718def scale_factory(scale, axis, **kwargs): 

719 """ 

720 Return a scale class by name. 

721 

722 Parameters 

723 ---------- 

724 scale : {%(names)s} 

725 axis : `matplotlib.axis.Axis` 

726 """ 

727 scale = scale.lower() 

728 cbook._check_in_list(_scale_mapping, scale=scale) 

729 return _scale_mapping[scale](axis, **kwargs) 

730 

731 

732if scale_factory.__doc__: 

733 scale_factory.__doc__ = scale_factory.__doc__ % { 

734 "names": ", ".join(map(repr, get_scale_names()))} 

735 

736 

737def register_scale(scale_class): 

738 """ 

739 Register a new kind of scale. 

740 

741 Parameters 

742 ---------- 

743 scale_class : subclass of `ScaleBase` 

744 The scale to register. 

745 """ 

746 _scale_mapping[scale_class.name] = scale_class 

747 

748 

749@cbook.deprecated( 

750 '3.1', message='get_scale_docs() is considered private API since ' 

751 '3.1 and will be removed from the public API in 3.3.') 

752def get_scale_docs(): 

753 """ 

754 Helper function for generating docstrings related to scales. 

755 """ 

756 return _get_scale_docs() 

757 

758 

759def _get_scale_docs(): 

760 """ 

761 Helper function for generating docstrings related to scales. 

762 """ 

763 docs = [] 

764 for name, scale_class in _scale_mapping.items(): 

765 docs.extend([ 

766 f" {name!r}", 

767 "", 

768 textwrap.indent(inspect.getdoc(scale_class.__init__), " " * 8), 

769 "" 

770 ]) 

771 return "\n".join(docs) 

772 

773 

774docstring.interpd.update( 

775 scale_type='{%s}' % ', '.join([repr(x) for x in get_scale_names()]), 

776 scale_docs=_get_scale_docs().rstrip(), 

777 )