Skip to content

Styles Module#

The styles module provides classes and functions for styling plots, including line styles, marker styles, scaling functions, and color normalization.

Styles Class#

cleopatra.styles.Styles #

A class providing line and marker styles for matplotlib plots.

This class contains collections of predefined line styles and marker styles that can be used to customize matplotlib plots. It provides static methods to retrieve these styles by name or index.

Attributes:

Name Type Description
line_styles OrderedDict

A dictionary of line style definitions, mapping style names to matplotlib line style tuples. Each tuple defines the line style pattern.

marker_style_list list

A list of marker style strings that combine line styles with markers.

Methods:

Name Description
get_line_style

Get a line style tuple by name or index.

get_marker_style

Get a marker style string by index.

Notes

Line styles define the pattern of the line (solid, dashed, dotted, etc.), while marker styles define both the line pattern and the marker shape (circle, square, triangle, etc.) used at data points.

Examples:

>>> from cleopatra.styles import Styles
>>> # Get a line style by name
>>> solid_line = Styles.get_line_style("solid")
>>> # Get a line style by index
>>> dashed_line = Styles.get_line_style(5)  # "dashed"
>>> # Get a marker style
>>> marker_style = Styles.get_marker_style(0)  # "--o"
Source code in cleopatra/styles.py
class Styles:
    """A class providing line and marker styles for matplotlib plots.

    This class contains collections of predefined line styles and marker styles
    that can be used to customize matplotlib plots. It provides static methods
    to retrieve these styles by name or index.

    Attributes
    ----------
    line_styles : OrderedDict
        A dictionary of line style definitions, mapping style names to
        matplotlib line style tuples. Each tuple defines the line style pattern.
    marker_style_list : list
        A list of marker style strings that combine line styles with markers.

    Methods
    -------
    get_line_style(style)
        Get a line style tuple by name or index.
    get_marker_style(style)
        Get a marker style string by index.

    Notes
    -----
    Line styles define the pattern of the line (solid, dashed, dotted, etc.),
    while marker styles define both the line pattern and the marker shape
    (circle, square, triangle, etc.) used at data points.

    Examples
    --------
    ```python
    >>> from cleopatra.styles import Styles
    >>> # Get a line style by name
    >>> solid_line = Styles.get_line_style("solid")
    >>> # Get a line style by index
    >>> dashed_line = Styles.get_line_style(5)  # "dashed"
    >>> # Get a marker style
    >>> marker_style = Styles.get_marker_style(0)  # "--o"

    ```
    """

    line_styles = OrderedDict(
        [
            ("solid", (0, ())),  # 0
            ("loosely dotted", (0, (1, 10))),  # 1
            ("dotted", (0, (1, 5))),  # 2
            ("densely dotted", (0, (1, 1))),  # 3
            ("loosely dashed", (0, (5, 10))),  # 4
            ("dashed", (0, (5, 5))),  # 5
            ("densely dashed", (0, (5, 1))),  # 6
            ("loosely dashdotted", (0, (3, 10, 1, 10))),  # 7
            ("dashdotted", (0, (3, 5, 1, 5))),  # 8
            ("densely dashdotted", (0, (3, 1, 1, 1))),  # 9
            ("loosely dashdotdotted", (0, (3, 10, 1, 10, 1, 10))),  # 10
            ("dashdotdotted", (0, (3, 5, 1, 5, 1, 5))),  # 11
            ("densely dashdotdotted", (0, (3, 1, 1, 1, 1, 1))),  # 12
            ("densely dashdotdottededited", (0, (6, 1, 1, 1, 1, 1))),  # 13
        ]
    )

    marker_style_list = [
        "--o",
        ":D",
        "-.H",
        "--x",
        ":v",
        "--|",
        "-+",
        "-^",
        "--s",
        "-.*",
        "-.h",
    ]

    @staticmethod
    def get_line_style(style: Union[str, int] = "loosely dotted"):
        """Get a matplotlib line style tuple by name or index.

        This method retrieves a line style tuple that can be used with matplotlib
        plotting functions to customize the appearance of lines. The style can be
        specified either by name (string) or by index (integer).

        Parameters
        ----------
        style : Union[str, int], optional
            The line style to retrieve, by default "loosely dotted".
            If a string, it should be one of the keys in the `line_styles` dictionary.
            If an integer, it should be an index into the `line_styles` dictionary.
            Available style names:
            - "solid"
            - "loosely dotted"
            - "dotted"
            - "densely dotted"
            - "loosely dashed"
            - "dashed"
            - "densely dashed"
            - "loosely dashdotted"
            - "dashdotted"
            - "densely dashdotted"
            - "loosely dashdotdotted"
            - "dashdotdotted"
            - "densely dashdotdotted"
            - "densely dashdotdottededited"

        Returns
        -------
        tuple
            A matplotlib line style tuple that can be used with plot functions.
            The tuple format is (offset, (on_off_seq)) where:
            - offset is usually 0
            - on_off_seq is a sequence of on/off lengths in points

        Raises
        ------
        KeyError
            If the style name provided does not exist in the `line_styles` dictionary.
            In this case, a message is printed and the available styles are listed.

        Examples
        --------
        Get a line style by name:
        ```python
        >>> from cleopatra.styles import Styles
        >>> solid = Styles.get_line_style("solid")
        >>> solid
        (0, ())

        ```
        Get a line style by index:
        ```python
        >>> dashed = Styles.get_line_style(5)  # "dashed"
        >>> dashed
        (0, (5, 5))

        ```
        Use a line style in a matplotlib plot:
        ```python
        >>> import matplotlib.pyplot as plt
        >>> import numpy as np
        >>> x = np.linspace(0, 10, 100)
        >>> y = np.sin(x)
        >>> plt.plot(x, y, linestyle=Styles.get_line_style("dashed"))  # doctest: +SKIP

        ```
        """
        if isinstance(style, str):
            try:
                return Styles.line_styles[style]
            except KeyError:
                msg = (
                    f" The style name you entered-{style}-does not exist please"
                    "choose from the available styles"
                )
                print(msg)
                print(list(Styles.line_styles))
        else:
            return list(Styles.line_styles.items())[style][1]

    @staticmethod
    def get_marker_style(style: int):
        """Get a matplotlib marker style string by index.

        This method retrieves a marker style string that can be used with matplotlib
        plotting functions to customize the appearance of markers and lines. The style
        is specified by an index into the `marker_style_list`.

        Parameters
        ----------
        style : int
            The index of the marker style to retrieve from the `marker_style_list`.
            If the index is out of range, it will be wrapped around using modulo
            operation to ensure a valid style is always returned.

        Returns
        -------
        str
            A matplotlib marker style string that combines line style and marker.
            Examples: "--o" (dashed line with circle markers), ":D" (dotted line with
            diamond markers), etc.

        Notes
        -----
        The marker style strings use matplotlib's shorthand notation:
        - Line styles: "-" (solid), "--" (dashed), "-." (dash-dot), ":" (dotted)
        - Markers: "o" (circle), "D" (diamond), "s" (square), "^" (triangle up), etc.

        Examples
        --------
        Get a marker style by index:
        ```python
        >>> from cleopatra.styles import Styles
        >>> # Get the first marker style
        >>> style0 = Styles.get_marker_style(0)
        >>> style0
        '--o'

        >>> # Get another marker style
        >>> style1 = Styles.get_marker_style(1)
        >>> style1
        ':D'

        ```
        Handle index out of range (wraps around):
        ```python
        >>> # If we have 11 styles and request index 15, we get style at index 15 % 11 = 4
        >>> len(Styles.marker_style_list)
        11
        >>> style15 = Styles.get_marker_style(15)  # Same as style4
        >>> style4 = Styles.get_marker_style(4)
        >>> style15 == style4
        True

        ```
        Use a marker style in a matplotlib plot:
        ```python
        >>> import matplotlib.pyplot as plt
        >>> import numpy as np
        >>> x = np.linspace(0, 10, 20)
        >>> y = np.sin(x)
        >>> plt.plot(x, y, Styles.get_marker_style(0))  # doctest: +SKIP

        ```
        """
        if style > len(Styles.marker_style_list) - 1:
            style = style % len(Styles.marker_style_list)
        return Styles.marker_style_list[style]

