Skip to content

API reference#

earthcarekit.colormap

Colormap utilities.

Notes#

This module depends on other internal modules:


cmaps module-attribute #

cmaps: dict[str, Colormap] = get_cmaps()

Dictionary of custom colormaps for earthcarekit.

Cmap #

Bases: ListedColormap

Colormap with categorical, gradient, and circular support.

This subclass of matplotlib.colors.ListedColormap adds utilities for continuous and categorical color mappings. Supports labels, ticks, normalization, blending, and transparency adjustments.

Attributes:

Name Type Description
categorical bool

Whether the colormap is discrete/categorical.

gradient bool

Whether the colormap was generated from a gradient.

circular bool

Whether the colormap wraps around cyclically.

ticks list[float]

Optional tick positions for categorical plots.

labels list[str]

Optional labels corresponding to ticks.

norm Normalize | None

Normalization strategy for value mapping.

values list

Associated values for categorical mapping.

Source code in earthcarekit/colormap/_cmap.py
class Cmap(ListedColormap):
    """Colormap with categorical, gradient, and circular support.

    This subclass of `matplotlib.colors.ListedColormap` adds utilities for
    continuous and categorical color mappings. Supports labels, ticks,
    normalization, blending, and transparency adjustments.

    Attributes:
        categorical (bool): Whether the colormap is discrete/categorical.
        gradient (bool): Whether the colormap was generated from a gradient.
        circular (bool): Whether the colormap wraps around cyclically.
        ticks (list[float]): Optional tick positions for categorical plots.
        labels (list[str]): Optional labels corresponding to ticks.
        norm (Normalize | None): Normalization strategy for value mapping.
        values (list): Associated values for categorical mapping.
    """

    def __init__(
        self,
        colors: Sequence,
        name: str = "colormap",
        N: int | None = None,
        categorical: bool = False,
        ticks: List[float] | None = None,
        labels: List[str] | None = None,
        norm: Normalize | None = None,
        values: List | None = None,
        gradient: bool = False,
        circular: bool = False,
    ):
        """Initialize a Cmap.

        Args:
            colors (Sequence): Sequence of colors (strings or ColorLike objects).
            name (str): Name of the colormap. Defaults to "colormap".
            N (int | None): Number of discrete colors. Defaults to None.
            categorical (bool): Whether the colormap is discrete/categorical. Defaults to False.
            ticks (list[float] | None): Optional tick positions for categorical plots. Defaults to None.
            labels (list[str] | None): Optional labels corresponding to ticks. Defaults to None.
            norm (Normalize | None): Normalization strategy for value mapping. Defaults to None.
            gradient (bool): If True, generate intermediate gradient colors. Defaults to False.
            circular (bool): If True, colormap wraps around cyclically. Defaults to False.
        """
        _colors: list = [Color(c) if isinstance(c, str) else c for c in colors]

        if gradient:
            tmp_cmap = LinearSegmentedColormap.from_list("tmp_cmap", _colors, N=256)
            _colors = [tmp_cmap(i) for i in range(256)]

        super().__init__(_colors, name=name, N=N or len(_colors))
        self.categorical = categorical
        self.gradient = gradient
        self.circular = circular
        self.ticks = ticks or []
        self.labels = labels or []
        self.norm = norm
        self.values = values or []

    @classmethod
    def from_colormap(cls, cmap: Colormap, N: int = 256, name: str | None = None) -> "Cmap":
        """Create a Cmap instance from an existing Matplotlib colormap.

        Args:
            cmap (Colormap): Source colormap to convert.
            N (int): Number of discrete colors (if needed, e.g, for categorical
                colormaps with limited number of colors). Defaults to 256.

        Returns:
            Cmap: New colormap.
        """
        if isinstance(cmap, cls):
            return cmap
        elif isinstance(cmap, ListedColormap):
            colors = list(cmap.colors)  # type: ignore
            if isinstance(colors, np.ndarray) and colors.ndim == 2:
                N = len(colors)
            else:
                N = cmap.N
        else:
            colors = [cmap(x) for x in np.linspace(0, 1, N)]

        _colors = []
        for c in colors:
            if (
                isinstance(c, (np.ndarray, list, tuple))
                and not isinstance(c, str)
                and all([_c <= 1 for _c in c])
            ):
                _colors.append(Color(c, is_normalized=True))  # type: ignore
            else:
                _colors = [Color(c) for c in colors]  # type: ignore
                continue
        new_cmap = cls(
            [c.hex for c in _colors],
            name=name or cmap.name,
            N=N,
        )
        new_cmap = copy_extremes(cmap, new_cmap)
        return new_cmap

    def copy(self) -> "Cmap":
        new_cmap = Cmap(
            colors=cast(Sequence, self.colors),
            name=self.name,
            N=self.N,
            categorical=self.categorical,
            ticks=self.ticks,
            labels=self.labels,
            norm=self.norm,
            values=self.values,
            gradient=self.gradient,
            circular=self.circular,
        )
        return copy_extremes(self, new_cmap)

    def to_categorical(
        self,
        values_to_labels: Dict[Any, str] | int,
        endpoint: bool | None = None,
        use_discrete: bool | None = None,
    ) -> "Cmap":
        """Convert a colormap to categorical.

        Args:
            values_to_labels (dict | int): Mapping from values to labels, or
                number of categories if int.
            endpoint (bool | None): Whether the last color is included at 1.0.
            use_discrete (bool | None): If True, use the colormap's defined colors directly rather than sampling across its range.

        Returns:
            Cmap: Categorical version of the colormap.
        """
        if isinstance(values_to_labels, int):
            values_to_labels = {i: str(i) for i in range(values_to_labels)}

        values_to_labels = dict(sorted(values_to_labels.items()))

        keys = list(values_to_labels.keys())
        labels = list(values_to_labels.values())
        sorted_values = keys

        n_classes = len(sorted_values)
        bounds = np.array(sorted_values + [sorted_values[-1] + 1]) - 0.5
        norm = BoundaryNorm(bounds, n_classes)

        ticks = [float(t) for t in np.arange(0.5, n_classes)]

        if use_discrete:
            colors = [self(i) for i in range(n_classes)]
        else:
            if not isinstance(endpoint, bool):
                endpoint = not self.circular
            offset = -1 if endpoint else 0
            colors = [self(i / max(n_classes + offset, 1)) for i in range(n_classes)]

        return Cmap(
            colors=colors,
            name=self.name,
            N=n_classes,
            categorical=True,
            gradient=False,
            circular=self.circular,
            ticks=ticks,
            labels=labels,
            norm=norm,
            values=sorted_values,
        )

    def to_discrete(self, n: int) -> "Cmap":
        """Convert a colormap to a discretized version of itself.

        Args:
            n (int): Number of steps (i.e., discrete colors).

        Returns:
            Cmap: Discretized version of the colormap.
        """
        new_cmap = self.to_categorical(n)
        new_cmap.categorical = False
        new_cmap.ticks = []
        new_cmap.labels = []
        new_cmap.norm = None
        new_cmap.values = []
        return new_cmap

    def set_alpha(self, value: float) -> "Cmap":
        """Return a copy of the colormap with modified alpha transparency.

        Args:
            value (float): Alpha value in the range [0, 1].

        Returns:
            Cmap: Colormap with updated transparency.
        """
        if not 0 <= value <= 1:
            raise ValueError(f"Invalid alpha value: '{value}' (must be in the 0-1 range)")

        new_cmap = Cmap(
            colors=[Color(c).set_alpha(value) for c in np.asarray(self.colors)],
            name=self.name,
            N=self.N,
            categorical=self.categorical,
            gradient=self.gradient,
            circular=self.circular,
            ticks=self.ticks,
            labels=self.labels,
            norm=self.norm,
        )

        if self._rgba_bad is not None:  # type: ignore
            new_cmap._rgba_bad = Color(self._rgba_bad, is_normalized=True).set_alpha(value).rgba  # type: ignore
        if self._rgba_over is not None:  # type: ignore
            new_cmap._rgba_over = Color(self._rgba_over, is_normalized=True).set_alpha(value).rgba  # type: ignore
        if self._rgba_under is not None:  # type: ignore
            new_cmap._rgba_under = Color(self._rgba_under, is_normalized=True).set_alpha(value).rgba  # type: ignore

        return new_cmap

    def blend(self, value: float, blend_color: Color | ColorLike = "white") -> "Cmap":
        """Return a copy of the colormap blended with a second color.

        Args:
            value (float): Blend factor in the range [0, 1].
            blend_color (Color | str): Color to blend with.

        Returns:
            Cmap: Blended colormap.
        """
        if not 0 <= value <= 1:
            raise ValueError(f"Invalid blend value: '{value}' (must be in the 0-1 range)")

        new_cmap = Cmap(
            colors=[Color(c).blend(value, blend_color) for c in np.asarray(self.colors)],
            name=self.name,
            N=self.N,
            categorical=self.categorical,
            gradient=self.gradient,
            circular=self.circular,
            ticks=self.ticks,
            labels=self.labels,
            norm=self.norm,
        )

        if self._rgba_bad is not None:  # type: ignore
            new_cmap._rgba_bad = (  # type: ignore
                Color(self._rgba_bad, is_normalized=True).blend(value, blend_color).rgba  # type: ignore
            )  # type: ignore
        if self._rgba_over is not None:  # type: ignore
            new_cmap._rgba_over = (  # type: ignore
                Color(self._rgba_over, is_normalized=True).blend(value, blend_color).rgba  # type: ignore
            )  # type: ignore
        if self._rgba_under is not None:  # type: ignore
            new_cmap._rgba_under = (  # type: ignore
                Color(self._rgba_under, is_normalized=True).blend(value, blend_color).rgba  # type: ignore
            )  # type: ignore

        return new_cmap

    @property
    def rgba_list(self) -> list[tuple[float, ...]]:
        """List of RGBA tuples representing all colors in the colormap."""
        return [Color(c, is_normalized=True).rgba for c in np.array(self.colors)]

    # def set_alpha_gradient(self, alpha_input: list) -> "Cmap":
    #     from matplotlib.colors import ListedColormap
    #     from scipy.interpolate import interp1d

    #     # Interpolate to 256 values
    #     n_colors = 256
    #     x_old = np.linspace(0, 1, len(alpha_input))
    #     x_new = np.linspace(0, 1, n_colors)
    #     alpha_interp = interp1d(x_old, alpha_input, kind="linear")(x_new)

    #     # Get base colormap and apply interpolated alpha
    #     colors = self(np.linspace(0, 1, n_colors))
    #     colors[:, 3] = alpha_interp  # Replace alpha channel

    #     # Create transparent colormap
    #     new_cmap = Cmap(colors, name=self.name)

    @property
    def opaque(self) -> "Cmap":
        """Return an opaque version of the colormap (alpha set to 1)."""
        return colormap_to_opaque(self)

    @property
    def alphamap(self) -> "Cmap":
        """Return the alpha-mapped version of the colormap."""
        return colormap_to_alphamap(self)

    @property
    def blended(self) -> "Cmap":
        """Return a blended version of the colormap (predefined blending)."""
        return colormap_to_blended(self)

    def __new__(cls, *args, **kwargs):
        """Allow instantiation from an existing Colormap or standard arguments."""
        if len(args) == 1 and isinstance(args[0], Colormap):
            return cls.from_colormap(args[0])
        return super().__new__(cls)

    def reversed(self, name: str | None = None) -> "Cmap":
        return Cmap(
            colors=cast(Sequence, super().reversed(name).colors),
            name=name or self.name.removesuffix("_r")
            if self.name.endswith("_r")
            else self.name + "_r",
            N=self.N,
            categorical=self.categorical,
            ticks=list(reversed(self.ticks)),
            labels=list(reversed(self.labels)),
            norm=self.norm,
            values=list(reversed(self.values)),
            gradient=self.gradient,
            circular=self.circular,
        )

