Source code for ridgeplot._colors

from __future__ import annotations

import json
import warnings
from pathlib import Path
from typing import Iterable, Tuple, Union, cast

from _plotly_utils.colors import validate_colors, validate_scale_values
from plotly.colors import find_intermediate_color, hex_to_rgb

from ridgeplot._utils import LazyMapping, normalise_min_max

_PATH_TO_COLORS_JSON = Path(__file__).parent.joinpath("colors.json")

ColorScale = Iterable[Tuple[float, str]]
"""A colorscale is an iterable of tuples of two elements:

0. the first element (a *scale value*) is a float bounded to the
   interval ``[0, 1]``
1. the second element (a *color*) is a string representation of a color parsable
   by Plotly

For instance, the Viridis colorscale would be defined as

>>> get_colorscale("viridis")
((0.0, 'rgb(68, 1, 84)'),
 (0.1111111111111111, 'rgb(72, 40, 120)'),
 (0.2222222222222222, 'rgb(62, 73, 137)'),
 (0.3333333333333333, 'rgb(49, 104, 142)'),
 (0.4444444444444444, 'rgb(38, 130, 142)'),
 (0.5555555555555556, 'rgb(31, 158, 137)'),
 (0.6666666666666666, 'rgb(53, 183, 121)'),
 (0.7777777777777777, 'rgb(110, 206, 88)'),
 (0.8888888888888888, 'rgb(181, 222, 43)'),
 (1.0, 'rgb(253, 231, 37)'))
"""

_Color = Union[str, Tuple[float, float, float]]
"""A color can be represented as an rgb(a) or hex string or a tuple of
``(r, g, b)`` values."""


[docs] def _colormap_loader() -> dict[str, ColorScale]: colors: dict[str, ColorScale] = json.loads(_PATH_TO_COLORS_JSON.read_text()) for name, colorscale in colors.items(): colors[name] = tuple((s, c) for s, c in colorscale) return colors
_COLORSCALE_MAPPING: LazyMapping[str, ColorScale] = LazyMapping(loader=_colormap_loader)
[docs] def validate_colorscale(colorscale: ColorScale) -> None: """Validate the structure, scale values, and colors of a colorscale. Adapted from ``_plotly_utils.colors.validate_colorscale``. """ scale, colors = zip(*colorscale) validate_scale_values(scale=scale) validate_colors(colors=colors)
[docs] def _any_to_rgb(color: _Color) -> str: """Convert any color to an rgb string. Parameters ---------- color A color. This can be a tuple of ``(r, g, b)`` values, a hex string, or an rgb string. Returns ------- str An rgb string. Raises ------ TypeError If ``color`` is not a tuple or a string. ValueError If ``color`` is a string that does not represent a hex or rgb color. """ if not isinstance(color, (str, tuple)): raise TypeError(f"Expected str or tuple for color, got {type(color)} instead.") if isinstance(color, tuple): r, g, b = color rgb = f"rgb({r}, {g}, {b})" elif color.startswith("#"): return _any_to_rgb(cast(str, hex_to_rgb(color))) elif color.startswith("rgb("): rgb = color else: raise ValueError( f"color should be a tuple or a str representation " f"of a hex or rgb color, got {color!r} instead." ) validate_colors(rgb) return rgb
[docs] def list_all_colorscale_names() -> list[str]: """Get a list with all available colorscale names. .. versionadded:: 0.1.21 Replaces the deprecated :func:`get_all_colorscale_names()`. Returns ------- list[str] A list with all available colorscale names. """ return sorted(_COLORSCALE_MAPPING.keys())
[docs] def get_all_colorscale_names() -> tuple[str, ...]: # pragma: no cover """Get a tuple with all available colorscale names. .. deprecated:: 0.1.21 Use :func:`list_all_colorscale_names()` instead. Returns ------- tuple[str, ...] A tuple with all available colorscale names. """ warnings.warn( "get_all_colorscale_names() is deprecated in favor of list_all_colorscale_names().", DeprecationWarning, stacklevel=2, ) return tuple(list_all_colorscale_names())
[docs] def get_colorscale(name: str) -> ColorScale: """Get a colorscale by name. Parameters ---------- name The colorscale name. This argument is case-insensitive. For instance, ``"YlOrRd"`` and ``"ylorrd"`` map to the same colorscale. Colorscale names ending in ``_r`` represent a *reversed* colorscale. Returns ------- ColorScale A colorscale. Raises ------ :exc:`ValueError` If an unknown name is provided """ name = name.lower() if name not in _COLORSCALE_MAPPING: raise ValueError( f"Unknown colorscale name: '{name}'. The available colorscale" f" names are {tuple(_COLORSCALE_MAPPING.keys())}." ) return _COLORSCALE_MAPPING[name]
[docs] def get_color(colorscale: ColorScale, midpoint: float) -> str: """Get a color from a colorscale at a given midpoint. Given a colorscale, it interpolates the expected color at a given midpoint, on a scale from 0 to 1. """ if not (0 <= midpoint <= 1): raise ValueError(f"The 'midpoint' should be a float value between 0 and 1, not {midpoint}.") scale = [s for s, _ in colorscale] colors = [_any_to_rgb(c) for _, c in colorscale] del colorscale if midpoint in scale: return colors[scale.index(midpoint)] ceil = min(filter(lambda s: s > midpoint, scale)) floor = max(filter(lambda s: s < midpoint, scale)) midpoint_normalised = normalise_min_max(midpoint, min_=floor, max_=ceil) return cast( str, find_intermediate_color( lowcolor=colors[scale.index(floor)], highcolor=colors[scale.index(ceil)], intermed=midpoint_normalised, colortype="rgb", ), )
[docs] def apply_alpha(color: _Color, alpha: float) -> str: color = _any_to_rgb(color) return f"rgba({color[4:-1]}, {alpha})"