get_line_style(style='loosely dotted') staticmethod #

Get a matplotlib line style tuple by name or index.

This method retrieves a line style tuple that can be used with matplotlib plotting functions to customize the appearance of lines. The style can be specified either by name (string) or by index (integer).

Parameters:

Name Type Description Default
style Union[str, int]

The line style to retrieve, by default "loosely dotted". If a string, it should be one of the keys in the line_styles dictionary. If an integer, it should be an index into the line_styles dictionary. Available style names: - "solid" - "loosely dotted" - "dotted" - "densely dotted" - "loosely dashed" - "dashed" - "densely dashed" - "loosely dashdotted" - "dashdotted" - "densely dashdotted" - "loosely dashdotdotted" - "dashdotdotted" - "densely dashdotdotted" - "densely dashdotdottededited"

'loosely dotted'

Returns:

Type Description
tuple

A matplotlib line style tuple that can be used with plot functions. The tuple format is (offset, (on_off_seq)) where: - offset is usually 0 - on_off_seq is a sequence of on/off lengths in points

Raises:

Type Description
KeyError

If the style name provided does not exist in the line_styles dictionary. In this case, a message is printed and the available styles are listed.

Examples:

Get a line style by name:

>>> from cleopatra.styles import Styles
>>> solid = Styles.get_line_style("solid")
>>> solid
(0, ())
Get a line style by index:
>>> dashed = Styles.get_line_style(5)  # "dashed"
>>> dashed
(0, (5, 5))
Use a line style in a matplotlib plot:
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> x = np.linspace(0, 10, 100)
>>> y = np.sin(x)
>>> plt.plot(x, y, linestyle=Styles.get_line_style("dashed"))  # doctest: +SKIP

Source code in cleopatra/styles.py
@staticmethod
def get_line_style(style: Union[str, int] = "loosely dotted"):
    """Get a matplotlib line style tuple by name or index.

    This method retrieves a line style tuple that can be used with matplotlib
    plotting functions to customize the appearance of lines. The style can be
    specified either by name (string) or by index (integer).

    Parameters
    ----------
    style : Union[str, int], optional
        The line style to retrieve, by default "loosely dotted".
        If a string, it should be one of the keys in the `line_styles` dictionary.
        If an integer, it should be an index into the `line_styles` dictionary.
        Available style names:
        - "solid"
        - "loosely dotted"
        - "dotted"
        - "densely dotted"
        - "loosely dashed"
        - "dashed"
        - "densely dashed"
        - "loosely dashdotted"
        - "dashdotted"
        - "densely dashdotted"
        - "loosely dashdotdotted"
        - "dashdotdotted"
        - "densely dashdotdotted"
        - "densely dashdotdottededited"

    Returns
    -------
    tuple
        A matplotlib line style tuple that can be used with plot functions.
        The tuple format is (offset, (on_off_seq)) where:
        - offset is usually 0
        - on_off_seq is a sequence of on/off lengths in points

    Raises
    ------
    KeyError
        If the style name provided does not exist in the `line_styles` dictionary.
        In this case, a message is printed and the available styles are listed.

    Examples
    --------
    Get a line style by name:
    ```python
    >>> from cleopatra.styles import Styles
    >>> solid = Styles.get_line_style("solid")
    >>> solid
    (0, ())

    ```
    Get a line style by index:
    ```python
    >>> dashed = Styles.get_line_style(5)  # "dashed"
    >>> dashed
    (0, (5, 5))

    ```
    Use a line style in a matplotlib plot:
    ```python
    >>> import matplotlib.pyplot as plt
    >>> import numpy as np
    >>> x = np.linspace(0, 10, 100)
    >>> y = np.sin(x)
    >>> plt.plot(x, y, linestyle=Styles.get_line_style("dashed"))  # doctest: +SKIP

    ```
    """
    if isinstance(style, str):
        try:
            return Styles.line_styles[style]
        except KeyError:
            msg = (
                f" The style name you entered-{style}-does not exist please"
                "choose from the available styles"
            )
            print(msg)
            print(list(Styles.line_styles))
    else:
        return list(Styles.line_styles.items())[style][1]

get_marker_style(style) staticmethod #

Get a matplotlib marker style string by index.

This method retrieves a marker style string that can be used with matplotlib plotting functions to customize the appearance of markers and lines. The style is specified by an index into the marker_style_list.

Parameters:

Name Type Description Default
style int

The index of the marker style to retrieve from the marker_style_list. If the index is out of range, it will be wrapped around using modulo operation to ensure a valid style is always returned.

required

Returns:

Type Description
str

A matplotlib marker style string that combines line style and marker. Examples: "--o" (dashed line with circle markers), ":D" (dotted line with diamond markers), etc.

Notes

The marker style strings use matplotlib's shorthand notation: - Line styles: "-" (solid), "--" (dashed), "-." (dash-dot), ":" (dotted) - Markers: "o" (circle), "D" (diamond), "s" (square), "^" (triangle up), etc.

Examples:

Get a marker style by index:

>>> from cleopatra.styles import Styles
>>> # Get the first marker style
>>> style0 = Styles.get_marker_style(0)
>>> style0
'--o'

>>> # Get another marker style
>>> style1 = Styles.get_marker_style(1)
>>> style1
':D'
Handle index out of range (wraps around):
>>> # If we have 11 styles and request index 15, we get style at index 15 % 11 = 4
>>> len(Styles.marker_style_list)
11
>>> style15 = Styles.get_marker_style(15)  # Same as style4
>>> style4 = Styles.get_marker_style(4)
>>> style15 == style4
True
Use a marker style in a matplotlib plot:
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>> x = np.linspace(0, 10, 20)
>>> y = np.sin(x)
>>> plt.plot(x, y, Styles.get_marker_style(0))  # doctest: +SKIP

