Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 56c18a4

Browse files
authored
Merge pull request matplotlib#30531 from timhoffm/deprecate-cmap-extreme-setter
MNT: Pending-deprecate setting colormap extremes in-place
2 parents 1c95f1b + 224828f commit 56c18a4

File tree

4 files changed

+53
-14
lines changed

4 files changed

+53
-14
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
In-place modifications of colormaps
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
Colormaps are planned to become immutable in the long term.
4+
5+
As a first step, in-place modifications of colormaps are now pending-deprecated.
6+
This affects the following methods of `.Colormap`:
7+
8+
- `.Colormap.set_bad` - use ``cmap.with_extremes(bad=...)`` instead
9+
- `.Colormap.set_under` - use ``cmap.with_extremes(under=...)`` instead
10+
- `.Colormap.set_over` - use ``cmap.with_extremes(over=...)`` instead
11+
- `.Colormap.set_extremes` - use ``cmap.with_extremes(...)`` instead
12+
13+
Use the respective `.Colormap.with_extremes` and appropriate keyword arguments
14+
instead which returns a copy of the colormap (available since matplotlib 3.4).
15+
Alternatively, if you create the colormap yourself, you can also pass the
16+
respective arguments to the constructor (available since matplotlib 3.11).

‎lib/matplotlib/colors.py‎

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -871,6 +871,10 @@ def get_bad(self):
871871
self._ensure_inited()
872872
return np.array(self._lut[self._i_bad])
873873

874+
@_api.deprecated(
875+
"3.11",
876+
pending=True,
877+
alternative="cmap.with_extremes(bad=...) or Colormap(bad=...)")
874878
def set_bad(self, color='k', alpha=None):
875879
"""Set the color for masked values."""
876880
self._set_extremes(bad=(color, alpha))
@@ -880,6 +884,10 @@ def get_under(self):
880884
self._ensure_inited()
881885
return np.array(self._lut[self._i_under])
882886

887+
@_api.deprecated(
888+
"3.11",
889+
pending=True,
890+
alternative="cmap.with_extremes(under=...) or Colormap(under=...)")
883891
def set_under(self, color='k', alpha=None):
884892
"""Set the color for low out-of-range values."""
885893
self._set_extremes(under=(color, alpha))
@@ -889,10 +897,19 @@ def get_over(self):
889897
self._ensure_inited()
890898
return np.array(self._lut[self._i_over])
891899

900+
@_api.deprecated(
901+
"3.11",
902+
pending=True,
903+
alternative="cmap.with_extremes(over=...) or Colormap(over=...)")
892904
def set_over(self, color='k', alpha=None):
893905
"""Set the color for high out-of-range values."""
894906
self._set_extremes(over=(color, alpha))
895907