alphamap property #

alphamap: Cmap

Return the alpha-mapped version of the colormap.

blended property #

blended: Cmap

Return a blended version of the colormap (predefined blending).

opaque property #

opaque: Cmap

Return an opaque version of the colormap (alpha set to 1).

rgba_list property #

rgba_list: list[tuple[float, ...]]

List of RGBA tuples representing all colors in the colormap.

blend #

blend(value: float, blend_color: Color | ColorLike = 'white') -> Cmap

Return a copy of the colormap blended with a second color.

Parameters:

Name Type Description Default
value float

Blend factor in the range [0, 1].

required
blend_color Color | str

Color to blend with.

'white'

Returns:

Name Type Description
Cmap Cmap

Blended colormap.

Source code in earthcarekit/colormap/_cmap.py
def blend(self, value: float, blend_color: Color | ColorLike = "white") -> "Cmap":
    """Return a copy of the colormap blended with a second color.

    Args:
        value (float): Blend factor in the range [0, 1].
        blend_color (Color | str): Color to blend with.

    Returns:
        Cmap: Blended colormap.
    """
    if not 0 <= value <= 1:
        raise ValueError(f"Invalid blend value: '{value}' (must be in the 0-1 range)")

    new_cmap = Cmap(
        colors=[Color(c).blend(value, blend_color) for c in np.asarray(self.colors)],
        name=self.name,
        N=self.N,
        categorical=self.categorical,
        gradient=self.gradient,
        circular=self.circular,
        ticks=self.ticks,
        labels=self.labels,
        norm=self.norm,
    )

    if self._rgba_bad is not None:  # type: ignore
        new_cmap._rgba_bad = (  # type: ignore
            Color(self._rgba_bad, is_normalized=True).blend(value, blend_color).rgba  # type: ignore
        )  # type: ignore
    if self._rgba_over is not None:  # type: ignore
        new_cmap._rgba_over = (  # type: ignore
            Color(self._rgba_over, is_normalized=True).blend(value, blend_color).rgba  # type: ignore
        )  # type: ignore
    if self._rgba_under is not None:  # type: ignore
        new_cmap._rgba_under = (  # type: ignore
            Color(self._rgba_under, is_normalized=True).blend(value, blend_color).rgba  # type: ignore
        )  # type: ignore

    return new_cmap