Source code in cleopatra/styles.py
@staticmethod
def get_marker_style(style: int):
    """Get a matplotlib marker style string by index.

    This method retrieves a marker style string that can be used with matplotlib
    plotting functions to customize the appearance of markers and lines. The style
    is specified by an index into the `marker_style_list`.

    Parameters
    ----------
    style : int
        The index of the marker style to retrieve from the `marker_style_list`.
        If the index is out of range, it will be wrapped around using modulo
        operation to ensure a valid style is always returned.

    Returns
    -------
    str
        A matplotlib marker style string that combines line style and marker.
        Examples: "--o" (dashed line with circle markers), ":D" (dotted line with
        diamond markers), etc.

    Notes
    -----
    The marker style strings use matplotlib's shorthand notation:
    - Line styles: "-" (solid), "--" (dashed), "-." (dash-dot), ":" (dotted)
    - Markers: "o" (circle), "D" (diamond), "s" (square), "^" (triangle up), etc.

    Examples
    --------
    Get a marker style by index:
    ```python
    >>> from cleopatra.styles import Styles
    >>> # Get the first marker style
    >>> style0 = Styles.get_marker_style(0)
    >>> style0
    '--o'

    >>> # Get another marker style
    >>> style1 = Styles.get_marker_style(1)
    >>> style1
    ':D'

    ```
    Handle index out of range (wraps around):
    ```python
    >>> # If we have 11 styles and request index 15, we get style at index 15 % 11 = 4
    >>> len(Styles.marker_style_list)
    11
    >>> style15 = Styles.get_marker_style(15)  # Same as style4
    >>> style4 = Styles.get_marker_style(4)
    >>> style15 == style4
    True

    ```
    Use a marker style in a matplotlib plot:
    ```python
    >>> import matplotlib.pyplot as plt
    >>> import numpy as np
    >>> x = np.linspace(0, 10, 20)
    >>> y = np.sin(x)
    >>> plt.plot(x, y, Styles.get_marker_style(0))  # doctest: +SKIP

    ```
    """
    if style > len(Styles.marker_style_list) - 1:
        style = style % len(Styles.marker_style_list)
    return Styles.marker_style_list[style]

Scale Class#

cleopatra.styles.Scale #

A class providing various scaling functions for data visualization.

This class contains static methods for different types of scaling operations that can be used to transform data values for visualization purposes. These include logarithmic scaling, power scaling, identity scaling, and general value rescaling between different ranges.

Methods:

Name Description
log_scale

Apply logarithmic (base 10) scaling to a value.

power_scale

Create a power scaling function based on a minimum value.

identity_scale

Create an identity scaling function that always returns 2.

rescale

Rescale a value from one range to another.

Notes

Scaling functions are useful for transforming data to improve visualization, especially when dealing with data that spans multiple orders of magnitude or needs to be normalized to a specific range.

Examples:

Apply logarithmic scaling:

>>> from cleopatra.styles import Scale
>>> Scale.log_scale(100)
np.float64(2.0)
>>> Scale.log_scale(1000)
np.float64(3.0)
Rescale a value from one range to another:
>>> Scale.rescale(5, 0, 10, 0, 100)  # 5 is 50% of [0,10], so 50% of [0,100] is 50
50.0
>>> Scale.rescale(75, 0, 100, -1, 1)  # 75 is 75% of [0,100], so 75% of [-1,1] is 0.5
0.5

Source code in cleopatra/styles.py
class Scale:
    """A class providing various scaling functions for data visualization.

    This class contains static methods for different types of scaling operations
    that can be used to transform data values for visualization purposes. These
    include logarithmic scaling, power scaling, identity scaling, and general
    value rescaling between different ranges.

    Methods
    -------
    log_scale(val)
        Apply logarithmic (base 10) scaling to a value.
    power_scale(min_val)
        Create a power scaling function based on a minimum value.
    identity_scale(min_val, max_val)
        Create an identity scaling function that always returns 2.
    rescale(old_value, old_min, old_max, new_min, new_max)
        Rescale a value from one range to another.

    Notes
    -----
    Scaling functions are useful for transforming data to improve visualization,
    especially when dealing with data that spans multiple orders of magnitude or
    needs to be normalized to a specific range.

    Examples
    --------
    Apply logarithmic scaling:
    ```python
    >>> from cleopatra.styles import Scale
    >>> Scale.log_scale(100)
    np.float64(2.0)
    >>> Scale.log_scale(1000)
    np.float64(3.0)

    ```
    Rescale a value from one range to another:
    ```python
    >>> Scale.rescale(5, 0, 10, 0, 100)  # 5 is 50% of [0,10], so 50% of [0,100] is 50
    50.0
    >>> Scale.rescale(75, 0, 100, -1, 1)  # 75 is 75% of [0,100], so 75% of [-1,1] is 0.5
    0.5

    ```
    """

    def __init__(self):
        """Initialize a Scale object.

        Note that this class is primarily intended to be used via its static methods,
        so initialization is not typically necessary.
        """
        pass

    @staticmethod
    def log_scale(val):
        """Apply logarithmic (base 10) scaling to a value or array.

        This method computes the base-10 logarithm of the input value(s),
        which is useful for visualizing data that spans multiple orders of magnitude.

        Parameters
        ----------
        val : float or numpy.ndarray
            The value or array of values to be logarithmically scaled.
            Must be positive (greater than 0) to avoid math domain errors.

        Returns
        -------
        float or numpy.ndarray
            The base-10 logarithm of the input value(s).
            If the input is an array, the output will be an array of the same shape.

        Notes
        -----
        Logarithmic scaling is particularly useful for:
        - Data that spans multiple orders of magnitude
        - Compressing wide ranges of values into a more manageable range
        - Visualizing exponential growth or decay

        Examples
        --------
        Scale a single value:
        ```python
        >>> from cleopatra.styles import Scale
        >>> Scale.log_scale(100)
        np.float64(2.0)
        >>> Scale.log_scale(1000)
        np.float64(3.0)

        ```
        Scale an array of values:
        ```python
        >>> import numpy as np
        >>> values = np.array([1, 10, 100, 1000])
        >>> Scale.log_scale(values)
        array([0., 1., 2., 3.])

        ```
        """
        return np.log10(val)

    @staticmethod
    def power_scale(min_val) -> callable:
        """Create a power scaling function based on a minimum value.

        This method returns a function that applies power scaling to its input.
        The scaling function first shifts the input value by adding the absolute
        value of the minimum value plus 1 (to ensure positive values), then
        divides by 1000 and squares the result.

        Parameters
        ----------
        min_val : float
            The minimum value in the data range. Used to shift the data to ensure
            all values are positive before applying the power transformation.

        Returns
        -------
        callable
            A function that takes a value or array and returns the power-scaled result.
            The returned function has the signature: f(val) -> float or numpy.ndarray

        Notes
        -----
        Power scaling is useful for:
        - Emphasizing differences in smaller values
        - Compressing the range of larger values
        - Creating non-linear visualizations where small changes in small values
          are more important than small changes in large values

        Examples
        --------
        Create a power scaling function and apply it to values:
        ```python
        >>> from cleopatra.styles import Scale
        >>> # Create a scaling function with minimum value -10
        >>> scale_func = Scale.power_scale(-10)
        >>> # Apply to a single value
        >>> scale_func(5)  # (5 + |-10| + 1) / 1000)^2 = (5 + 10 + 1)^2 / 1000000 = 16^2 / 1000000 = 256 / 1000000 = 0.000256
        0.000256
        >>> # Apply to another value
        >>> scale_func(100)  # (100 + |-10| + 1) / 1000)^2 = (100 + 10 + 1)^2 / 1000000 = 111^2 / 1000000 = 12321 / 1000000 ≈ 0.012321
        0.012321

        ```
        Apply to an array of values:
        ```python
        >>> import numpy as np
        >>> values = np.array([0, 10, 100])
        >>> scale_func = Scale.power_scale(-5)
        >>> scale_func(values)  # doctest: +ELLIPSIS
        array([3.6000e-05, 2.5600e-04, 1.1236e-02])

        >>> # [(0+5+1)/1000]^2, [(10+5+1)/1000]^2, [(100+5+1)/1000]^2]
        ```
        """

        def scalar(val):
            val = val + abs(min_val) + 1
            return (val / 1000) ** 2

        return scalar

    @staticmethod
    def identity_scale(min_val, max_val):
        """Create a constant scaling function that always returns 2.

        This method returns a function that ignores its input and always returns
        the constant value 2. Despite its name, this is not a true identity function
        (which would return the input unchanged), but rather a constant function.

        Parameters
        ----------
        min_val : float
            The minimum value in the data range. This parameter is not used in the
            implementation but is included for API consistency with other scaling methods.
        max_val : float
            The maximum value in the data range. This parameter is not used in the
            implementation but is included for API consistency with other scaling methods.

        Returns
        -------
        callable
            A function that takes any input and always returns 2.
            The returned function has the signature: f(val) -> int

        Notes
        -----
        This function can be useful in situations where:
        - A constant size or value is needed regardless of the input data
        - A placeholder scaling function is required
        - Testing or debugging code that expects a scaling function

        Examples
        --------
        Create and use the constant scaling function:
        ```python
        >>> from cleopatra.styles import Scale
        >>> scale_func = Scale.identity_scale(0, 100)  # min_val and max_val are ignored
        >>> scale_func(5)  # Returns 2 regardless of input
        2
        >>> scale_func(100)  # Still returns 2
        2
        >>> scale_func(-10)  # Still returns 2
        2

        ```
        Works with arrays too, but returns a scalar, not an array:
        ```python
        >>> import numpy as np
        >>> values = np.array([1, 2, 3, 4, 5])
        >>> scale_func(values)  # Returns scalar 2, not an array of 2s
        2

        ```
        """

        def scalar(val):
            return 2

        return scalar

    @staticmethod
    def rescale(old_value, old_min, old_max, new_min, new_max):
        """Rescale a value from one range to another.

        This method performs linear rescaling of a value from an original range
        [old_min, old_max] to a new range [new_min, new_max]. The transformation
        preserves the relative position of the value within its range.

        Parameters
        ----------
        old_value : float or numpy.ndarray
            The value(s) to be rescaled. Can be a single value or an array.
        old_min : float
            The minimum value of the original range.
        old_max : float
            The maximum value of the original range.
        new_min : float
            The minimum value of the target range.
        new_max : float
            The maximum value of the target range.

        Returns
        -------
        float or numpy.ndarray
            The rescaled value(s) in the new range. If the input is an array,
            the output will be an array of the same shape.

        Notes
        -----
        The rescaling formula is:
        new_value = (((old_value - old_min) * (new_max - new_min)) / (old_max - old_min)) + new_min

        This function is useful for:
        - Normalizing data to a specific range (e.g., [0, 1])
        - Converting between different units or scales
        - Preparing data for visualization with specific bounds

        Examples
        --------
        Rescale a value from [0, 10] to [0, 100]:
        ```python
        >>> from cleopatra.styles import Scale
        >>> Scale.rescale(5, 0, 10, 0, 100)  # 5 is 50% of [0,10], so 50% of [0,100] is 50
        50.0

        ```
        Rescale a value from [0, 100] to [-1, 1]:
        ```python
        >>> Scale.rescale(75, 0, 100, -1, 1)  # 75 is 75% of [0,100], so 75% of [-1,1] is 0.5
        0.5

        ```
        Rescale an array of values:
        ```python
        >>> import numpy as np
        >>> values = np.array([0, 5, 10])
        >>> Scale.rescale(values, 0, 10, 0, 1)  # Normalize to [0,1]
        array([0. , 0.5, 1. ])

        ```
        Invert a range by swapping the new min and max:
        ```python
        >>> Scale.rescale(25, 0, 100, 1, 0)  # 25 is 25% from min, so 25% from max in new range is 0.75
        0.75

        ```
        """
        old_range = old_max - old_min
        new_range = new_max - new_min
        new_value = (((old_value - old_min) * new_range) / old_range) + new_min

        return new_value

