[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/matplotlib/matplotlib/v3.11.2/lib/matplotlib/scale.py#L318-L356 [Back]  [Original]

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

The mapping is implemented through `.Transform` subclasses.

The following scales are built-in:

.. _builtin_scales:

============= ===================== ================================ =================================
Name          Class                 Transform                        Inverted transform
============= ===================== ================================ =================================
"asinh"       `AsinhScale`          `AsinhTransform`                 `InvertedAsinhTransform`
"function"    `FuncScale`           `FuncTransform`                  `FuncTransform`
"functionlog" `FuncScaleLog`        `FuncTransform` + `LogTransform` `InvertedLogTransform` + `FuncTransform`
"linear"      `LinearScale`         `.IdentityTransform`             `.IdentityTransform`
"log"         `LogScale`            `LogTransform`                   `InvertedLogTransform`
"logit"       `LogitScale`          `LogitTransform`                 `LogisticTransform`
"symlog"      `SymmetricalLogScale` `SymmetricalLogTransform`        `InvertedSymmetricalLogTransform`
============= ===================== ================================ =================================

A user will often only use the scale name, e.g. when setting the scale through
`~.Axes.set_xscale`: ``ax.set_xscale("log")``.

See also the :ref:`scales examples ` in the documentation.

Custom scaling can be achieved through `FuncScale`, or by creating your own
`ScaleBase` subclass and corresponding transforms (see :doc:`/gallery/scales/custom_scale`).
Third parties can register their scales by name through `register_scale`.
"""  # noqa: E501

import inspect
import textwrap
from functools import wraps

import numpy as np

import matplotlib as mpl
from matplotlib import _api, _docstring
from matplotlib.ticker import (
    NullFormatter, ScalarFormatter, LogFormatterSciNotation, LogitFormatter,
    NullLocator, LogLocator, AutoLocator, AutoMinorLocator,
    SymmetricalLogLocator, AsinhLocator, LogitLocator)
from matplotlib.transforms import Transform, IdentityTransform


class ScaleBase:
    """
    The base class for all scales.

    Scales are separable transformations, working on a single dimension.

    Subclasses should override

    :attr:`!name`
        The scale's name.
    :meth:`get_transform`
        A method returning a `.Transform`, which converts data coordinates to
        scaled coordinates.  This transform should be invertible, so that e.g.
        mouse positions can be converted back to data coordinates.
    :meth:`set_default_locators_and_formatters`
        A method that sets default locators and formatters for an `~.axis.Axis`
        that uses this scale.
    :meth:`limit_range_for_scale`
        An optional method that "fixes" the axis range to acceptable values,
        e.g. restricting log-scaled axes to positive values.
    """

    def __init__(self, axis):
        r"""
        Construct a new scale.

        Notes
        -----
        The following note is for scale implementers.

        For back-compatibility reasons, scales take an `~matplotlib.axis.Axis`
        object as the first argument.

        .. deprecated:: 3.11

           The *axis* parameter is now optional, i.e. matplotlib is compatible
           with `.ScaleBase` subclasses that do not take an *axis* parameter.

           The *axis* parameter is pending-deprecated. It will be deprecated
           in matplotlib 3.13, and removed in matplotlib 3.15.

           3rd-party scales are recommended to remove the *axis* parameter now
           if they can afford to restrict compatibility to matplotlib >= 3.11
           already. Otherwise, they may keep the *axis* parameter and remove it
           in time for matplotlib 3.13.
        """

    def get_transform(self):
        """
        Return the `.Transform` object associated with this scale.
        """
        raise NotImplementedError()

    def set_default_locators_and_formatters(self, axis):
        """
        Set the locators and formatters of *axis* to instances suitable for
        this scale.
        """
        raise NotImplementedError()

    def limit_range_for_scale(self, vmin, vmax, minpos):
        """
        Return the range *vmin*, *vmax*, restricted to the
        domain supported by this scale (if any).

        *minpos* should be the minimum positive value in the data.
        This is used by log scales to determine a minimum value.
        """
        return vmin, vmax

    def val_in_range(self, val):
        """
        Return whether the value(s) are within the valid range for this scale.

        Accepts a scalar or array-like ``val``. For a scalar, returns a
        Python ``bool``. For an array, returns a bool ndarray of the same
        shape. This is a generic implementation, and subclasses may implement
        more efficient solutions for their domain.
        """
        arr = np.asarray(val)
        with np.errstate(invalid='ignore'):
            try:
                vmin, vmax = self.limit_range_for_scale(arr, arr, minpos=1e-300)
            except (TypeError, ValueError):
                result = np.zeros(arr.shape, dtype=bool)
            else:
                result = np.isfinite(arr) & (vmin == arr) & (vmax == arr)
        return bool(result) if arr.ndim == 0 else result


def _make_axis_parameter_optional(init_func):
    """
    Decorator to allow leaving out the *axis* parameter in scale constructors.

    This decorator ensures backward compatibility for scale classes that
    previously required an *axis* parameter. It allows constructors to be
    called with or without the *axis* parameter.

    For simplicity, this does not handle the case when *axis*
    is passed as a keyword. However,
    scanning GitHub, there's no evidence that that is used anywhere.

    Parameters
    ----------
    init_func : callable
        The original __init__ method of a scale class.

    Returns
    -------
    callable
        A wrapped version of *init_func* that handles the optional *axis*.

    Notes
    -----
    If the wrapped constructor defines *axis* as its first argument, the
    parameter is preserved when present. Otherwise, the value `None` is injected
    as the first argument.

    Examples
    --------
    >>> from matplotlib.scale import ScaleBase
    >>> class CustomScale(ScaleBase):
    ...     @_make_axis_parameter_optional
    ...     def __init__(self, axis, custom_param=1):
    ...         self.custom_param = custom_param
    """
    @wraps(init_func)
    def wrapper(self, *args, **kwargs):
        sig = inspect.signature(init_func)
        try:
            # Try old signature.
            sig.bind(self, *args, **kwargs)
        except TypeError:
            # Use the new signature and pass in an unused axis=None.
            init_func(self, None, *args, **kwargs)
        else:
            # Use the old signature.
            init_func(self, *args, **kwargs)
    return wrapper


class LinearScale(ScaleBase):
    """
    The default linear scale.
    """

    name = 'linear'

    @_make_axis_parameter_optional
    def __init__(self, axis):
        # This method is present only to prevent inheritance of the base class'
        # constructor docstring, which would otherwise end up interpolated into
        # the docstring of Axis.set_scale.
        """
        """  # noqa: D419

    def set_default_locators_and_formatters(self, axis):
        # docstring inherited
        axis.set_major_locator(AutoLocator())
        axis.set_major_formatter(ScalarFormatter())
        axis.set_minor_formatter(NullFormatter())
        # update the minor locator for x and y axis based on rcParams
        if (axis.axis_name == 'x' and mpl.rcParams['xtick.minor.visible'] or
                axis.axis_name == 'y' and mpl.rcParams['ytick.minor.visible']):
            axis.set_minor_locator(AutoMinorLocator())
        else:
            axis.set_minor_locator(NullLocator())

    def get_transform(self):
        """
        Return the transform for linear scaling, which is just the
        `~matplotlib.transforms.IdentityTransform`.
        """
        return IdentityTransform()

    def val_in_range(self, val):
        """
        Return whether the value(s) are within the valid range for this scale.

        This is True for all values, except +-inf and NaN.
        """
        arr = np.asarray(val)
        result = np.isfinite(arr)
        return bool(result) if arr.ndim == 0 else result


class FuncTransform(Transform):
    """
    A simple transform that takes and arbitrary function for the
    forward and inverse transform.
    """

    input_dims = output_dims = 1

    def __init__(self, forward, inverse):
        """
        Parameters
        ----------
        forward : callable
            The forward function for the transform.  This function must have
            an inverse and, for best behavior, be monotonic.
            It must have the signature::

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

        inverse : callable
            The inverse of the forward function.  Signature as ``forward``.
        """
        super().__init__()
        if callable(forward) and callable(inverse):
            self._forward = forward
            self._inverse = inverse
        else:
            raise ValueError('arguments to FuncTransform must be functions')

    def transform_non_affine(self, values):
        return self._forward(values)

    def inverted(self):
        return FuncTransform(self._inverse, self._forward)


class FuncScale(ScaleBase):
    """
    Provide an arbitrary scale with user-supplied function for the axis.
    """

    name = 'function'

    @_make_axis_parameter_optional
    def __init__(self, axis, functions):
        """
        Parameters
        ----------
        axis : `~matplotlib.axis.Axis`
            The axis for the scale.

            .. note::
                This parameter is unused and will be removed in an imminent release.
                It can already be left out because of special preprocessing,
                so that ``FuncScale(functions)`` is valid.

        functions : (callable, callable)
            two-tuple of the forward and inverse functions for the scale.
            The forward function must be monotonic.

            Both functions must have the signature::

               def forward(values: array-like) -> array-like
        """
        forward, inverse = functions
        transform = FuncTransform(forward, inverse)
        self._transform = transform

    def get_transform(self):
        """Return the `.FuncTransform` associated with this scale."""
        return self._transform

    def set_default_locators_and_formatters(self, axis):
        # docstring inherited
        axis.set_major_locator(AutoLocator())
        axis.set_major_formatter(ScalarFormatter())
        axis.set_minor_formatter(NullFormatter())
        # update the minor locator for x and y axis based on rcParams
        if (axis.axis_name == 'x' and mpl.rcParams['xtick.minor.visible'] or
                axis.axis_name == 'y' and mpl.rcParams['ytick.minor.visible']):
            axis.set_minor_locator(AutoMinorLocator())
        else:
            axis.set_minor_locator(NullLocator())


class LogTransform(Transform):
    input_dims = output_dims = 1

    def __init__(self, base, nonpositive='clip'):
        super().__init__()
        if base 

Web Proxy Viewer  |  New URL  |  Original Page