Coverage for /usr/lib/python3/dist-packages/matplotlib/scale.py: 56%

277 statements  

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

1""" 

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

3They are defined as subclasses of `ScaleBase`. 

4 

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

6 

7See :doc:`/gallery/scales/custom_scale` for a full example of defining a custom 

8scale. 

9 

10Matplotlib also supports non-separable transformations that operate on both 

11`~.axis.Axis` at the same time. They are known as projections, and defined in 

12`matplotlib.projections`. 

13""" 

14 

15import inspect 

16import textwrap 

17 

18import numpy as np 

19 

20import matplotlib as mpl 

21from matplotlib import _api, _docstring 

22from matplotlib.ticker import ( 

23 NullFormatter, ScalarFormatter, LogFormatterSciNotation, LogitFormatter, 

24 NullLocator, LogLocator, AutoLocator, AutoMinorLocator, 

25 SymmetricalLogLocator, AsinhLocator, LogitLocator) 

26from matplotlib.transforms import Transform, IdentityTransform 

27 

28 

29class ScaleBase: 

30 """ 

31 The base class for all scales. 

32 

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

34 

35 Subclasses should override 

36 

37 :attr:`name` 

38 The scale's name. 

39 :meth:`get_transform` 

40 A method returning a `.Transform`, which converts data coordinates to 

41 scaled coordinates. This transform should be invertible, so that e.g. 

42 mouse positions can be converted back to data coordinates. 

43 :meth:`set_default_locators_and_formatters` 

44 A method that sets default locators and formatters for an `~.axis.Axis` 

45 that uses this scale. 

46 :meth:`limit_range_for_scale` 

47 An optional method that "fixes" the axis range to acceptable values, 

48 e.g. restricting log-scaled axes to positive values. 

49 """ 

50 

51 def __init__(self, axis): 

52 r""" 

53 Construct a new scale. 

54 

55 Notes 

56 ----- 

57 The following note is for scale implementors. 

58 

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

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

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

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

63 """ 

64 

65 def get_transform(self): 

66 """ 

67 Return the `.Transform` object associated with this scale. 

68 """ 

69 raise NotImplementedError() 

70 

71 def set_default_locators_and_formatters(self, axis): 

72 """ 

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

74 this scale. 

75 """ 

76 raise NotImplementedError() 

77 

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

79 """ 

80 Return the range *vmin*, *vmax*, restricted to the 

81 domain supported by this scale (if any). 

82 

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

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

85 """ 

86 return vmin, vmax 

87 

88 

89class LinearScale(ScaleBase): 

90 """ 

91 The default linear scale. 

92 """ 

93 

94 name = 'linear' 

95 

96 def __init__(self, axis): 

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

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

99 # the docstring of Axis.set_scale. 

100 """ 

101 """ 

102 

103 def set_default_locators_and_formatters(self, axis): 

104 # docstring inherited 

105 axis.set_major_locator(AutoLocator()) 

106 axis.set_major_formatter(ScalarFormatter()) 

107 axis.set_minor_formatter(NullFormatter()) 

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

109 if (axis.axis_name == 'x' and mpl.rcParams['xtick.minor.visible'] or 

110 axis.axis_name == 'y' and mpl.rcParams['ytick.minor.visible']): 

111 axis.set_minor_locator(AutoMinorLocator()) 

112 else: 

113 axis.set_minor_locator(NullLocator()) 

114 

115 def get_transform(self): 

116 """ 

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

118 `~matplotlib.transforms.IdentityTransform`. 

119 """ 

120 return IdentityTransform() 

121 

122 

123class FuncTransform(Transform): 

124 """ 

125 A simple transform that takes and arbitrary function for the 

126 forward and inverse transform. 

127 """ 

128 

129 input_dims = output_dims = 1 

130 

131 def __init__(self, forward, inverse): 

132 """ 

133 Parameters 

134 ---------- 

135 forward : callable 

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

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

138 It must have the signature:: 

139 

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

141 

142 inverse : callable 

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

144 """ 

145 super().__init__() 

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

147 self._forward = forward 

148 self._inverse = inverse 

149 else: 

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

151 