__init__() #

Initialize a Scale object.

Note that this class is primarily intended to be used via its static methods, so initialization is not typically necessary.

Source code in cleopatra/styles.py
def __init__(self):
    """Initialize a Scale object.

    Note that this class is primarily intended to be used via its static methods,
    so initialization is not typically necessary.
    """
    pass

identity_scale(min_val, max_val) staticmethod #

Create a constant scaling function that always returns 2.

This method returns a function that ignores its input and always returns the constant value 2. Despite its name, this is not a true identity function (which would return the input unchanged), but rather a constant function.

Parameters:

Name Type Description Default
min_val float

The minimum value in the data range. This parameter is not used in the implementation but is included for API consistency with other scaling methods.

required
max_val float

The maximum value in the data range. This parameter is not used in the implementation but is included for API consistency with other scaling methods.

required

Returns:

Type Description
callable

A function that takes any input and always returns 2. The returned function has the signature: f(val) -> int

Notes

This function can be useful in situations where: - A constant size or value is needed regardless of the input data - A placeholder scaling function is required - Testing or debugging code that expects a scaling function

Examples:

Create and use the constant scaling function:

>>> from cleopatra.styles import Scale
>>> scale_func = Scale.identity_scale(0, 100)  # min_val and max_val are ignored
>>> scale_func(5)  # Returns 2 regardless of input
2
>>> scale_func(100)  # Still returns 2
2
>>> scale_func(-10)  # Still returns 2
2
Works with arrays too, but returns a scalar, not an array:
>>> import numpy as np
>>> values = np.array([1, 2, 3, 4, 5])
>>> scale_func(values)  # Returns scalar 2, not an array of 2s
2

Source code in cleopatra/styles.py
@staticmethod
def identity_scale(min_val, max_val):
    """Create a constant scaling function that always returns 2.

    This method returns a function that ignores its input and always returns
    the constant value 2. Despite its name, this is not a true identity function
    (which would return the input unchanged), but rather a constant function.

    Parameters
    ----------
    min_val : float
        The minimum value in the data range. This parameter is not used in the
        implementation but is included for API consistency with other scaling methods.
    max_val : float
        The maximum value in the data range. This parameter is not used in the
        implementation but is included for API consistency with other scaling methods.

    Returns
    -------
    callable
        A function that takes any input and always returns 2.
        The returned function has the signature: f(val) -> int

    Notes
    -----
    This function can be useful in situations where:
    - A constant size or value is needed regardless of the input data
    - A placeholder scaling function is required
    - Testing or debugging code that expects a scaling function

    Examples
    --------
    Create and use the constant scaling function:
    ```python
    >>> from cleopatra.styles import Scale
    >>> scale_func = Scale.identity_scale(0, 100)  # min_val and max_val are ignored
    >>> scale_func(5)  # Returns 2 regardless of input
    2
    >>> scale_func(100)  # Still returns 2
    2
    >>> scale_func(-10)  # Still returns 2
    2

    ```
    Works with arrays too, but returns a scalar, not an array:
    ```python
    >>> import numpy as np
    >>> values = np.array([1, 2, 3, 4, 5])
    >>> scale_func(values)  # Returns scalar 2, not an array of 2s
    2

    ```
    """

    def scalar(val):
        return 2

    return scalar

log_scale(val) staticmethod #

Apply logarithmic (base 10) scaling to a value or array.

This method computes the base-10 logarithm of the input value(s), which is useful for visualizing data that spans multiple orders of magnitude.

Parameters:

Name Type Description Default
val float or ndarray

