| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 | from __future__ import annotationsimport itertoolsfrom typing import (    TYPE_CHECKING,    Collection,    Iterator,    cast,)import warningsimport matplotlib as mplimport matplotlib.colorsimport numpy as npfrom pandas._typing import MatplotlibColor as Colorfrom pandas.util._exceptions import find_stack_levelfrom pandas.core.dtypes.common import is_list_likeimport pandas.core.common as comif TYPE_CHECKING:    from matplotlib.colors import Colormapdef get_standard_colors(    num_colors: int,    colormap: Colormap | None = None,    color_type: str = "default",    color: dict[str, Color] | Color | Collection[Color] | None = None,):    """    Get standard colors based on `colormap`, `color_type` or `color` inputs.    Parameters    ----------    num_colors : int        Minimum number of colors to be returned.        Ignored if `color` is a dictionary.    colormap : :py:class:`matplotlib.colors.Colormap`, optional        Matplotlib colormap.        When provided, the resulting colors will be derived from the colormap.    color_type : {"default", "random"}, optional        Type of colors to derive. Used if provided `color` and `colormap` are None.        Ignored if either `color` or `colormap` are not None.    color : dict or str or sequence, optional        Color(s) to be used for deriving sequence of colors.        Can be either be a dictionary, or a single color (single color string,        or sequence of floats representing a single color),        or a sequence of colors.    Returns    -------    dict or list        Standard colors. Can either be a mapping if `color` was a dictionary,        or a list of colors with a length of `num_colors` or more.    Warns    -----    UserWarning        If both `colormap` and `color` are provided.        Parameter `color` will override.    """    if isinstance(color, dict):        return color    colors = _derive_colors(        color=color,        colormap=colormap,        color_type=color_type,        num_colors=num_colors,    )    return list(_cycle_colors(colors, num_colors=num_colors))def _derive_colors(    *,    color: Color | Collection[Color] | None,    colormap: str | Colormap | None,    color_type: str,    num_colors: int,) -> list[Color]:    """    Derive colors from either `colormap`, `color_type` or `color` inputs.    Get a list of colors either from `colormap`, or from `color`,    or from `color_type` (if both `colormap` and `color` are None).    Parameters    ----------    color : str or sequence, optional        Color(s) to be used for deriving sequence of colors.        Can be either be a single color (single color string, or sequence of floats        representing a single color), or a sequence of colors.    colormap : :py:class:`matplotlib.colors.Colormap`, optional        Matplotlib colormap.        When provided, the resulting colors will be derived from the colormap.    color_type : {"default", "random"}, optional        Type of colors to derive. Used if provided `color` and `colormap` are None.        Ignored if either `color` or `colormap`` are not None.    num_colors : int        Number of colors to be extracted.    Returns    -------    list        List of colors extracted.    Warns    -----    UserWarning        If both `colormap` and `color` are provided.        Parameter `color` will override.    """    if color is None and colormap is not None:        return _get_colors_from_colormap(colormap, num_colors=num_colors)    elif color is not None:        if colormap is not None:            warnings.warn(                "'color' and 'colormap' cannot be used simultaneously. Using 'color'",                stacklevel=find_stack_level(),            )        return _get_colors_from_color(color)    else:        return _get_colors_from_color_type(color_type, num_colors=num_colors)def _cycle_colors(colors: list[Color], num_colors: int) -> Iterator[Color]:    """Cycle colors until achieving max of `num_colors` or length of `colors`.    Extra colors will be ignored by matplotlib if there are more colors    than needed and nothing needs to be done here.    """    max_colors = max(num_colors, len(colors))    yield from itertools.islice(itertools.cycle(colors), max_colors)def _get_colors_from_colormap(    colormap: str | Colormap,    num_colors: int,) -> list[Color]:    """Get colors from colormap."""    cmap = _get_cmap_instance(colormap)    return [cmap(num) for num in np.linspace(0, 1, num=num_colors)]def _get_cmap_instance(colormap: str | Colormap) -> Colormap:    """Get instance of matplotlib colormap."""    if isinstance(colormap, str):        cmap = colormap        colormap = mpl.colormaps[colormap]        if colormap is None:            raise ValueError(f"Colormap {cmap} is not recognized")    return colormapdef _get_colors_from_color(    color: Color | Collection[Color],) -> list[Color]:    """Get colors from user input color."""    if len(color) == 0:        raise ValueError(f"Invalid color argument: {color}")    if _is_single_color(color):        color = cast(Color, color)        return [color]    color = cast(Collection[Color], color)    return list(_gen_list_of_colors_from_iterable(color))def _is_single_color(color: Color | Collection[Color]) -> bool:    """Check if `color` is a single color, not a sequence of colors.    Single color is of these kinds:        - Named color "red", "C0", "firebrick"        - Alias "g"        - Sequence of floats, such as (0.1, 0.2, 0.3) or (0.1, 0.2, 0.3, 0.4).    See Also    --------    _is_single_string_color    """    if isinstance(color, str) and _is_single_string_color(color):        # GH #36972        return True    if _is_floats_color(color):        return True    return Falsedef _gen_list_of_colors_from_iterable(color: Collection[Color]) -> Iterator[Color]:    """    Yield colors from string of several letters or from collection of colors.    """    for x in color:        if _is_single_color(x):            yield x        else:            raise ValueError(f"Invalid color {x}")def _is_floats_color(color: Color | Collection[Color]) -> bool:    """Check if color comprises a sequence of floats representing color."""    return bool(        is_list_like(color)        and (len(color) == 3 or len(color) == 4)        and all(isinstance(x, (int, float)) for x in color)    )def _get_colors_from_color_type(color_type: str, num_colors: int) -> list[Color]:    """Get colors from user input color type."""    if color_type == "default":        return _get_default_colors(num_colors)    elif color_type == "random":        return _get_random_colors(num_colors)    else:        raise ValueError("color_type must be either 'default' or 'random'")def _get_default_colors(num_colors: int) -> list[Color]:    """Get `num_colors` of default colors from matplotlib rc params."""    import matplotlib.pyplot as plt    colors = [c["color"] for c in plt.rcParams["axes.prop_cycle"]]    return colors[0:num_colors]def _get_random_colors(num_colors: int) -> list[Color]:    """Get `num_colors` of random colors."""    return [_random_color(num) for num in range(num_colors)]def _random_color(column: int) -> list[float]:    """Get a random color represented as a list of length 3"""    # GH17525 use common._random_state to avoid resetting the seed    rs = com.random_state(column)    return rs.rand(3).tolist()def _is_single_string_color(color: Color) -> bool:    """Check if `color` is a single string color.    Examples of single string colors:        - 'r'        - 'g'        - 'red'        - 'green'        - 'C3'        - 'firebrick'    Parameters    ----------    color : Color        Color string or sequence of floats.    Returns    -------    bool        True if `color` looks like a valid color.        False otherwise.    """    conv = matplotlib.colors.ColorConverter()    try:        conv.to_rgba(color)    except ValueError:        return False    else:        return True
 |