Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion ultraplot/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# colors or truncate colors. So we translate the relevant ListedColormaps to
# LinearSegmentedColormaps for consistency. See :rc:`cmap.listedthresh`
import functools
import itertools
import json
import os
import re
Expand Down Expand Up @@ -1715,6 +1716,20 @@ def __repr__(self):
string += "})"
return string

@property
def monochrome(self):
"""Whether every color is identical, normalized to a Python boolean."""
if hasattr(self, "_monochrome"):
return self._monochrome
try:
return bool(super().monochrome)
except AttributeError:
return False

@monochrome.setter
def monochrome(self, value):
self._monochrome = bool(value)

def __init__(self, colors, name=None, N=None, alpha=None, **kwargs):
"""
Parameters
Expand Down Expand Up @@ -1748,7 +1763,13 @@ def __init__(self, colors, name=None, N=None, alpha=None, **kwargs):
# identical monochromatic ListedColormaps when it receives scalar colors.
N = _not_none(N, len(colors))
name = _not_none(name, DEFAULT_NAME)
super().__init__(colors, name=name, N=N, **kwargs)
if isinstance(colors, str):
colors = [colors] * N
elif np.iterable(colors):
colors = list(itertools.islice(itertools.cycle(colors), N))
else:
colors = [colors] * N
super().__init__(colors, name=name, **kwargs)
if alpha is not None:
self.set_alpha(alpha)
for i, color in enumerate(self.colors):
Expand Down
73 changes: 72 additions & 1 deletion ultraplot/tests/test_colors.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,83 @@
import os
import warnings

import matplotlib as mpl
import matplotlib.colors as mcolors
import pytest
import numpy as np
import matplotlib.colors as mcolors

from ultraplot import colors as pcolors
from ultraplot import config


@pytest.mark.parametrize(
("N", "expected"),
(
(None, ["#ff0000", "#0000ff"]),
(0, []),
(1, ["#ff0000"]),
(3, ["#ff0000", "#0000ff", "#ff0000"]),
),
)
def test_discrete_colormap_resizes_without_listed_n_warning(N, expected):
"""DiscreteColormap preserves N semantics without deprecated mpl input."""
with warnings.catch_warnings():
warnings.filterwarnings(
"error",
message=r"Passing 'N' to ListedColormap.*",
category=mpl.MatplotlibDeprecationWarning,
)
cmap = pcolors.DiscreteColormap(["red", "blue"], N=N)

assert cmap.N == len(expected)
assert [mcolors.to_hex(color) for color in cmap.colors] == expected


@pytest.mark.parametrize(
("N", "expected_size"),
(
(None, 3),
(1, 1),
(4, 4),
),
)
def test_discrete_colormap_expands_scalar_color(N, expected_size):
"""Scalar color strings produce the requested monochromatic colormap."""
cmap = pcolors.DiscreteColormap("red", N=N)

assert cmap.N == expected_size
assert [mcolors.to_hex(color) for color in cmap.colors] == [
"#ff0000"
] * expected_size
assert cmap.monochrome is True


def test_discrete_colormap_resizing_preserves_alpha_override():
"""Alpha replacement is applied to every color after cycling the input."""
cmap = pcolors.DiscreteColormap([(1, 0, 0, 0.2), (0, 0, 1, 0.8)], N=3, alpha=0.5)

assert [mcolors.to_hex(color, keep_alpha=True) for color in cmap.colors] == [
"#ff000080",
"#0000ff80",
"#ff000080",
]
assert cmap.monochrome is False


@pytest.mark.parametrize(
("colors", "expected"),
(
(["red", "red"], True),
(["red", "blue"], False),
),
)
def test_discrete_colormap_monochrome_is_python_bool(colors, expected):
"""Monochrome detection remains available across matplotlib versions."""
cmap = pcolors.DiscreteColormap(colors)

assert cmap.monochrome is expected


@pytest.fixture(autouse=True)
def setup_teardown():
"""
Expand Down