The value or array of values to be logarithmically scaled. Must be positive (greater than 0) to avoid math domain errors.

required

Returns:

Type Description
float or ndarray

The base-10 logarithm of the input value(s). If the input is an array, the output will be an array of the same shape.

Notes

Logarithmic scaling is particularly useful for: - Data that spans multiple orders of magnitude - Compressing wide ranges of values into a more manageable range - Visualizing exponential growth or decay

Examples:

Scale a single value:

>>> from cleopatra.styles import Scale
>>> Scale.log_scale(100)
np.float64(2.0)
>>> Scale.log_scale(1000)
np.float64(3.0)
Scale an array of values:
>>> import numpy as np
>>> values = np.array([1, 10, 100, 1000])
>>> Scale.log_scale(values)
array([0., 1., 2., 3.])

Source code in cleopatra/styles.py
@staticmethod
def log_scale(val):
    """Apply logarithmic (base 10) scaling to a value or array.

    This method computes the base-10 logarithm of the input value(s),
    which is useful for visualizing data that spans multiple orders of magnitude.

    Parameters
    ----------
    val : float or numpy.ndarray
        The value or array of values to be logarithmically scaled.
        Must be positive (greater than 0) to avoid math domain errors.

    Returns
    -------
    float or numpy.ndarray
        The base-10 logarithm of the input value(s).
        If the input is an array, the output will be an array of the same shape.

    Notes
    -----
    Logarithmic scaling is particularly useful for:
    - Data that spans multiple orders of magnitude
    - Compressing wide ranges of values into a more manageable range
    - Visualizing exponential growth or decay

    Examples
    --------
    Scale a single value:
    ```python
    >>> from cleopatra.styles import Scale
    >>> Scale.log_scale(100)
    np.float64(2.0)
    >>> Scale.log_scale(1000)
    np.float64(3.0)

    ```
    Scale an array of values:
    ```python
    >>> import numpy as np
    >>> values = np.array([1, 10, 100, 1000])
    >>> Scale.log_scale(values)
    array([0., 1., 2., 3.])

    ```
    """
    return np.log10(val)

power_scale(min_val) staticmethod #

Create a power scaling function based on a minimum value.

This method returns a function that applies power scaling to its input. The scaling function first shifts the input value by adding the absolute value of the minimum value plus 1 (to ensure positive values), then divides by 1000 and squares the result.

Parameters:

Name Type Description Default
min_val float

The minimum value in the data range. Used to shift the data to ensure all values are positive before applying the power transformation.

required

Returns:

Type Description
callable

A function that takes a value or array and returns the power-scaled result. The returned function has the signature: f(val) -> float or numpy.ndarray

Notes

Power scaling is useful for: - Emphasizing differences in smaller values - Compressing the range of larger values - Creating non-linear visualizations where small changes in small values are more important than small changes in large values

Examples:

Create a power scaling function and apply it to values:

>>> from cleopatra.styles import Scale
>>> # Create a scaling function with minimum value -10
>>> scale_func = Scale.power_scale(-10)
>>> # Apply to a single value
>>> scale_func(5)  # (5 + |-10| + 1) / 1000)^2 = (5 + 10 + 1)^2 / 1000000 = 16^2 / 1000000 = 256 / 1000000 = 0.000256
0.000256
>>> # Apply to another value
>>> scale_func(100)  # (100 + |-10| + 1) / 1000)^2 = (100 + 10 + 1)^2 / 1000000 = 111^2 / 1000000 = 12321 / 1000000 ≈ 0.012321
0.012321
Apply to an array of values:
>>> import numpy as np
>>> values = np.array([0, 10, 100])
>>> scale_func = Scale.power_scale(-5)
>>> scale_func(values)  # doctest: +ELLIPSIS
array([3.6000e-05, 2.5600e-04, 1.1236e-02])

>>> # [(0+5+1)/1000]^2, [(10+5+1)/1000]^2, [(100+5+1)/1000]^2]

Source code in cleopatra/styles.py
@staticmethod
def power_scale(min_val) -> callable:
    """Create a power scaling function based on a minimum value.

    This method returns a function that applies power scaling to its input.
    The scaling function first shifts the input value by adding the absolute
    value of the minimum value plus 1 (to ensure positive values), then
    divides by 1000 and squares the result.

    Parameters
    ----------
    min_val : float
        The minimum value in the data range. Used to shift the data to ensure
        all values are positive before applying the power transformation.

    Returns
    -------
    callable
        A function that takes a value or array and returns the power-scaled result.
        The returned function has the signature: f(val) -> float or numpy.ndarray

    Notes
    -----
    Power scaling is useful for:
    - Emphasizing differences in smaller values
    - Compressing the range of larger values
    - Creating non-linear visualizations where small changes in small values
      are more important than small changes in large values

    Examples
    --------
    Create a power scaling function and apply it to values:
    ```python
    >>> from cleopatra.styles import Scale
    >>> # Create a scaling function with minimum value -10
    >>> scale_func = Scale.power_scale(-10)
    >>> # Apply to a single value
    >>> scale_func(5)  # (5 + |-10| + 1) / 1000)^2 = (5 + 10 + 1)^2 / 1000000 = 16^2 / 1000000 = 256 / 1000000 = 0.000256
    0.000256
    >>> # Apply to another value
    >>> scale_func(100)  # (100 + |-10| + 1) / 1000)^2 = (100 + 10 + 1)^2 / 1000000 = 111^2 / 1000000 = 12321 / 1000000 ≈ 0.012321
    0.012321

    ```
    Apply to an array of values:
    ```python
    >>> import numpy as np
    >>> values = np.array([0, 10, 100])
    >>> scale_func = Scale.power_scale(-5)
    >>> scale_func(values)  # doctest: +ELLIPSIS
    array([3.6000e-05, 2.5600e-04, 1.1236e-02])

    >>> # [(0+5+1)/1000]^2, [(10+5+1)/1000]^2, [(100+5+1)/1000]^2]
    ```
    """

    def scalar(val):
        val = val + abs(min_val) + 1
        return (val / 1000) ** 2

    return scalar

rescale(old_value, old_min, old_max, new_min, new_max) staticmethod #

Rescale a value from one range to another.

This method performs linear rescaling of a value from an original range [old_min, old_max] to a new range [new_min, new_max]. The transformation preserves the relative position of the value within its range.

Parameters:

Name Type Description Default
old_value float or ndarray

The value(s) to be rescaled. Can be a single value or an array.

required
old_min float

The minimum value of the original range.

required
old_max float

The maximum value of the original range.

required
new_min float

The minimum value of the target range.

required
new_max float

The maximum value of the target range.

required

Returns:

Type Description
float or ndarray

The rescaled value(s) in the new range. If the input is an array, the output will be an array of the same shape.

Notes

The rescaling formula is: new_value = (((old_value - old_min) * (new_max - new_min)) / (old_max - old_min)) + new_min

This function is useful for: - Normalizing data to a specific range (e.g., [0, 1]) - Converting between different units or scales - Preparing data for visualization with specific bounds

Examples:

Rescale a value from [0, 10] to [0, 100]:

>>> from cleopatra.styles import Scale
>>> Scale.rescale(5, 0, 10, 0, 100)  # 5 is 50% of [0,10], so 50% of [0,100] is 50
50.0
Rescale a value from [0, 100] to [-1, 1]:
>>> Scale.rescale(75, 0, 100, -1, 1)  # 75 is 75% of [0,100], so 75% of [-1,1] is 0.5
0.5
Rescale an array of values:
>>> import numpy as np
>>> values = np.array([0, 5, 10])
>>> Scale.rescale(values, 0, 10, 0, 1)  # Normalize to [0,1]
array([0. , 0.5, 1. ])
Invert a range by swapping the new min and max:
>>> Scale.rescale(25, 0, 100, 1, 0)  # 25 is 25% from min, so 25% from max in new range is 0.75
0.75

Source code in cleopatra/styles.py
@staticmethod
def rescale(old_value, old_min, old_max, new_min, new_max):
    """Rescale a value from one range to another.

    This method performs linear rescaling of a value from an original range
    [old_min, old_max] to a new range [new_min, new_max]. The transformation
    preserves the relative position of the value within its range.

    Parameters
    ----------
    old_value : float or numpy.ndarray
        The value(s) to be rescaled. Can be a single value or an array.
    old_min : float
        The minimum value of the original range.
    old_max : float
        The maximum value of the original range.
    new_min : float
        The minimum value of the target range.
    new_max : float
        The maximum value of the target range.

    Returns
    -------
    float or numpy.ndarray
        The rescaled value(s) in the new range. If the input is an array,
        the output will be an array of the same shape.

    Notes
    -----
    The rescaling formula is:
    new_value = (((old_value - old_min) * (new_max - new_min)) / (old_max - old_min)) + new_min

    This function is useful for:
    - Normalizing data to a specific range (e.g., [0, 1])
    - Converting between different units or scales
    - Preparing data for visualization with specific bounds

    Examples
    --------
    Rescale a value from [0, 10] to [0, 100]:
    ```python
    >>> from cleopatra.styles import Scale
    >>> Scale.rescale(5, 0, 10, 0, 100)  # 5 is 50% of [0,10], so 50% of [0,100] is 50
    50.0

    ```
    Rescale a value from [0, 100] to [-1, 1]:
    ```python
    >>> Scale.rescale(75, 0, 100, -1, 1)  # 75 is 75% of [0,100], so 75% of [-1,1] is 0.5
    0.5

    ```
    Rescale an array of values:
    ```python
    >>> import numpy as np
    >>> values = np.array([0, 5, 10])
    >>> Scale.rescale(values, 0, 10, 0, 1)  # Normalize to [0,1]
    array([0. , 0.5, 1. ])

    ```
    Invert a range by swapping the new min and max:
    ```python
    >>> Scale.rescale(25, 0, 100, 1, 0)  # 25 is 25% from min, so 25% from max in new range is 0.75
    0.75

    ```
    """
    old_range = old_max - old_min
    new_range = new_max - new_min
    new_value = (((old_value - old_min) * new_range) / old_range) + new_min

    return new_value

MidpointNormalize Class#

cleopatra.styles.MidpointNormalize #

Bases: Normalize

A normalization class that scales data with a midpoint.

This class extends matplotlib's Normalize class to create a colormap normalization that has a fixed midpoint. This is useful for data that has a natural midpoint (like zero) where the colormap should be centered, regardless of the actual data range.

The normalization maps values to the range [0, 1] with the midpoint mapped to 0.5, which allows for symmetric colormaps to be properly centered.

Parameters:

Name Type Description Default
vmin float

The minimum data value that corresponds to 0 in the normalized data. If None, it is automatically calculated from the data.

None
vmax float

The maximum data value that corresponds to 1 in the normalized data. If None, it is automatically calculated from the data.

None
midpoint float

The data value that corresponds to 0.5 in the normalized data. If None, it defaults to the midpoint between vmin and vmax.

None
clip bool

If True, values outside the [vmin, vmax] range are clipped to be within that range, by default False.

False

Attributes:

Name Type Description
midpoint float

The data value that will be mapped to 0.5 in the normalized data.

Notes

This normalization is particularly useful for: - Diverging colormaps where a specific value should be at the center - Data with positive and negative values where zero should be the midpoint - Highlighting deviations from a reference value

Examples:

Create a plot with a midpoint normalization:

>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> from cleopatra.styles import MidpointNormalize
>>> # Create some data with positive and negative values
>>> data = np.linspace(-10, 10, 100)
>>> # Create a normalization with midpoint at 0
>>> norm = MidpointNormalize(vmin=-10, vmax=10, midpoint=0)
>>> # Use in a plot
>>> plt.figure(figsize=(8, 1)) # doctest: +SKIP
>>> plt.imshow([data], cmap='coolwarm', norm=norm, aspect='auto')  # doctest: +SKIP
>>> plt.colorbar()  # doctest: +SKIP
>>> plt.title('Midpoint Normalization with midpoint=0')  # doctest: +SKIP
>>> plt.tight_layout()  # doctest: +SKIP
Create a normalization with a non-zero midpoint:
>>> # Create a normalization with midpoint at 5
>>> norm = MidpointNormalize(vmin=0, vmax=10, midpoint=5)
>>> # Values below midpoint are mapped to [0, 0.5]
>>> norm(0)
masked_array(data=0.,
             mask=False,
       fill_value=1e+20)
>>> norm(2.5)
masked_array(data=0.25,
             mask=False,
       fill_value=1e+20)
>>> # Midpoint is mapped to 0.5
>>> norm(5)
masked_array(data=0.5,
             mask=False,
       fill_value=1e+20)
>>> # Values above midpoint are mapped to [0.5, 1]
>>> norm(7.5)
masked_array(data=0.75,
             mask=False,
       fill_value=1e+20)
>>> norm(10)
masked_array(data=1.,
             mask=False,
       fill_value=1e+20)

Source code in cleopatra/styles.py
class MidpointNormalize(colors.Normalize):
    """A normalization class that scales data with a midpoint.

    This class extends matplotlib's Normalize class to create a colormap
    normalization that has a fixed midpoint. This is useful for data that
    has a natural midpoint (like zero) where the colormap should be centered,
    regardless of the actual data range.

    The normalization maps values to the range [0, 1] with the midpoint
    mapped to 0.5, which allows for symmetric colormaps to be properly centered.

    Parameters
    ----------
    vmin : float, optional
        The minimum data value that corresponds to 0 in the normalized data.
        If None, it is automatically calculated from the data.
    vmax : float, optional
        The maximum data value that corresponds to 1 in the normalized data.
        If None, it is automatically calculated from the data.
    midpoint : float, optional
        The data value that corresponds to 0.5 in the normalized data.
        If None, it defaults to the midpoint between vmin and vmax.
    clip : bool, optional
        If True, values outside the [vmin, vmax] range are clipped to be
        within that range, by default False.

    Attributes
    ----------
    midpoint : float
        The data value that will be mapped to 0.5 in the normalized data.

    Notes
    -----
    This normalization is particularly useful for:
    - Diverging colormaps where a specific value should be at the center
    - Data with positive and negative values where zero should be the midpoint
    - Highlighting deviations from a reference value

    Examples
    --------
    Create a plot with a midpoint normalization:
    ```python
    >>> import numpy as np
    >>> import matplotlib.pyplot as plt
    >>> from cleopatra.styles import MidpointNormalize
    >>> # Create some data with positive and negative values
    >>> data = np.linspace(-10, 10, 100)
    >>> # Create a normalization with midpoint at 0
    >>> norm = MidpointNormalize(vmin=-10, vmax=10, midpoint=0)
    >>> # Use in a plot
    >>> plt.figure(figsize=(8, 1)) # doctest: +SKIP
    >>> plt.imshow([data], cmap='coolwarm', norm=norm, aspect='auto')  # doctest: +SKIP
    >>> plt.colorbar()  # doctest: +SKIP
    >>> plt.title('Midpoint Normalization with midpoint=0')  # doctest: +SKIP
    >>> plt.tight_layout()  # doctest: +SKIP

    ```
    Create a normalization with a non-zero midpoint:
    ```python
    >>> # Create a normalization with midpoint at 5
    >>> norm = MidpointNormalize(vmin=0, vmax=10, midpoint=5)
    >>> # Values below midpoint are mapped to [0, 0.5]
    >>> norm(0)
    masked_array(data=0.,
                 mask=False,
           fill_value=1e+20)
    >>> norm(2.5)
    masked_array(data=0.25,
                 mask=False,
           fill_value=1e+20)
    >>> # Midpoint is mapped to 0.5
    >>> norm(5)
    masked_array(data=0.5,
                 mask=False,
           fill_value=1e+20)
    >>> # Values above midpoint are mapped to [0.5, 1]
    >>> norm(7.5)
    masked_array(data=0.75,
                 mask=False,
           fill_value=1e+20)
    >>> norm(10)
    masked_array(data=1.,
                 mask=False,
           fill_value=1e+20)

    ```
    """

    def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
        """Initialize a MidpointNormalize instance.

        Parameters
        ----------
        vmin : float, optional
            The minimum data value that corresponds to 0 in the normalized data.
            If None, it is automatically calculated from the data when the
            normalization is applied.
        vmax : float, optional
            The maximum data value that corresponds to 1 in the normalized data.
            If None, it is automatically calculated from the data when the
            normalization is applied.
        midpoint : float, optional
            The data value that corresponds to 0.5 in the normalized data.
            If None, it defaults to the midpoint between vmin and vmax.
        clip : bool, optional
            If True, values outside the [vmin, vmax] range are clipped to be
            within that range, by default False.

        Notes
        -----
        This initialization sets up the midpoint attribute and calls the parent
        class (matplotlib.colors.Normalize) constructor with the vmin, vmax, and
        clip parameters.

        Examples
        --------
        Create a normalization with default parameters:
        ```python
        >>> from cleopatra.styles import MidpointNormalize
        >>> norm = MidpointNormalize()  # vmin, vmax, midpoint will be determined from data

        Create a normalization with specific range and midpoint:
        ```python
        >>> norm = MidpointNormalize(vmin=-10, vmax=10, midpoint=0)
        >>> norm.midpoint
        0

        ```
        """
        self.midpoint = midpoint
        colors.Normalize.__init__(self, vmin, vmax, clip)

    def __call__(self, value, clip=None):
        """Normalize data values to the [0, 1] range with a fixed midpoint.

        This method implements the normalization logic, mapping input values to
        the range [0, 1] with the midpoint mapped to 0.5. It uses linear interpolation
        to create two separate linear mappings: one for values below the midpoint
        and another for values above the midpoint.

        Parameters
        ----------
        value : float or numpy.ndarray
            The data value(s) to normalize. Can be a single value or an array.
        clip : bool, optional
            Whether to clip the input values to the [vmin, vmax] range.
            If None, the clip attribute of the instance is used.

        Returns
        -------
        numpy.ma.masked_array
            The normalized value(s) in the range [0, 1], with the midpoint mapped to 0.5.
            If the input is an array, the output will be an array of the same shape.
            Masked values in the input remain masked in the output.

        Notes
        -----
        The normalization is performed using numpy's interp function, which does
        linear interpolation between the points:
        - (vmin, 0): minimum value maps to 0
        - (midpoint, 0.5): midpoint value maps to 0.5
        - (vmax, 1): maximum value maps to 1

        This creates a piecewise linear mapping that ensures the midpoint is
        always at 0.5 in the normalized range.

        Examples
        --------
        Normalize values with a zero midpoint:
        ```python
        >>> from cleopatra.styles import MidpointNormalize
        >>> norm = MidpointNormalize(vmin=-10, vmax=10, midpoint=0)
        >>> # Values below midpoint are mapped to [0, 0.5]
        >>> norm(-10)  # vmin maps to 0
        masked_array(data=0.,
                     mask=False,
               fill_value=1e+20)
        >>> norm(-5)   # halfway between vmin and midpoint maps to 0.25
        masked_array(data=0.25,
                     mask=False,
               fill_value=1e+20)
        >>> # Midpoint maps to 0.5
        >>> norm(0)
        masked_array(data=0.5,
                     mask=False,
               fill_value=1e+20)
        >>> # Values above midpoint are mapped to [0.5, 1]
        >>> norm(5)    # halfway between midpoint and vmax maps to 0.75
        masked_array(data=0.75,
                     mask=False,
               fill_value=1e+20)
        >>> norm(10)   # vmax maps to 1
        masked_array(data=1.,
                     mask=False,
               fill_value=1e+20)

        ```
        Normalize an array of values:
        ```python
        >>> import numpy as np
        >>> values = np.array([-10, -5, 0, 5, 10])
        >>> norm(values)
        masked_array(data=[0.  , 0.25, 0.5 , 0.75, 1.  ],
                     mask=False,
               fill_value=1e+20)

        ```
        """
        # I'm ignoring masked values and all kinds of edge cases to make a
        # simple example...
        x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]

        return np.ma.masked_array(np.interp(value, x, y))

__call__(value, clip=None) #

Normalize data values to the [0, 1] range with a fixed midpoint.

This method implements the normalization logic, mapping input values to the range [0, 1] with the midpoint mapped to 0.5. It uses linear interpolation to create two separate linear mappings: one for values below the midpoint and another for values above the midpoint.

Parameters:

Name Type Description Default
value float or ndarray

The data value(s) to normalize. Can be a single value or an array.

required
clip bool

Whether to clip the input values to the [vmin, vmax] range. If None, the clip attribute of the instance is used.

None

Returns:

Type Description
masked_array

The normalized value(s) in the range [0, 1], with the midpoint mapped to 0.5. If the input is an array, the output will be an array of the same shape. Masked values in the input remain masked in the output.

Notes

The normalization is performed using numpy's interp function, which does linear interpolation between the points: - (vmin, 0): minimum value maps to 0 - (midpoint, 0.5): midpoint value maps to 0.5 - (vmax, 1): maximum value maps to 1

This creates a piecewise linear mapping that ensures the midpoint is always at 0.5 in the normalized range.

Examples:

Normalize values with a zero midpoint:

>>> from cleopatra.styles import MidpointNormalize
>>> norm = MidpointNormalize(vmin=-10, vmax=10, midpoint=0)
>>> # Values below midpoint are mapped to [0, 0.5]
>>> norm(-10)  # vmin maps to 0
masked_array(data=0.,
             mask=False,
       fill_value=1e+20)
>>> norm(-5)   # halfway between vmin and midpoint maps to 0.25
masked_array(data=0.25,
             mask=False,
       fill_value=1e+20)
>>> # Midpoint maps to 0.5
>>> norm(0)
masked_array(data=0.5,
             mask=False,
       fill_value=1e+20)
>>> # Values above midpoint are mapped to [0.5, 1]
>>> norm(5)    # halfway between midpoint and vmax maps to 0.75
masked_array(data=0.75,
             mask=False,
       fill_value=1e+20)