152 def transform_non_affine(self, values): 

153 return self._forward(values) 

154 

155 def inverted(self): 

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

157 

158 

159class FuncScale(ScaleBase): 

160 """ 

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

162 """ 

163 

164 name = 'function' 

165 

166 def __init__(self, axis, functions): 

167 """ 

168 Parameters 

169 ---------- 

170 axis : `~matplotlib.axis.Axis` 

171 The axis for the scale. 

172 functions : (callable, callable) 

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

174 The forward function must be monotonic. 

175 

176 Both functions must have the signature:: 

177 

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

179 """ 

180 forward, inverse = functions 

181 transform = FuncTransform(forward, inverse) 

182 self._transform = transform 

183 

184 def get_transform(self): 

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

186 return self._transform 

187 

188 def set_default_locators_and_formatters(self, axis): 

189 # docstring inherited 

190 axis.set_major_locator(AutoLocator()) 

191 axis.set_major_formatter(ScalarFormatter()) 

192 axis.set_minor_formatter(NullFormatter()) 

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

194 if (axis.axis_name == 'x' and mpl.rcParams['xtick.minor.visible'] or 

195 axis.axis_name == 'y' and mpl.rcParams['ytick.minor.visible']): 

196 axis.set_minor_locator(AutoMinorLocator()) 

197 else: 

198 axis.set_minor_locator(NullLocator()) 

199 

200 

201class LogTransform(Transform): 

202 input_dims = output_dims = 1 

203 

204 def __init__(self, base, nonpositive='clip'): 

205 super().__init__() 

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

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

208 self.base = base 

209 self._clip = _api.check_getitem( 

210 {"clip": True, "mask": False}, nonpositive=nonpositive) 

211 

212 def __str__(self): 

