From ce488f570fbad85fa23181fff9195e46deb55970 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Tue, 12 Nov 2024 12:16:58 +0100 Subject: [PATCH] Make ListedColormap.monochrome a property The calculated property replaces the attribute *monochome*, which was manually set on `__init__`, but was not correctly set for all possible inputs. This property ensures consistency and simplifies initialization at the cost of some computation overhead to determine whether the colormap is monochrome. The computation cost is bearable (even without caching), because it's only used in `ContourSet._process_colors`. It's a separate discussion whether we need this property on colormaps at all (at least as public API). Usually, colormaps are not monochrome and monochrome colormaps are a very special edge case used in contours only. We may eventually deprecate it, but since it is currently public API, let's leave it for now. There's also a technical API incompatibility in that users cannot set the attribute anymore, but I'd argue that that has never been intended and there's no practical use-case, so I refrain from the extra hassle of allowing setting this property. Co-authored-by: Greg Lucas --- lib/matplotlib/colors.py | 20 +++++++++++++++----- lib/matplotlib/colors.pyi | 3 ++- lib/matplotlib/tests/test_colors.py | 6 ++++++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/colors.py b/lib/matplotlib/colors.py index 5f909d07c190..08fe5c053802 100644 --- a/lib/matplotlib/colors.py +++ b/lib/matplotlib/colors.py @@ -1191,17 +1191,13 @@ class ListedColormap(Colormap): the list will be extended by repetition. """ def __init__(self, colors, name='from_list', N=None): - self.monochrome = False # Are all colors identical? (for contour.py) if N is None: self.colors = colors N = len(colors) else: if isinstance(colors, str): self.colors = [colors] * N - self.monochrome = True elif np.iterable(colors): - if len(colors) == 1: - self.monochrome = True self.colors = list( itertools.islice(itertools.cycle(colors), N)) else: @@ -1211,7 +1207,6 @@ def __init__(self, colors, name='from_list', N=None): pass else: self.colors = [gray] * N - self.monochrome = True super().__init__(name, N) def _init(self): @@ -1220,6 +1215,21 @@ def _init(self): self._isinit = True self._set_extremes() + @property + def monochrome(self): + """Return whether all colors in the colormap are identical.""" + # Replacement for the attribute *monochrome*. This ensures a consistent + # response independent of the way the ListedColormap was created, which + # was not the case for the manually set attribute. + # + # TODO: It's a separate discussion whether we need this property on + # colormaps at all (at least as public API). It's a very special edge + # case and we only use it for contours internally. + if not self._isinit: + self._init() + + return self.N <= 1 or np.all(self._lut[0] == self._lut[1:self.N]) + def resampled(self, lutsize): """Return a new colormap with *lutsize* entries.""" colors = self(np.linspace(0, 1, lutsize)) diff --git a/lib/matplotlib/colors.pyi b/lib/matplotlib/colors.pyi index 6941e3d5d176..4ecf90164512 100644 --- a/lib/matplotlib/colors.pyi +++ b/lib/matplotlib/colors.pyi @@ -130,11 +130,12 @@ class LinearSegmentedColormap(Colormap): def reversed(self, name: str | None = ...) -> LinearSegmentedColormap: ... class ListedColormap(Colormap): - monochrome: bool colors: ArrayLike | ColorType def __init__( self, colors: ArrayLike | ColorType, name: str = ..., N: int | None = ... ) -> None: ... + @property + def monochrome(self) -> bool: ... def resampled(self, lutsize: int) -> ListedColormap: ... def reversed(self, name: str | None = ...) -> ListedColormap: ... diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index cc6cb1bb11a7..19c60091e608 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -74,6 +74,12 @@ def test_resampled(): assert_array_almost_equal(lc(np.nan), lc3(np.nan)) +def test_monochrome(): + assert mcolors.ListedColormap(["red"]).monochrome + assert mcolors.ListedColormap(["red"] * 5).monochrome + assert not mcolors.ListedColormap(["red", "green"]).monochrome + + def test_colormaps_get_cmap(): cr = mpl.colormaps