from_colormap classmethod #

from_colormap(cmap: Colormap, N: int = 256, name: str | None = None) -> Cmap

Create a Cmap instance from an existing Matplotlib colormap.

Parameters:

Name Type Description Default
cmap Colormap

Source colormap to convert.

required
N int

Number of discrete colors (if needed, e.g, for categorical colormaps with limited number of colors). Defaults to 256.

256

Returns:

Name Type Description
Cmap Cmap

New colormap.

Source code in earthcarekit/colormap/_cmap.py
@classmethod
def from_colormap(cls, cmap: Colormap, N: int = 256, name: str | None = None) -> "Cmap":
    """Create a Cmap instance from an existing Matplotlib colormap.

    Args:
        cmap (Colormap): Source colormap to convert.
        N (int): Number of discrete colors (if needed, e.g, for categorical
            colormaps with limited number of colors). Defaults to 256.

    Returns:
        Cmap: New colormap.
    """
    if isinstance(cmap, cls):
        return cmap
    elif isinstance(cmap, ListedColormap):
        colors = list(cmap.colors)  # type: ignore
        if isinstance(colors, np.ndarray) and colors.ndim == 2:
            N = len(colors)
        else:
            N = cmap.N
    else:
        colors = [cmap(x) for x in np.linspace(0, 1, N)]

    _colors = []
    for c in colors:
        if (
            isinstance(c, (np.ndarray, list, tuple))
            and not isinstance(c, str)
            and all([_c <= 1 for _c in c])
        ):
            _colors.append(Color(c, is_normalized=True))  # type: ignore
        else:
            _colors = [Color(c) for c in colors]  # type: ignore
            continue
    new_cmap = cls(
        [c.hex for c in _colors],
        name=name or cmap.name,
        N=N,
    )
    new_cmap = copy_extremes(cmap, new_cmap)
    return new_cmap