213 return "{}(base={}, nonpositive={!r})".format( 

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

215 

216 def transform_non_affine(self, a): 

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

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

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

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

221 out = log(a) 

222 else: 

223 out = np.log(a) 

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

225 if self._clip: 

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

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

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

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

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

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

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

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

234 # so 1000 seems safe. 

235 out[a <= 0] = -1000 

236 return out 

237 

238 def inverted(self): 

239 return InvertedLogTransform(self.base) 

240 

241 

242class InvertedLogTransform(Transform): 

243 input_dims = output_dims = 1 

244 

245 def __init__(self, base): 

246 super().__init__() 

247 self.base = base 

248 

249 def __str__(self): 

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

251 

252 def transform_non_affine(self, a): 

253 return np.power(self.base, a) 

254 

255 def inverted(self): 

256 return LogTransform(self.base) 

257 

258 

259class LogScale(ScaleBase): 

260 """ 

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

262 """ 

263 name = 'log' 

264 

265 def __init__(self, axis, *, base=10, subs=None, nonpositive="clip"): 

266 """ 

267 Parameters 

268 ---------- 

269 axis : `~matplotlib.axis.Axis` 

270 The axis for the scale. 

271 base : float, default: 10 

272 The base of the logarithm. 

273 nonpositive : {'clip', 'mask'}, default: 'clip' 

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

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

276 subs : sequence of int, default: None 

277 Where to place the subticks between each major tick. For example, 

278 in a log10 scale, ``[2, 3, 4, 5, 6, 7, 8, 9]`` will place 8 

279 logarithmically spaced minor ticks between each major tick. 

280 """ 

281 self._transform = LogTransform(base, nonpositive) 

282 self.subs = subs 

283 

284 base = property(lambda self: self._transform.base) 

285 

286 def set_default_locators_and_formatters(self, axis): 

287 # docstring inherited 

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

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

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

291 axis.set_minor_formatter( 

292 LogFormatterSciNotation(self.base, 

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

294 

295 def get_transform(self): 

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

297 return self._transform 

298 

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

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

301 if not np.isfinite(minpos): 

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

303 

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

305 minpos if vmax <= 0 else vmax) 

306 

307 

308class FuncScaleLog(LogScale): 

309 """ 

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

311 then put on a logarithmic axes. 

312 """ 

313 

314 name = 'functionlog' 

315 

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

317 """ 

318 Parameters 

319 ---------- 

320 axis : `matplotlib.axis.Axis` 

321 The axis for the scale. 

322 functions : (callable, callable) 

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

324 The forward function must be monotonic. 

325 

326 Both functions must have the signature:: 

327 

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

329 

330 base : float, default: 10 

331 Logarithmic base of the scale. 

332 """ 

333 forward, inverse = functions 

334 self.subs = None 

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

336 

337 @property 

338 def base(self): 

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

340 

341 def get_transform(self): 

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

343 return self._transform 

344 

345 

346class SymmetricalLogTransform(Transform): 

347 input_dims = output_dims = 1 

348 

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

350 super().__init__() 

351 if base <= 1.0: 

352 raise ValueError("'base' must be larger than 1") 

353 if linthresh <= 0.0: 

354 raise ValueError("'linthresh' must be positive") 

355 if linscale <= 0.0: 

356 raise ValueError("'linscale' must be positive") 

357 self.base = base 

358 self.linthresh = linthresh 

359 self.linscale = linscale 

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

361 self._log_base = np.log(base) 

362 

363 def transform_non_affine(self, a): 

364 abs_a = np.abs(a) 

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

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

367 self._linscale_adj + 

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

369 inside = abs_a <= self.linthresh 

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

371 return out 

372 

373 def inverted(self): 

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

375 self.linscale) 

376 

377 

378class InvertedSymmetricalLogTransform(Transform): 

379 input_dims = output_dims = 1 

380 

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

382 super().__init__() 

383 symlog = SymmetricalLogTransform(base, linthresh, linscale) 

384 self.base = base 

385 self.linthresh = linthresh 

386 self.invlinthresh = symlog.transform(linthresh) 

387 self.linscale = linscale 

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

389 

390 def transform_non_affine(self, a): 

391 abs_a = np.abs(a) 

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

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

394 np.power(self.base, 

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

396 inside = abs_a <= self.invlinthresh 

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

398 return out 

399 

400 def inverted(self): 

401 return SymmetricalLogTransform(self.base, 

402 self.linthresh, self.linscale) 

403 

404 

405class SymmetricalLogScale(ScaleBase): 

406 """ 

407 The symmetrical logarithmic scale is logarithmic in both the 

408 positive and negative directions from the origin. 

409 

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

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

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

413 (-*linthresh*, *linthresh*). 

414 

415 Parameters 

416 ---------- 

417 base : float, default: 10 

418 The base of the logarithm. 

419 

420 linthresh : float, default: 2 

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

422 This avoids having the plot go to infinity around zero. 

423 

424 subs : sequence of int 

425 Where to place the subticks between each major tick. 

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

427 8 logarithmically spaced minor ticks between each major tick. 

428 

429 linscale : float, optional 

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

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

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

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

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

435 the logarithmic range. 

436 """ 

437 name = 'symlog' 

438 

439 def __init__(self, axis, *, base=10, linthresh=2, subs=None, linscale=1): 

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

441 self.subs = subs 

442 

443 base = property(lambda self: self._transform.base) 

444 linthresh = property(lambda self: self._transform.linthresh) 

445 linscale = property(lambda self: self._transform.linscale) 

446 

447 def set_default_locators_and_formatters(self, axis): 

448 # docstring inherited 

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

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

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

452 self.subs)) 

453 axis.set_minor_formatter(NullFormatter()) 

454 

455 def get_transform(self): 

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

457 return self._transform 

458 

459 

460class AsinhTransform(Transform): 

461 """Inverse hyperbolic-sine transformation used by `.AsinhScale`""" 

462 input_dims = output_dims = 1 

463 

464 def __init__(self, linear_width): 

465 super().__init__() 

466 if linear_width <= 0.0: 

467 raise ValueError("Scale parameter 'linear_width' " + 

468 "must be strictly positive") 

469 self.linear_width = linear_width 

470 

471 def transform_non_affine(self, a): 

472 return self.linear_width * np.arcsinh(a / self.linear_width) 

473 

474 def inverted(self): 

475 return InvertedAsinhTransform(self.linear_width) 

476 

477 

478class InvertedAsinhTransform(Transform): 

