Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/scale.py: 45%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

282 statements  

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 implementers. 

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 """ # noqa: D419 

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 @_api.rename_parameter("3.8", "a", "values") 

217 def transform_non_affine(self, values): 

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

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

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

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

222 out = log(values) 

223 else: 

224 out = np.log(values) 

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

226 if self._clip: 

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

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

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

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

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

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

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

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

235 # so 1000 seems safe. 

236 out[values <= 0] = -1000 

237 return out 

238 

239 def inverted(self): 

240 return InvertedLogTransform(self.base) 

241 

242 

243class InvertedLogTransform(Transform): 

244 input_dims = output_dims = 1 

245 

246 def __init__(self, base): 

247 super().__init__() 

248 self.base = base 

249 

250 def __str__(self): 

251 return f"{type(self).__name__}(base={self.base})" 

252 

253 @_api.rename_parameter("3.8", "a", "values") 

254 def transform_non_affine(self, values): 

255 return np.power(self.base, values) 

256 

257 def inverted(self): 

258 return LogTransform(self.base) 

259 

260 

261class LogScale(ScaleBase): 

262 """ 

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

264 """ 

265 name = 'log' 

266 

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

268 """ 

269 Parameters 

270 ---------- 

271 axis : `~matplotlib.axis.Axis` 

272 The axis for the scale. 

273 base : float, default: 10 

274 The base of the logarithm. 

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

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

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

278 subs : sequence of int, default: None 

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

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

281 logarithmically spaced minor ticks between each major tick. 

282 """ 

283 self._transform = LogTransform(base, nonpositive) 

284 self.subs = subs 

285 

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

287 

288 def set_default_locators_and_formatters(self, axis): 

289 # docstring inherited 

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

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

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

293 axis.set_minor_formatter( 

294 LogFormatterSciNotation(self.base, 

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

296 

297 def get_transform(self): 

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

299 return self._transform 

300 

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

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

303 if not np.isfinite(minpos): 

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

305 

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

307 minpos if vmax <= 0 else vmax) 

308 

309 

310class FuncScaleLog(LogScale): 

311 """ 

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

313 then put on a logarithmic axes. 

314 """ 

315 

316 name = 'functionlog' 

317 

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

319 """ 

320 Parameters 

321 ---------- 

322 axis : `~matplotlib.axis.Axis` 

323 The axis for the scale. 

324 functions : (callable, callable) 

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

326 The forward function must be monotonic. 

327 

328 Both functions must have the signature:: 

329 

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

331 

332 base : float, default: 10 

333 Logarithmic base of the scale. 

334 """ 

335 forward, inverse = functions 

336 self.subs = None 

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

338 

339 @property 

340 def base(self): 

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

342 

343 def get_transform(self): 

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

345 return self._transform 

346 

347 

348class SymmetricalLogTransform(Transform): 

349 input_dims = output_dims = 1 

350 

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

352 super().__init__() 

353 if base <= 1.0: 

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

355 if linthresh <= 0.0: 

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

357 if linscale <= 0.0: 

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

359 self.base = base 

360 self.linthresh = linthresh 

361 self.linscale = linscale 

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

363 self._log_base = np.log(base) 

364 

365 @_api.rename_parameter("3.8", "a", "values") 

366 def transform_non_affine(self, values): 

367 abs_a = np.abs(values) 

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

369 out = np.sign(values) * self.linthresh * ( 

370 self._linscale_adj + 

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

372 inside = abs_a <= self.linthresh 

373 out[inside] = values[inside] * self._linscale_adj 

374 return out 

375 

376 def inverted(self): 

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

378 self.linscale) 

379 

380 

381class InvertedSymmetricalLogTransform(Transform): 

382 input_dims = output_dims = 1 

383 

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

385 super().__init__() 

386 symlog = SymmetricalLogTransform(base, linthresh, linscale) 

387 self.base = base 

388 self.linthresh = linthresh 

389 self.invlinthresh = symlog.transform(linthresh) 

390 self.linscale = linscale 

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

392 

393 @_api.rename_parameter("3.8", "a", "values") 

394 def transform_non_affine(self, values): 

395 abs_a = np.abs(values) 

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

397 out = np.sign(values) * self.linthresh * ( 

398 np.power(self.base, 

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

400 inside = abs_a <= self.invlinthresh 

401 out[inside] = values[inside] / self._linscale_adj 

402 return out 

403 

404 def inverted(self): 

405 return SymmetricalLogTransform(self.base, 

406 self.linthresh, self.linscale) 

407 

408 

409class SymmetricalLogScale(ScaleBase): 

410 """ 