set_alpha #

set_alpha(value: float) -> Cmap

Return a copy of the colormap with modified alpha transparency.

Parameters:

Name Type Description Default
value float

Alpha value in the range [0, 1].

required

Returns:

Name Type Description
Cmap Cmap

Colormap with updated transparency.

Source code in earthcarekit/colormap/_cmap.py
def set_alpha(self, value: float) -> "Cmap":
    """Return a copy of the colormap with modified alpha transparency.

    Args:
        value (float): Alpha value in the range [0, 1].

    Returns:
        Cmap: Colormap with updated transparency.
    """
    if not 0 <= value <= 1:
        raise ValueError(f"Invalid alpha value: '{value}' (must be in the 0-1 range)")

    new_cmap = Cmap(
        colors=[Color(c).set_alpha(value) for c in np.asarray(self.colors)],
        name=self.name,
        N=self.N,
        categorical=self.categorical,
        gradient=self.gradient,
        circular=self.circular,
        ticks=self.ticks,
        labels=self.labels,
        norm=self.norm,
    )

    if self._rgba_bad is not None:  # type: ignore
        new_cmap._rgba_bad = Color(self._rgba_bad, is_normalized=True).set_alpha(value).rgba  # type: ignore
    if self._rgba_over is not None:  # type: ignore
        new_cmap._rgba_over = Color(self._rgba_over, is_normalized=True).set_alpha(value).rgba  # type: ignore
    if self._rgba_under is not None:  # type: ignore
        new_cmap._rgba_under = Color(self._rgba_under, is_normalized=True).set_alpha(value).rgba  # type: ignore

    return new_cmap

to_categorical #

to_categorical(
    values_to_labels: Dict[Any, str] | int,
    endpoint: bool | None = None,
    use_discrete: bool | None = None,
) -> Cmap