908+
@_api.deprecated(
909+
"3.11",
910+
pending=True,
911+
alternative="cmap.with_extremes(bad=..., under=..., over=...) or "
912+
"Colormap(bad=..., under=..., over=...)")
896913
def set_extremes(self, *, bad=None, under=None, over=None):
897914
"""
898915
Set the colors for masked (*bad*) values and, when ``norm.clip =
@@ -1614,14 +1631,16 @@ def with_extremes(self, *, bad=None, under=None, over=None):
16141631
f" i.e. be of length {len(new_cm)}.")
16151632
else:
16161633
for c, b in zip(new_cm, under):
1617-
c.set_under(b)
1634+
# in-place change is ok, since we've just created c as a copy
1635+
c._set_extremes(under=b)
16181636
if over is not None:
16191637
if not np.iterable(over) or len(over) != len(new_cm):
16201638
raise ValueError("*over* must contain a color for each scalar colormap"
16211639
f" i.e. be of length {len(new_cm)}.")
16221640
else:
16231641
for c, b in zip(new_cm, over):
1624-
c.set_over(b)
1642+
# in-place change is ok, since we've just created c as a copy
1643+
c._set_extremes(over=b)
16251644
return new_cm
16261645

16271646
@property
@@ -2070,26 +2089,27 @@ def __getitem__(self, item):
20702089
"""Creates and returns a colorbar along the selected axis"""
20712090
if not self._isinit:
20722091
self._init()
2092+
extremes = (
2093+
dict(bad=self._rgba_bad, over=self._rgba_outside, under=self._rgba_outside)
2094+
if self.shape in ['ignore', 'circleignore']
2095+
else dict(bad=self._rgba_bad)
2096+
)
20732097
if item == 0:
20742098
origin_1_as_int = int(self._origin[1]*self.M)
20752099
if origin_1_as_int > self.M-1:
20762100
origin_1_as_int = self.M-1
20772101
one_d_lut = self._lut[:, origin_1_as_int]
2078-
new_cmap = ListedColormap(one_d_lut, name=f'{self.name}_0')
2102+
new_cmap = ListedColormap(one_d_lut, name=f'{self.name}_0', **extremes)
20792103

20802104
elif item == 1:
20812105
origin_0_as_int = int(self._origin[0]*self.N)
20822106
if origin_0_as_int > self.N-1:
20832107
origin_0_as_int = self.N-1
20842108
one_d_lut = self._lut[origin_0_as_int, :]
2085-
new_cmap = ListedColormap(one_d_lut, name=f'{self.name}_1')
2109+
new_cmap = ListedColormap(one_d_lut, name=f'{self.name}_1', **extremes)
20862110
else:
20872111
raise KeyError(f"only 0 or 1 are"
20882112
f" valid keys for BivarColormap, not {item!r}")
2089-
new_cmap._rgba_bad = self._rgba_bad
2090-
if self.shape in ['ignore', 'circleignore']:
2091-
new_cmap.set_over(self._rgba_outside)
2092-
new_cmap.set_under(self._rgba_outside)
20932113
return new_cmap
20942114

20952115
def _repr_png_(self):

‎lib/matplotlib/tests/test_axes.py‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2966,8 +2966,7 @@ def test_scatter_edgecolor_RGB(self):
29662966
@check_figures_equal()
29672967
def test_scatter_invalid_color(self, fig_test, fig_ref):
29682968
ax = fig_test.subplots()
2969-
cmap = mpl.colormaps["viridis"].resampled(16)
2970-
cmap.set_bad("k", 1)
2969+
cmap = mpl.colormaps["viridis"].resampled(16).with_extremes(bad="black")
29712970
# Set a nonuniform size to prevent the last call to `scatter` (plotting
29722971
# the invalid points separately in fig_ref) from using the marker
29732972
# stamping fast path, which would result in slightly offset markers.

‎lib/matplotlib/tests/test_colors.py‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ def test_colormap_copy():
111111
with np.errstate(invalid='ignore'):
112112
ret1 = copied_cmap([-1, 0, .5, 1, np.nan, np.inf])
113113
cmap2 = copy.copy(copied_cmap)
114-
cmap2.set_bad('g')
114+
with pytest.warns(PendingDeprecationWarning):
115+
cmap2.set_bad('g')
115116
with np.errstate(invalid='ignore'):
116117
ret2 = copied_cmap([-1, 0, .5, 1, np.nan, np.inf])
117118
assert_array_equal(ret1, ret2)
@@ -121,7 +122,8 @@ def test_colormap_copy():
121122
with np.errstate(invalid='ignore'):
122123
ret1 = copied_cmap([-1, 0, .5, 1, np.nan, np.inf])
123124
cmap2 = copy.copy(copied_cmap)
124-
cmap2.set_bad('g')
125+
with pytest.warns(PendingDeprecationWarning):
126+
cmap2.set_bad('g')
125127
with np.errstate(invalid='ignore'):
126128
ret2 = copied_cmap([-1, 0, .5, 1, np.nan, np.inf])
127129
assert_array_equal(ret1, ret2)
@@ -135,7 +137,8 @@ def test_colormap_equals():
135137
# But the same data should be equal
136138
assert cm_copy == cmap
137139
# Change the copy
138-
cm_copy.set_bad('y')
140+
with pytest.warns(PendingDeprecationWarning):
141+
cm_copy.set_bad('y')
139142
assert cm_copy != cmap
140143
# Make sure we can compare different sizes without failure
141144
cm_copy._lut = cm_copy._lut[:10, :]
@@ -1535,7 +1538,8 @@ def test_get_under_over_bad():
15351538
def test_non_mutable_get_values(kind):
15361539
cmap = copy.copy(mpl.colormaps['viridis'])
15371540
init_value = getattr(cmap, f'get_{kind}')()
1538-
getattr(cmap, f'set_{kind}')('k')
1541+
with pytest.warns(PendingDeprecationWarning):
1542+
getattr(cmap, f'set_{kind}')('k')
15391543
black_value = getattr(cmap, f'get_{kind}')()
15401544
assert np.all(black_value == [0, 0, 0, 1])
15411545
assert not np.all(init_value == black_value)

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /