Skip to content

Commit bcbc3e8

Browse files
committed
ENH: Add bad, under, over kwargs to Colormap
This is part of the effort for making Colormaps immutable (matplotlib#29141). Obviously, we can only get immutable in the future if the bad, under, over colors can already be set upon creation.
1 parent 685ea2b commit bcbc3e8

File tree

3 files changed

+74
-14
lines changed

3 files changed

+74
-14
lines changed

lib/matplotlib/colors.py

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -714,20 +714,26 @@ class Colormap:
714714
chain.
715715
"""
716716

717-
def __init__(self, name, N=256):
717+
def __init__(self, name, N=256, *, bad=None, under=None, over=None):
718718
"""
719719
Parameters
720720
----------
721721
name : str
722722
The name of the colormap.
723723
N : int
724724
The number of RGB quantization levels.
725+
bad : :mpltype:`color`, default: transparent
726+
The color for invalid values (NaN or masked).
727+
under : :mpltype:`color`, default: color of the lowest value
728+
The color for low out-of-range values.
729+
over : :mpltype:`color`, default: color of the highest value
730+
The color for high out-of-range values.
725731
"""
726732
self.name = name
727733
self.N = int(N) # ensure that N is always int
728-
self._rgba_bad = (0.0, 0.0, 0.0, 0.0) # If bad, don't paint anything.
729-
self._rgba_under = None
730-
self._rgba_over = None
734+
self._rgba_bad = (0.0, 0.0, 0.0, 0.0) if bad is None else to_rgba(bad)
735+
self._rgba_under = None if under is None else to_rgba(under)
736+
self._rgba_over = None if over is None else to_rgba(over)
731737
self._i_under = self.N
732738
self._i_over = self.N + 1
733739
self._i_bad = self.N + 2
@@ -1034,9 +1040,10 @@ class LinearSegmentedColormap(Colormap):
10341040
segments.
10351041
"""
10361042

1037-
def __init__(self, name, segmentdata, N=256, gamma=1.0):
1043+
def __init__(self, name, segmentdata, N=256, gamma=1.0, *,
1044+
bad=None, under=None, over=None):
10381045
"""
1039-
Create colormap from linear mapping segments
1046+
Create colormap from linear mapping segments.
10401047
10411048
segmentdata argument is a dictionary with a red, green and blue
10421049
entries. Each entry should be a list of *x*, *y0*, *y1* tuples,
@@ -1070,7 +1077,7 @@ def __init__(self, name, segmentdata, N=256, gamma=1.0):
10701077
/
10711078
row i+1: x y0 y1
10721079
1073-
Hence y0 in the first row and y1 in the last row are never used.
1080+
Hence, y0 in the first row and y1 in the last row are never used.
10741081
10751082
See Also
10761083
--------
@@ -1080,7 +1087,7 @@ def __init__(self, name, segmentdata, N=256, gamma=1.0):
10801087
"""
10811088
# True only if all colors in map are identical; needed for contouring.
10821089
self.monochrome = False
1083-
super().__init__(name, N)
1090+
super().__init__(name, N, bad=bad, under=under, over=over)
10841091
self._segmentdata = segmentdata
10851092
self._gamma = gamma
10861093

@@ -1104,7 +1111,7 @@ def set_gamma(self, gamma):
11041111
self._init()
11051112

11061113
@staticmethod
1107-
def from_list(name, colors, N=256, gamma=1.0):
1114+
def from_list(name, colors, N=256, gamma=1.0, bad=None, under=None, over=None):
11081115
"""
11091116
Create a `LinearSegmentedColormap` from a list of colors.
11101117
@@ -1121,6 +1128,13 @@ def from_list(name, colors, N=256, gamma=1.0):
11211128
N : int
11221129
The number of RGB quantization levels.
11231130
gamma : float
1131+
1132+
bad : :mpltype:`color`, default: transparent
1133+
The color for invalid values (NaN or masked).
1134+
under : :mpltype:`color`, default: color of the lowest value
1135+
The color for low out-of-range values.
1136+
over : :mpltype:`color`, default: color of the highest value
1137+
The color for high out-of-range values.
11241138
"""
11251139
if not np.iterable(colors):
11261140
raise ValueError('colors must be iterable')
@@ -1140,7 +1154,8 @@ def from_list(name, colors, N=256, gamma=1.0):
11401154
"alpha": np.column_stack([vals, a, a]),
11411155
}
11421156

1143-
return LinearSegmentedColormap(name, cdict, N, gamma)
1157+
return LinearSegmentedColormap(name, cdict, N, gamma,
1158+
bad=bad, under=under, over=over)
11441159