411 The symmetrical logarithmic scale is logarithmic in both the 

412 positive and negative directions from the origin. 

413 

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

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

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

417 (-*linthresh*, *linthresh*). 

418 

419 Parameters 

420 ---------- 

421 base : float, default: 10 

422 The base of the logarithm. 

423 

424 linthresh : float, default: 2 

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

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

427 

428 subs : sequence of int 

429 Where to place the subticks between each major tick. 

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

431 8 logarithmically spaced minor ticks between each major tick. 

432 

433 linscale : float, optional 

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

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

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

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

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

439 the logarithmic range. 

440 """ 

441 name = 'symlog' 

442 

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

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

445 self.subs = subs 

446 

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

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

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

450 

451 def set_default_locators_and_formatters(self, axis): 

452 # docstring inherited 

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

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

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

456 self.subs)) 

457 axis.set_minor_formatter(NullFormatter()) 

458 

459 def get_transform(self): 

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

461 return self._transform 

462 

463 

464class AsinhTransform(Transform): 

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

466 input_dims = output_dims = 1 

467 

468 def __init__(self, linear_width): 

469 super().__init__() 

470 if linear_width <= 0.0: 

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

472 "must be strictly positive") 

473 self.linear_width = linear_width 

474 

475 @_api.rename_parameter("3.8", "a", "values") 

476 def transform_non_affine(self, values): 

477 return self.linear_width * np.arcsinh(values / self.linear_width) 

478 

479 def inverted(self): 

480 return InvertedAsinhTransform(self.linear_width) 

481 

482 

483class InvertedAsinhTransform(Transform): 

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

485 input_dims = output_dims = 1 

486 

487 def __init__(self, linear_width): 

488 super().__init__() 

489 self.linear_width = linear_width 

490 

491 @_api.rename_parameter("3.8", "a", "values") 

492 def transform_non_affine(self, values): 

493 return self.linear_width * np.sinh(values / self.linear_width) 

494 

495 def inverted(self): 

496 return AsinhTransform(self.linear_width) 

497 

498 

499class AsinhScale(ScaleBase): 

500 """ 

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

502 

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

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

505 it is asymptotically logarithmic. The transition between these 

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

507 in the function gradient in contrast to 

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

509 

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

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

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

513 In that region, the transformation is 

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

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

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

517 

518 .. note:: 

519 

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

521 based on early user feedback. 

522 """ 

523 

524 name = 'asinh' 

525 

526 auto_tick_multipliers = { 

527 3: (2, ), 

528 4: (2, ), 

529 5: (2, ), 

530 8: (2, 4), 

531 10: (2, 5), 

532 16: (2, 4, 8), 

533 64: (4, 16), 

534 1024: (256, 512) 

535 } 

536 

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

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

539 """ 

540 Parameters 

541 ---------- 

542 linear_width : float, default: 1 

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

544 defining the extent of the quasi-linear region, 

545 and the coordinate values beyond which the transformation 

546 becomes asymptotically logarithmic. 

547 base : int, default: 10 

548 The number base used for rounding tick locations 

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

550 then rounding is to the nearest integer multiple 

551 of powers of ten. 

552 subs : sequence of int 

553 Multiples of the number base used for minor ticks. 

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

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