>>> norm(10)   # vmax maps to 1
masked_array(data=1.,
             mask=False,
       fill_value=1e+20)
Normalize an array of values:
>>> import numpy as np
>>> values = np.array([-10, -5, 0, 5, 10])
>>> norm(values)
masked_array(data=[0.  , 0.25, 0.5 , 0.75, 1.  ],
             mask=False,
       fill_value=1e+20)

Source code in cleopatra/styles.py
def __call__(self, value, clip=None):
    """Normalize data values to the [0, 1] range with a fixed midpoint.

    This method implements the normalization logic, mapping input values to
    the range [0, 1] with the midpoint mapped to 0.5. It uses linear interpolation
    to create two separate linear mappings: one for values below the midpoint
    and another for values above the midpoint.

    Parameters
    ----------
    value : float or numpy.ndarray
        The data value(s) to normalize. Can be a single value or an array.
    clip : bool, optional
        Whether to clip the input values to the [vmin, vmax] range.
        If None, the clip attribute of the instance is used.

    Returns
    -------
    numpy.ma.masked_array
        The normalized value(s) in the range [0, 1], with the midpoint mapped to 0.5.
        If the input is an array, the output will be an array of the same shape.
        Masked values in the input remain masked in the output.

    Notes
    -----
    The normalization is performed using numpy's interp function, which does
    linear interpolation between the points:
    - (vmin, 0): minimum value maps to 0
    - (midpoint, 0.5): midpoint value maps to 0.5
    - (vmax, 1): maximum value maps to 1

    This creates a piecewise linear mapping that ensures the midpoint is
    always at 0.5 in the normalized range.

    Examples
    --------
    Normalize values with a zero midpoint:
    ```python
    >>> from cleopatra.styles import MidpointNormalize
    >>> norm = MidpointNormalize(vmin=-10, vmax=10, midpoint=0)
    >>> # Values below midpoint are mapped to [0, 0.5]
    >>> norm(-10)  # vmin maps to 0
    masked_array(data=0.,
                 mask=False,
           fill_value=1e+20)
    >>> norm(-5)   # halfway between vmin and midpoint maps to 0.25
    masked_array(data=0.25,
                 mask=False,
           fill_value=1e+20)
    >>> # Midpoint maps to 0.5
    >>> norm(0)
    masked_array(data=0.5,
                 mask=False,
           fill_value=1e+20)
    >>> # Values above midpoint are mapped to [0.5, 1]
    >>> norm(5)    # halfway between midpoint and vmax maps to 0.75
    masked_array(data=0.75,
                 mask=False,
           fill_value=1e+20)
    >>> norm(10)   # vmax maps to 1
    masked_array(data=1.,
                 mask=False,
           fill_value=1e+20)

    ```
    Normalize an array of values:
    ```python
    >>> import numpy as np
    >>> values = np.array([-10, -5, 0, 5, 10])
    >>> norm(values)
    masked_array(data=[0.  , 0.25, 0.5 , 0.75, 1.  ],
                 mask=False,
           fill_value=1e+20)

    ```
    """
    # I'm ignoring masked values and all kinds of edge cases to make a
    # simple example...
    x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]

    return np.ma.masked_array(np.interp(value, x, y))

__init__(vmin=None, vmax=None, midpoint=None, clip=False) #

Initialize a MidpointNormalize instance.

Parameters:

Name Type Description Default
vmin float

The minimum data value that corresponds to 0 in the normalized data. If None, it is automatically calculated from the data when the normalization is applied.

None
vmax float

The maximum data value that corresponds to 1 in the normalized data. If None, it is automatically calculated from the data when the normalization is applied.

None
midpoint float

The data value that corresponds to 0.5 in the normalized data. If None, it defaults to the midpoint between vmin and vmax.

None
clip bool

If True, values outside the [vmin, vmax] range are clipped to be within that range, by default False.

False
Notes

This initialization sets up the midpoint attribute and calls the parent class (matplotlib.colors.Normalize) constructor with the vmin, vmax, and clip parameters.

Examples:

Create a normalization with default parameters: ```python

from cleopatra.styles import MidpointNormalize norm = MidpointNormalize() # vmin, vmax, midpoint will be determined from data

Create a normalization with specific range and midpoint: ```python

>>> norm = MidpointNormalize(vmin=-10, vmax=10, midpoint=0)
>>> norm.midpoint
0

```

Source code in cleopatra/styles.py
def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
    """Initialize a MidpointNormalize instance.

    Parameters
    ----------
    vmin : float, optional
        The minimum data value that corresponds to 0 in the normalized data.
        If None, it is automatically calculated from the data when the
        normalization is applied.
    vmax : float, optional
        The maximum data value that corresponds to 1 in the normalized data.
        If None, it is automatically calculated from the data when the
        normalization is applied.
    midpoint : float, optional
        The data value that corresponds to 0.5 in the normalized data.
        If None, it defaults to the midpoint between vmin and vmax.
    clip : bool, optional
        If True, values outside the [vmin, vmax] range are clipped to be
        within that range, by default False.

    Notes
    -----
    This initialization sets up the midpoint attribute and calls the parent
    class (matplotlib.colors.Normalize) constructor with the vmin, vmax, and
    clip parameters.

    Examples
    --------
    Create a normalization with default parameters:
    ```python
    >>> from cleopatra.styles import MidpointNormalize
    >>> norm = MidpointNormalize()  # vmin, vmax, midpoint will be determined from data

    Create a normalization with specific range and midpoint:
    ```python
    >>> norm = MidpointNormalize(vmin=-10, vmax=10, midpoint=0)
    >>> norm.midpoint
    0

    ```
    """
    self.midpoint = midpoint
    colors.Normalize.__init__(self, vmin, vmax, clip)

Examples#

Log Scale#

import numpy as np
import matplotlib.pyplot as plt
from cleopatra.styles import Scale

# Create some data with a wide range of values
data = np.array([0.1, 1, 10, 100, 1000])

# Apply log scale
scale = Scale()
log_data = scale.log_scale(data)

# Plot the original and log-scaled data
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
ax1.plot(data)
ax1.set_title('Original Data')
ax2.plot(log_data)
ax2.set_title('Log-Scaled Data')
plt.tight_layout()

Log Scale Example

Power Scale#

# Apply power scale with gamma=0.5 (square root)
power_data = scale.power_scale(data)(0.5)

# Plot the original and power-scaled data
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
ax1.plot(data)
ax1.set_title('Original Data')
ax2.plot(power_data)
ax2.set_title('Power-Scaled Data (gamma=0.5)')
plt.tight_layout()

Power Scale Example

Midpoint Normalize#

import numpy as np
import matplotlib.pyplot as plt
from cleopatra.styles import MidpointNormalize
import matplotlib.colors as colors

# Create some data with positive and negative values
data = np.random.uniform(-10, 10, (10, 10))

# Create a figure with two subplots
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))

# Plot with standard normalization
im1 = ax1.imshow(data, cmap='RdBu_r', norm=colors.Normalize(vmin=-10, vmax=10))
ax1.set_title('Standard Normalization')
plt.colorbar(im1, ax=ax1)

# Plot with midpoint normalization (midpoint at 0)
im2 = ax2.imshow(data, cmap='RdBu_r', norm=MidpointNormalize(vmin=-10, vmax=10, midpoint=0))
ax2.set_title('Midpoint Normalization')
plt.colorbar(im2, ax=ax2)

plt.tight_layout()

Midpoint Scale Example