479 """Hyperbolic sine transformation used by `.AsinhScale`""" 

480 input_dims = output_dims = 1 

481 

482 def __init__(self, linear_width): 

483 super().__init__() 

484 self.linear_width = linear_width 

485 

486 def transform_non_affine(self, a): 

487 return self.linear_width * np.sinh(a / self.linear_width) 

488 

489 def inverted(self): 

490 return AsinhTransform(self.linear_width) 

491 

492 

493class AsinhScale(ScaleBase): 

494 """ 

495 A quasi-logarithmic scale based on the inverse hyperbolic sine (asinh) 

496 

497 For values close to zero, this is essentially a linear scale, 

498 but for large magnitude values (either positive or negative) 

499 it is asymptotically logarithmic. The transition between these 

500 linear and logarithmic regimes is smooth, and has no discontinuities 

501 in the function gradient in contrast to 

502 the `.SymmetricalLogScale` ("symlog") scale. 

503 

504 Specifically, the transformation of an axis coordinate :math:`a` is 

505 :math:`a \\rightarrow a_0 \\sinh^{-1} (a / a_0)` where :math:`a_0` 

506 is the effective width of the linear region of the transformation. 

507 In that region, the transformation is 

508 :math:`a \\rightarrow a + \\mathcal{O}(a^3)`. 

509 For large values of :math:`a` the transformation behaves as 

510 :math:`a \\rightarrow a_0 \\, \\mathrm{sgn}(a) \\ln |a| + \\mathcal{O}(1)`. 

511 

512 .. note:: 

513 

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

515 based on early user feedback. 

516 """ 

517 

518 name = 'asinh' 

519 

520 auto_tick_multipliers = { 

521 3: (2, ), 

522 4: (2, ), 

523 5: (2, ), 

524 8: (2, 4), 

525 10: (2, 5), 

526 16: (2, 4, 8), 

527 64: (4, 16), 

528 1024: (256, 512) 

529 } 

530 

531 def __init__(self, axis, *, linear_width=1.0, 

532 base=10, subs='auto', **kwargs): 

533 """ 

534 Parameters 

535 ---------- 

536 linear_width : float, default: 1 

537 The scale parameter (elsewhere referred to as :math:`a_0`) 

538 defining the extent of the quasi-linear region, 

539 and the coordinate values beyond which the transformation 

540 becomes asymptotically logarithmic. 

541 base : int, default: 10 

542 The number base used for rounding tick locations 

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

544 then rounding is to the nearest integer multiple 

545 of powers of ten. 

546 subs : sequence of int 

547 Multiples of the number base used for minor ticks. 

548 If set to 'auto', this will use built-in defaults, 

549 e.g. (2, 5) for base=10. 

550 """ 

551 super().__init__(axis) 

552 self._transform = AsinhTransform(linear_width) 

553 self._base = int(base) 

554 if subs == 'auto': 

555 self._subs = self.auto_tick_multipliers.get(self._base) 

556 else: 

557 self._subs = subs 

558 

559 linear_width = property(lambda self: self._transform.linear_width) 

560 

561 def get_transform(self): 

562 return self._transform 

563 

564 def set_default_locators_and_formatters(self, axis): 

565 axis.set(major_locator=AsinhLocator(self.linear_width, 

566 base=self._base), 

567 minor_locator=AsinhLocator(self.linear_width, 

568 base=self._base, 

569 subs=self._subs), 

570 minor_formatter=NullFormatter()) 

571 if self._base > 1: 

572 axis.set_major_formatter(LogFormatterSciNotation(self._base)) 

573 else: 

574 axis.set_major_formatter('{x:.3g}'), 

575 

576 

577class LogitTransform(Transform): 

578 input_dims = output_dims = 1 

579 

580 def __init__(self, nonpositive='mask'): 

581 super().__init__() 

582 _api.check_in_list(['mask', 'clip'], nonpositive=nonpositive) 

583 self._nonpositive = nonpositive 

584 self._clip = {"clip": True, "mask": False}[nonpositive] 

585 

586 def transform_non_affine(self, a): 

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

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

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

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

591 out[a <= 0] = -1000 

592 out[1 <= a] = 1000 

593 return out 