556 """ 

557 super().__init__(axis) 

558 self._transform = AsinhTransform(linear_width) 

559 self._base = int(base) 

560 if subs == 'auto': 

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

562 else: 

563 self._subs = subs 

564 

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

566 

567 def get_transform(self): 

568 return self._transform 

569 

570 def set_default_locators_and_formatters(self, axis): 

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

572 base=self._base), 

573 minor_locator=AsinhLocator(self.linear_width, 

574 base=self._base, 

575 subs=self._subs), 

576 minor_formatter=NullFormatter()) 

577 if self._base > 1: 

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

579 else: 

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

581 

582 

583class LogitTransform(Transform): 

584 input_dims = output_dims = 1 

585 

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

587 super().__init__() 

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

589 self._nonpositive = nonpositive 

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

591 

592 @_api.rename_parameter("3.8", "a", "values") 

593 def transform_non_affine(self, values): 

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

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

596 out = np.log10(values / (1 - values)) 

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

598 out[values <= 0] = -1000 

599 out[1 <= values] = 1000 

600 return out 

601 

602 def inverted(self): 

603 return LogisticTransform(self._nonpositive) 

604 

605 def __str__(self): 

606 return f"{type(self).__name__}({self._nonpositive!r})" 

607 

608 

609class LogisticTransform(Transform): 

610 input_dims = output_dims = 1 

611 

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

613 super().__init__() 

614 self._nonpositive = nonpositive 

615 

616 @_api.rename_parameter("3.8", "a", "values") 

617 def transform_non_affine(self, values): 

618 """logistic transform (base 10)""" 

619 return 1.0 / (1 + 10**(-values)) 

620 

621 def inverted(self): 

622 return LogitTransform(self._nonpositive) 

623 

624 def __str__(self): 

625 return f"{type(self).__name__}({self._nonpositive!r})" 

626 

627 

628class LogitScale(ScaleBase): 

629 """ 

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

631 

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

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

634 """ 

635 name = 'logit' 

636 

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

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

639 r""" 

640 Parameters 

641 ---------- 

642 axis : `~matplotlib.axis.Axis` 

643 Currently unused. 

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

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

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

647 close to 0 or 1. 

648 use_overline : bool, default: False 

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

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

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

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

653 """ 

654 self._transform = LogitTransform(nonpositive) 

655 self._use_overline = use_overline 

656 self._one_half = one_half 

657 

658 def get_transform(self): 

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

660 return self._transform 

661 

662 def set_default_locators_and_formatters(self, axis): 

663 # docstring inherited 

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

665 axis.set_major_locator(LogitLocator()) 

666 axis.set_major_formatter( 

667 LogitFormatter( 

668 one_half=self._one_half, 

669 use_overline=self._use_overline 

670 ) 

671 ) 

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

673 axis.set_minor_formatter( 

674 LogitFormatter( 

675 minor=True, 

676 one_half=self._one_half, 

677 use_overline=self._use_overline 

678 ) 

679 ) 

680 

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

682 """ 

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

684 """ 

685 if not np.isfinite(minpos): 

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

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

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

689 

690 

691_scale_mapping = { 

692 'linear': LinearScale, 

693 'log': LogScale, 

694 'symlog': SymmetricalLogScale, 

695 'asinh': AsinhScale, 

696 'logit': LogitScale, 

697 'function': FuncScale, 

698 'functionlog': FuncScaleLog, 

699 } 

700 

701 

702def get_scale_names(): 

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

704 return sorted(_scale_mapping) 

705 

706 

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

708 """ 

709 Return a scale class by name. 

710 

711 Parameters 

712 ---------- 

713 scale : {%(names)s} 

714 axis : `~matplotlib.axis.Axis` 

715 """ 

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

717 return scale_cls(axis, **kwargs) 

718 

719 

720if scale_factory.__doc__: 

721 scale_factory.__doc__ = scale_factory.__doc__ % { 

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

723 

724 

725def register_scale(scale_class): 

726 """ 

727 Register a new kind of scale. 

728 

729 Parameters 

730 ---------- 

731 scale_class : subclass of `ScaleBase` 

732 The scale to register. 

733 """ 

734 _scale_mapping[scale_class.name] = scale_class 

735 

736 

737def _get_scale_docs(): 

738 """ 

739 Helper function for generating docstrings related to scales. 

740 """ 

741 docs = [] 

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

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

744 docs.extend([ 

745 f" {name!r}", 

746 "", 

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

748 "" 

749 ]) 

750 return "\n".join(docs) 

751 

752 

753_docstring.interpd.update( 

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

755 scale_docs=_get_scale_docs().rstrip(), 

756 )