Convert a colormap to categorical.

Parameters:

Name Type Description Default
values_to_labels dict | int

Mapping from values to labels, or number of categories if int.

required
endpoint bool | None

Whether the last color is included at 1.0.

None
use_discrete bool | None

If True, use the colormap's defined colors directly rather than sampling across its range.

None

Returns:

Name Type Description
Cmap Cmap

Categorical version of the colormap.

Source code in earthcarekit/colormap/_cmap.py
def to_categorical(
    self,
    values_to_labels: Dict[Any, str] | int,
    endpoint: bool | None = None,
    use_discrete: bool | None = None,
) -> "Cmap":
    """Convert a colormap to categorical.

    Args:
        values_to_labels (dict | int): Mapping from values to labels, or
            number of categories if int.
        endpoint (bool | None): Whether the last color is included at 1.0.
        use_discrete (bool | None): If True, use the colormap's defined colors directly rather than sampling across its range.

    Returns:
        Cmap: Categorical version of the colormap.
    """
    if isinstance(values_to_labels, int):
        values_to_labels = {i: str(i) for i in range(values_to_labels)}

    values_to_labels = dict(sorted(values_to_labels.items()))

    keys = list(values_to_labels.keys())
    labels = list(values_to_labels.values())
    sorted_values = keys

    n_classes = len(sorted_values)
    bounds = np.array(sorted_values + [sorted_values[-1] + 1]) - 0.5
    norm = BoundaryNorm(bounds, n_classes)

    ticks = [float(t) for t in np.arange(0.5, n_classes)]

    if use_discrete:
        colors = [self(i) for i in range(n_classes)]
    else:
        if not isinstance(endpoint, bool):
            endpoint = not self.circular
        offset = -1 if endpoint else 0
        colors = [self(i / max(n_classes + offset, 1)) for i in range(n_classes)]

    return Cmap(
        colors=colors,
        name=self.name,
        N=n_classes,
        categorical=True,
        gradient=False,
        circular=self.circular,
        ticks=ticks,
        labels=labels,
        norm=norm,
        values=sorted_values,
    )

to_discrete #

to_discrete(n: int) -> Cmap

Convert a colormap to a discretized version of itself.

Parameters:

Name Type Description Default
n int

Number of steps (i.e., discrete colors).

required

Returns:

Name Type Description
Cmap Cmap

Discretized version of the colormap.

Source code in earthcarekit/colormap/_cmap.py
def to_discrete(self, n: int) -> "Cmap":
    """Convert a colormap to a discretized version of itself.

    Args:
        n (int): Number of steps (i.e., discrete colors).

    Returns:
        Cmap: Discretized version of the colormap.
    """
    new_cmap = self.to_categorical(n)
    new_cmap.categorical = False
    new_cmap.ticks = []
    new_cmap.labels = []
    new_cmap.norm = None
    new_cmap.values = []
    return new_cmap

combine_cmaps #

combine_cmaps(
    cmap1: str | Colormap | None,
    cmap2: str | Colormap | None,
    name: str = "combined_cmap",
    n: int = 256,
) -> Cmap

Create a combined colormap from two colormaps.

Parameters:

Name Type Description Default
cmap1 str | Colormap | None

Colormap to be modified.

required
cmap2 str | Colormap | None

Colormap to be modified

required
name str

New colormap name. Defaults to "combined_cmap".

'combined_cmap'
n int

Number of colors.

256

Returns:

Name Type Description
Cmap Cmap

The combined colormap.

Source code in earthcarekit/colormap/_combine_cmaps.py
def combine_cmaps(
    cmap1: str | Colormap | None,
    cmap2: str | Colormap | None,
    name: str = "combined_cmap",
    n: int = 256,
) -> Cmap:
    """Create a combined colormap from two colormaps.

    Args:
        cmap1 (str | Colormap | None): Colormap to be modified.
        cmap2 (str | Colormap | None): Colormap to be modified
        name (str): New colormap name. Defaults to "combined_cmap".
        n (int): Number of colors.

    Returns:
        Cmap: The combined colormap.
    """
    return get_cmap(
        combine_mpl_cmaps(
            cmap1=get_cmap(cmap1),
            cmap2=get_cmap(cmap2),
            name=name,
            n=n,
        ),
    )

get_cmap #

get_cmap(cmap: CmapLike | None, **kwargs) -> Cmap

Return a colormap given by cmap.

Parameters:

