Skip to content

Commit 2df6ee2

Browse files
committed
Deprecate ListedColormap(..., N=...) parameter
Truncating or repeating the given colors to a specific value of N is not within the scope of colormaps. Instead, users should create an appropriate color list themselves and feed that to ListedColormap. Also the current behavior can be surprising: It may well be that a given N was intended to truncate, but depending on the list, it could repeat. Repeated colors in a colormap are dangerous / often not intentended because they create an ambiguity in the color -> value mapping.
1 parent 6af9a45 commit 2df6ee2

File tree

5 files changed

+45
-10
lines changed

5 files changed

+45
-10
lines changed

lib/matplotlib/cbook.py

+20
Original file line numberDiff line numberDiff line change
@@ -1739,6 +1739,26 @@ def sanitize_sequence(data):
17391739
else data)
17401740

17411741

1742+
def _resize_sequence(seq, N):
1743+
"""
1744+
Trim the given sequence to exactly N elements.
1745+
1746+
If there are more elements in the sequence, cut it.
1747+
If there are less elements in the sequence, repeat them.
1748+
1749+
Implementation detail: We maintain type stability for the output for
1750+
N <= len(seq). We simply return a list for N > len(seq); this was good
1751+
enough for the present use cases but is not a fixed design decision.
1752+
"""
1753+
num_elements = len(seq)
1754+
if N == num_elements:
1755+
return seq
1756+
elif N < num_elements:
1757+
return seq[:num_elements]
1758+
else:
1759+
return list(itertools.islice(itertools.cycle(seq), N))
1760+
1761+
17421762
def normalize_kwargs(kw, alias_mapping=None):
17431763
"""
17441764
Helper function to normalize kwarg inputs.

lib/matplotlib/cbook.pyi

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ from typing import (
1414
Generic,
1515
IO,
1616
Literal,
17+
Sequence,
1718
TypeVar,
1819
overload,
1920
)
@@ -143,6 +144,7 @@ STEP_LOOKUP_MAP: dict[str, Callable]
143144
def index_of(y: float | ArrayLike) -> tuple[np.ndarray, np.ndarray]: ...
144145
def safe_first_element(obj: Collection[_T]) -> _T: ...
145146
def sanitize_sequence(data): ...
147+
def _resize_sequence(seq: Sequence, N: int) -> Sequence: ...
146148
def normalize_kwargs(
147149
kw: dict[str, Any],
148150
alias_mapping: dict[str, list[str]] | type[Artist] | Artist | None = ...,

lib/matplotlib/colors.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,12 @@ def __init__(self, colors, name='from_list', N=None):
11951195
self.colors = colors
11961196
N = len(colors)
11971197
else:
1198+
_api.warn_deprecated(
1199+
"3.11",
1200+
message="Passing 'N' to ListedColormap is deprecated since %(since)s "
1201+
"and will be removed in %(removal)s. Please process the list "
1202+
"of passed colors yourself if needed."
1203+
)
11981204
if isinstance(colors, str):
11991205
self.colors = [colors] * N
12001206
elif np.iterable(colors):
@@ -1259,7 +1265,7 @@ def reversed(self, name=None):
12591265
name = self.name + "_r"
12601266

12611267
colors_r = list(reversed(self.colors))
1262-
new_cmap = ListedColormap(colors_r, name=name, N=self.N)
1268+
new_cmap = ListedColormap(colors_r, name=name)
12631269
# Reverse the over/under values too
12641270
new_cmap._rgba_over = self._rgba_under
12651271
new_cmap._rgba_under = self._rgba_over
@@ -1943,14 +1949,16 @@ def __getitem__(self, item):
19431949
if origin_1_as_int > self.M-1:
19441950
origin_1_as_int = self.M-1
19451951
one_d_lut = self._lut[:, origin_1_as_int]
1946-
new_cmap = ListedColormap(one_d_lut, name=f'{self.name}_0', N=self.N)
1952+
assert len(one_d_lut) == self.N # temporary
1953+
new_cmap = ListedColormap(one_d_lut, name=f'{self.name}_0')
19471954

19481955
elif item == 1:
19491956
origin_0_as_int = int(self._origin[0]*self.N)
19501957
if origin_0_as_int > self.N-1:
19511958
origin_0_as_int = self.N-1
19521959
one_d_lut = self._lut[origin_0_as_int, :]
1953-
new_cmap = ListedColormap(one_d_lut, name=f'{self.name}_1', N=self.M)
1960+
assert len(one_d_lut) == self.M # temporary
1961+
new_cmap = ListedColormap(one_d_lut, name=f'{self.name}_1')
19541962
else:
19551963
raise KeyError(f"only 0 or 1 are"
19561964
f" valid keys for BivarColormap, not {item!r}")

lib/matplotlib/contour.py

+10-5
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,14 @@ def clabel(self, levels=None, *,
183183
self.labelMappable = self
184184
self.labelCValueList = np.take(self.cvalues, self.labelIndiceList)
185185
else:
186-
cmap = mcolors.ListedColormap(colors, N=len(self.labelLevelList))
187-
self.labelCValueList = list(range(len(self.labelLevelList)))
188-
self.labelMappable = cm.ScalarMappable(cmap=cmap,
189-
norm=mcolors.NoNorm())
186+
# handling of explicit colors for labels:
187+
# make labelCValueList contain integers [0, 1, 2, ...] and a cmap
188+
# so that cmap(i) == colors[i]
189+
num_levels = len(self.labelLevelList)
190+
colors = cbook._resize_sequence(mcolors.to_rgba_array(colors), num_levels)
191+
self.labelMappable = cm.ScalarMappable(
192+
cmap=mcolors.ListedColormap(colors), norm=mcolors.NoNorm())
193+
self.labelCValueList = list(range(num_levels))
190194

191195
self.labelXYs = []
192196

@@ -738,7 +742,8 @@ def __init__(self, ax, *args,
738742
if self._extend_min:
739743
i0 = 1
740744

741-
cmap = mcolors.ListedColormap(color_sequence[i0:None], N=ncolors)
745+
cmap = mcolors.ListedColormap(
746+
cbook._resize_sequence(color_sequence[i0:], ncolors))
742747

743748
if use_set_under_over:
744749
if self._extend_min:

lib/matplotlib/tests/test_colors.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1165,8 +1165,8 @@ def test_pandas_iterable(pd):
11651165
# a single color
11661166
lst = ['red', 'blue', 'green']
11671167
s = pd.Series(lst)
1168-
cm1 = mcolors.ListedColormap(lst, N=5)
1169-
cm2 = mcolors.ListedColormap(s, N=5)
1168+
cm1 = mcolors.ListedColormap(lst)
1169+
cm2 = mcolors.ListedColormap(s)
11701170
assert_array_equal(cm1.colors, cm2.colors)
11711171

11721172

0 commit comments

Comments
 (0)