11451160
def resampled(self, lutsize):
11461161
"""Return a new colormap with *lutsize* entries."""
@@ -1215,6 +1230,18 @@ class ListedColormap(Colormap):
12151230
N > len(colors)
12161231
12171232
the list will be extended by repetition.
1233+
1234+
.. deprecated:: 3.11
1235+
1236+
This parameter will be removed. Please ensure instead that
1237+
the list of passed colors is the required length.
1238+
1239+
bad : :mpltype:`color`, default: transparent
1240+
The color for invalid values (NaN or masked).
1241+
under : :mpltype:`color`, default: color of the lowest value
1242+
The color for low out-of-range values.
1243+
over : :mpltype:`color`, default: color of the highest value
1244+
The color for high out-of-range values.
12181245
"""
12191246

12201247
@_api.delete_parameter(
@@ -1223,7 +1250,8 @@ class ListedColormap(Colormap):
12231250
"and will be removed in %(removal)s. Please ensure the list "
12241251
"of passed colors is the required length instead."
12251252
)
1226-
def __init__(self, colors, name='from_list', N=None):
1253+
def __init__(self, colors, name='from_list', N=None, *,
1254+
bad=None, under=None, over=None):
12271255
if N is None:
12281256
self.colors = colors
12291257
N = len(colors)
@@ -1240,7 +1268,7 @@ def __init__(self, colors, name='from_list', N=None):
12401268
pass
12411269
else:
12421270
self.colors = [gray] * N
1243-
super().__init__(name, N)
1271+
super().__init__(name, N, bad=bad, under=under, over=over)
12441272

12451273
def _init(self):
12461274
self._lut = np.zeros((self.N + 3, 4), float)

lib/matplotlib/colors.pyi

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,19 +120,24 @@ class LinearSegmentedColormap(Colormap):
120120
],
121121
N: int = ...,
122122
gamma: float = ...,
123+
bad: ColorType | None = ...,
124+
under: ColorType | None = ...,
125+
over: ColorType | None = ...,
123126
) -> None: ...
124127
def set_gamma(self, gamma: float) -> None: ...
125128
@staticmethod
126129
def from_list(
127-
name: str, colors: ArrayLike | Sequence[tuple[float, ColorType]], N: int = ..., gamma: float = ...
130+
name: str, colors: ArrayLike | Sequence[tuple[float, ColorType]], N: int = ..., gamma: float = ...,
131+
bad: ColorType | None = ..., under: ColorType | None = ..., over: ColorType | None = ...,
128132
) -> LinearSegmentedColormap: ...
129133
def resampled(self, lutsize: int) -> LinearSegmentedColormap: ...
130134
def reversed(self, name: str | None = ...) -> LinearSegmentedColormap: ...
131135

132136
class ListedColormap(Colormap):
133137
colors: ArrayLike | ColorType
134138
def __init__(
135-
self, colors: ArrayLike | ColorType, name: str = ..., N: int | None = ...
139+
self, colors: ArrayLike | ColorType, name: str = ..., N: int | None = ...,
140+
bad: ColorType | None = ..., under: ColorType | None = ..., over: ColorType | None = ...
136141
) -> None: ...
137142
@property
138143
def monochrome(self) -> bool: ...

lib/matplotlib/tests/test_colors.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,33 @@ def test_colormap_return_types():
221221
assert cmap(x2d).shape == x2d.shape + (4,)
222222

223223

224+
def test_ListedColormap_bad_under_over():
225+
cmap = mcolors.ListedColormap(["r", "g", "b"], bad="c", under="m", over="y")
226+
assert mcolors.same_color(cmap.get_bad(), "c")
227+
assert mcolors.same_color(cmap.get_under(), "m")
228+
assert mcolors.same_color(cmap.get_over(), "y")
229+
230+
231+
def test_LinearSegmentedColormap_bad_under_over():
232+
cdict = {
233+
'red': [(0., 0., 0.), (0.5, 1., 1.), (1., 1., 1.)],
234+
'green': [(0., 0., 0.), (0.25, 0., 0.), (0.75, 1., 1.), (1., 1., 1.)],
235+
'blue': [(0., 0., 0.), (0.5, 0., 0.), (1., 1., 1.)],
236+
}
237+
cmap = mcolors.LinearSegmentedColormap("lsc", cdict, bad="c", under="m", over="y")
238+
assert mcolors.same_color(cmap.get_bad(), "c")
239+
assert mcolors.same_color(cmap.get_under(), "m")
240+
assert mcolors.same_color(cmap.get_over(), "y")
241+
242+
243+
def test_LinearSegmentedColormap_from_list_bad_under_over():
244+
cmap = mcolors.LinearSegmentedColormap.from_list(
245+
"lsc", ["r", "g", "b"], bad="c", under="m", over="y")
246+
assert mcolors.same_color(cmap.get_bad(), "c")
247+
assert mcolors.same_color(cmap.get_under(), "m")
248+
assert mcolors.same_color(cmap.get_over(), "y")
249+
250+
224251
def test_BoundaryNorm():
225252
"""
226253
GitHub issue #1258: interpolation was failing with numpy

0 commit comments

Comments
 (0)