594 

595 def inverted(self): 

596 return LogisticTransform(self._nonpositive) 

597 

598 def __str__(self): 

599 return "{}({!r})".format(type(self).__name__, self._nonpositive) 

600 

601 

602class LogisticTransform(Transform): 

603 input_dims = output_dims = 1 

604 

605 def __init__(self, nonpositive='mask'): 

606 super().__init__() 

607 self._nonpositive = nonpositive 

608 

609 def transform_non_affine(self, a): 

610 """logistic transform (base 10)""" 

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

612 

613 def inverted(self): 

614 return LogitTransform(self._nonpositive) 

615 

616 def __str__(self): 

617 return "{}({!r})".format(type(self).__name__, self._nonpositive) 

618 

619 

620class LogitScale(ScaleBase): 

621 """ 

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

623 

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

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

626 """ 

627 name = 'logit' 

628 

629 def __init__(self, axis, nonpositive='mask', *, 

630 one_half=r"\frac{1}{2}", use_overline=False): 

631 r""" 

632 Parameters 

633 ---------- 

634 axis : `matplotlib.axis.Axis` 

635 Currently unused. 

636 nonpositive : {'mask', 'clip'} 

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

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

639 close to 0 or 1. 

640 use_overline : bool, default: False 

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

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

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

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

645 """ 

646 self._transform = LogitTransform(nonpositive) 

647 self._use_overline = use_overline 

648 self._one_half = one_half 

649 

650 def get_transform(self): 

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

652 return self._transform 

653 

654 def set_default_locators_and_formatters(self, axis): 

655 # docstring inherited 

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

657 axis.set_major_locator(LogitLocator()) 

658 axis.set_major_formatter( 

659 LogitFormatter( 

660 one_half=self._one_half, 

661 use_overline=self._use_overline 

662 ) 

663 ) 

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

665 axis.set_minor_formatter( 

666 LogitFormatter( 

667 minor=True, 

668 one_half=self._one_half, 

669 use_overline=self._use_overline 

670 ) 

671 ) 

672 

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

674 """ 

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

676 """ 

677 if not np.isfinite(minpos): 

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

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

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

681 

682 

683_scale_mapping = { 

684 'linear': LinearScale, 

685 'log': LogScale, 

686 'symlog': SymmetricalLogScale, 

687 'asinh': AsinhScale, 

688 'logit': LogitScale, 

689 'function': FuncScale, 

690 'functionlog': FuncScaleLog, 

691 } 

692 

693 

694def get_scale_names(): 

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

696 return sorted(_scale_mapping) 

697 

698 

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

700 """ 

701 Return a scale class by name. 

702 

703 Parameters 

704 ---------- 

705 scale : {%(names)s} 

706 axis : `matplotlib.axis.Axis` 

707 """ 

708 if scale != scale.lower(): 

709 _api.warn_deprecated( 

710 "3.5", message="Support for case-insensitive scales is deprecated " 

711 "since %(since)s and support will be removed %(removal)s.") 

712 scale = scale.lower() 

713 scale_cls = _api.check_getitem(_scale_mapping, scale=scale) 

714 return scale_cls(axis, **kwargs) 

715 

716 

717if scale_factory.__doc__: 

718 scale_factory.__doc__ = scale_factory.__doc__ % { 

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

720 

721 

722def register_scale(scale_class): 

723 """ 

724 Register a new kind of scale. 

725 

726 Parameters 

727 ---------- 

728 scale_class : subclass of `ScaleBase` 

729 The scale to register. 

730 """ 

731 _scale_mapping[scale_class.name] = scale_class 

732 

733 

734def _get_scale_docs(): 

735 """ 

736 Helper function for generating docstrings related to scales. 

737 """ 

738 docs = [] 

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

740 docstring = inspect.getdoc(scale_class.__init__) or "" 

741 docs.extend([ 

742 f" {name!r}", 

743 "", 

744 textwrap.indent(docstring, " " * 8), 

745 "" 

746 ]) 

747 return "\n".join(docs) 

748 

749 

750_docstring.interpd.update( 

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

752 scale_docs=_get_scale_docs().rstrip(), 

753 )