Name Type Description Default
cmap CmapLike | None
  • If a colormap, return it.
  • If a str, return first matching colormap from earthcarekit, cmcrameri, plotly, or matplotlib (in that order).
  • If a list of colors, create a corresponding descrete colormap.
  • If None, return the default colormap ("viridis").
required

Returns: Cmap: The resolved colormap.

Source code in earthcarekit/colormap/_get_cmap.py
def get_cmap(cmap: CmapLike | None, **kwargs) -> Cmap:
    """Return a colormap given by `cmap`.

    Args:
        cmap (CmapLike | None):
            - If a colormap, return it.
            - If a `str`, return first matching colormap from `earthcarekit`, `cmcrameri`,
              `plotly`, or `matplotlib` (in that order).
            - If a `list` of colors, create a corresponding descrete colormap.
            - If None, return the default colormap ("viridis").
    Returns:
        Cmap:
            The resolved colormap.
    """
    if isinstance(cmap, (str, Colormap)) or cmap is None:
        return _get_cmap(cmap, **kwargs)

    if len(kwargs) > 0:
        raise TypeError(f"get_cmap() got an unexpected keyword argument '{tuple(kwargs)[0]}'")

    if isinstance(cmap, Cmap):
        return cmap.copy()

    return Cmap.from_colormap(ListedColormap(cmap))

rename_cmap #

rename_cmap(cmap: Colormap, name: str) -> Colormap

Returns the given cmap with the new name.

Source code in earthcarekit/colormap/_rename.py
4
5
6
7
8
def rename_cmap(cmap: Colormap, name: str) -> Colormap:
    """Returns the given `cmap` with the new `name`."""
    result_cmap = cmap.copy()
    result_cmap.name = name
    return result_cmap

shift_cmap #

shift_cmap(
    cmap: str | Colormap | None,
    start: float = 0.0,
    midpoint: float = 0.5,
    stop: float = 1.0,
    name: str = "shifted_cmap",
) -> Cmap

Create a colormap with its center point shifted to a specified value.

This function is useful for data with asymmetric ranges (e.g., negative min and positive max) where you want the center of the colormap to align with a specific value like zero.

Parameters:

Name Type Description Default
cmap str | Colormap | None

Colormap to be modified

required
start float

Lower bound of the colormap range (value between 0 and midpoint). Defaults to 0.0.

0.0
midpoint float

New center point of the colormap (value between 0 and 1). Defaults to 0.5. For data ranging from vmin to vmax where you want the center at value v, set midpoint = 1 - vmax/(vmax + abs(vmin))

0.5
stop float

Upper bound of the colormap range (value between midpoint and 1). Defaults to 1.0.

1.0
name str

Name of the new colormap. Defaults to "shifted_cmap".

'shifted_cmap'

Returns:

Name Type Description
Cmap Cmap

New colormap with shifted center

Source code in earthcarekit/colormap/_shift_cmap.py
def shift_cmap(
    cmap: str | Colormap | None,
    start: float = 0.0,
    midpoint: float = 0.5,
    stop: float = 1.0,
    name: str = "shifted_cmap",
) -> Cmap:
    """Create a colormap with its center point shifted to a specified value.

    This function is useful for data with asymmetric ranges (e.g., negative min and
    positive max) where you want the center of the colormap to align with a specific
    value like zero.

    Args:
        cmap (str | Colormap | None): Colormap to be modified
        start (float): Lower bound of the colormap range (value between 0 and `midpoint`). Defaults to 0.0.
        midpoint (float): New center point of the colormap (value between 0 and 1). Defaults to 0.5.
            For data ranging from vmin to vmax where you want the center at value v,
            set midpoint = 1 - vmax/(vmax + abs(vmin))
        stop (float): Upper bound of the colormap range (value between `midpoint` and 1). Defaults to 1.0.
        name (str): Name of the new colormap. Defaults to "shifted_cmap".

    Returns:
        Cmap: New colormap with shifted center
    """
    from ._get_cmap import get_cmap

    cmap_old = get_cmap(cmap)
    cmap_new = shift_mpl_colormap(
        cmap_old,
        start=start,
        midpoint=midpoint,
        stop=stop,
        name=name,
    )
    cmap_new = get_cmap(cmap_new)
    cmap_new.categorical = cmap_old.categorical
    cmap_new.ticks = cmap_old.ticks
    cmap_new.labels = cmap_old.labels
    cmap_new.norm = cmap_old.norm
    cmap_new.values = cmap_old.values
    cmap_new.gradient = cmap_old.gradient
    cmap_new.circular = cmap_old.circular
    return cmap_new