Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork7.9k
Build lognorm/symlognorm from corresponding scales.#16457
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -68,6 +68,7 @@ | ||
import base64 | ||
from collections.abc import Sized | ||
import functools | ||
import inspect | ||
import io | ||
import itertools | ||
from numbers import Number | ||
@@ -77,8 +78,7 @@ | ||
import matplotlib as mpl | ||
import numpy as np | ||
from matplotlib import cbook, docstring, scale | ||
from ._color_data import BASE_COLORS, TABLEAU_COLORS, CSS4_COLORS, XKCD_COLORS | ||
@@ -1203,60 +1203,84 @@ class DivergingNorm(TwoSlopeNorm): | ||
... | ||
def _make_norm_from_scale(scale_cls, base_norm_cls=None, *, init=None): | ||
""" | ||
Decorator for building a `.Normalize` subclass from a `.Scale` subclass. | ||
After :: | ||
@_make_norm_from_scale(scale_cls) | ||
class base_norm_cls(Normalize): | ||
... | ||
*base_norm_cls* is filled with methods so that normalization computations | ||
are forwarded to *scale_cls* (i.e., *scale_cls* is the scale that would be | ||
used for the colorbar of a mappable normalized with *base_norm_cls*). | ||
The constructor signature of *base_norm_cls* is derived from the | ||
constructor signature of *scale_cls*, but can be overridden using *init* | ||
(a callable which is *only* used for its signature). | ||
""" | ||
if base_norm_cls is None: | ||
return functools.partial(_make_norm_from_scale, scale_cls, init=init) | ||
if init is None: | ||
def init(vmin=None, vmax=None, clip=False): pass | ||
init_signature = inspect.signature(init) | ||
class Norm(base_norm_cls): | ||
def __init__(self, *args, **kwargs): | ||
ba = init_signature.bind(*args, **kwargs) | ||
ba.apply_defaults() | ||
super().__init__( | ||
**{k: ba.arguments.pop(k) for k in ["vmin", "vmax", "clip"]}) | ||
self._scale = scale_cls(axis=None, **ba.arguments) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. This looks great to me. For colorbar we will need access to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Yes. | ||
self._trf = self._scale.get_transform() | ||
self._inv_trf = self._trf.inverted() | ||
def __call__(self, value, clip=None): | ||
value, is_scalar = self.process_value(value) | ||
self.autoscale_None(value) | ||
if self.vmin > self.vmax: | ||
raise ValueError("vmin must be less or equal to vmax") | ||
if self.vmin == self.vmax: | ||
return np.full_like(value, 0) | ||
if clip is None: | ||
clip = self.clip | ||
if clip: | ||
value = np.clip(value, self.vmin, self.vmax) | ||
t_value = self._trf.transform(value).reshape(np.shape(value)) | ||
t_vmin, t_vmax = self._trf.transform([self.vmin, self.vmax]) | ||
if not np.isfinite([t_vmin, t_vmax]).all(): | ||
raise ValueError("Invalid vmin or vmax") | ||
t_value -= t_vmin | ||
t_value /= (t_vmax - t_vmin) | ||
t_value = np.ma.masked_invalid(t_value, copy=False) | ||
return t_value[0] if is_scalar else t_value | ||
def inverse(self, value): | ||
if not self.scaled(): | ||
raise ValueError("Not invertible until scaled") | ||
if self.vmin > self.vmax: | ||
raise ValueError("vmin must be less or equal to vmax") | ||
t_vmin, t_vmax = self._trf.transform([self.vmin, self.vmax]) | ||
if not np.isfinite([t_vmin, t_vmax]).all(): | ||
raise ValueError("Invalid vmin or vmax") | ||
rescaled = value * (t_vmax - t_vmin) | ||
rescaled += t_vmin | ||
return self._inv_trf.transform(rescaled).reshape(np.shape(value)) | ||
Norm.__name__ = base_norm_cls.__name__ | ||
Norm.__qualname__ = base_norm_cls.__qualname__ | ||
Norm.__module__ = base_norm_cls.__module__ | ||
return Norm | ||
@_make_norm_from_scale(functools.partial(scale.LogScale, nonpositive="mask")) | ||
class LogNorm(Normalize): | ||
"""Normalize a given value to the 0-1 range on a log scale.""" | ||
def autoscale(self, A): | ||
# docstring inherited. | ||
@@ -1267,6 +1291,10 @@ def autoscale_None(self, A): | ||
super().autoscale_None(np.ma.masked_less_equal(A, 0, copy=False)) | ||
@_make_norm_from_scale( | ||
scale.SymmetricalLogScale, | ||
init=lambda linthresh, linscale=1., vmin=None, vmax=None, clip=False, *, | ||
base=10: None) | ||
class SymLogNorm(Normalize): | ||
""" | ||
The symmetrical logarithmic scale is logarithmic in both the | ||
@@ -1276,124 +1304,29 @@ class SymLogNorm(Normalize): | ||
need to have a range around zero that is linear. The parameter | ||
*linthresh* allows the user to specify the size of this range | ||
(-*linthresh*, *linthresh*). | ||
Parameters | ||
---------- | ||
linthresh : float | ||
The range within which the plot is linear (to avoid having the plot | ||
go to infinity around zero). | ||
linscale : float, default: 1 | ||
This allows the linear range (-*linthresh* to *linthresh*) to be | ||
stretched relative to the logarithmic range. Its value is the | ||
number of decades to use for each half of the linear range. For | ||
example, when *linscale* == 1.0 (the default), the space used for | ||
the positive and negative halves of the linear range will be equal | ||
to one decade in the logarithmic range. | ||
base : float, default: 10 | ||
""" | ||
@property | ||
def linthresh(self): | ||
return self._scale.linthresh | ||
@linthresh.setter | ||
def linthresh(self, value): | ||
self._scale.linthresh = value | ||
class PowerNorm(Normalize): | ||