Skip to content

Commit 692b83c

Browse files
committed
Deprecation warning for cmap_d.
1 parent 4915f44 commit 692b83c

File tree

7 files changed

+92
-65
lines changed

7 files changed

+92
-65
lines changed

examples/images_contours_and_fields/demo_bboximage.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
a = np.vstack((a, a))
3939

4040
# List of all colormaps; skip reversed colormaps.
41-
maps = sorted(m for m in plt.cm.cmap_d if not m.endswith("_r"))
41+
maps = sorted(m for m in plt.colormaps() if not m.endswith("_r"))
4242

4343
ncol = 2
4444
nrow = len(maps)//ncol + 1

lib/matplotlib/backends/qt_editor/figureoptions.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,11 @@ def prepare_data(d, init):
140140
mappabledict[label] = mappable
141141
mappablelabels = sorted(mappabledict, key=cmp_key)
142142
mappables = []
143-
cmaps = [(cmap, name) for name, cmap in sorted(cm.cmap_d.items())]
143+
cmaps = [(cmap, name) for name, cmap in sorted(cm._cmap_registry.items())]
144144
for label in mappablelabels:
145145
mappable = mappabledict[label]
146146
cmap = mappable.get_cmap()
147-
if cmap not in cm.cmap_d.values():
147+
if cmap not in cm._cmap_registry.values():
148148
cmaps = [(cmap, cmap.name), *cmaps]
149149
low, high = mappable.get_clim()
150150
mappabledata = [

lib/matplotlib/cm.py

+66-15
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
normalization.
1616
"""
1717

18+
from collections.abc import MutableMapping
1819
import functools
1920

2021
import numpy as np
@@ -49,7 +50,7 @@ def revcmap(data):
4950
LUTSIZE = mpl.rcParams['image.lut']
5051

5152

52-
def _gen_cmap_d():
53+
def _gen_cmap_registry():
5354
"""
5455
Generate a dict mapping standard colormap names to standard colormaps, as
5556
well as the reversed colormaps.
@@ -71,10 +72,50 @@ def _gen_cmap_d():
7172
return cmap_d
7273

7374

74-
_cmap_d = _gen_cmap_d()
75-
locals().update(_cmap_d)
75+
class _DeprecatedCmapDictWrapper(MutableMapping):
76+
"""Dictionary mapping for deprecated _cmap_d access."""
77+
78+
def __init__(self, cmap_registry):
79+
self._cmap_registry = cmap_registry
80+
81+
def __delitem__(self, key):
82+
self._warn_deprecated()
83+
self._cmap_registry.__delitem__(key)
84+
85+
def __getitem__(self, key):
86+
self._warn_deprecated()
87+
return self._cmap_registry.__getitem__(key)
88+
89+
def __iter__(self):
90+
self._warn_deprecated()
91+
return self._cmap_registry.__iter__()
92+
93+
def __len__(self):
94+
self._warn_deprecated()
95+
return self._cmap_registry.__len__()
96+
97+
def __setitem__(self, key, val):
98+
self._warn_deprecated()
99+
self._cmap_registry.__setitem__(key, val)
100+
101+
def get(self, key, default=None):
102+
self._warn_deprecated()
103+
return self._cmap_registry.get(key, default)
104+
105+
def _warn_deprecated(self):
106+
cbook.warn_deprecated(
107+
"3.3",
108+
message="The global colormaps dictionary is no longer "
109+
"considered public API.",
110+
alternative="Please use register_cmap() and get_cmap() to "
111+
"access the contents of the dictionary."
112+
)
113+
114+
115+
_cmap_registry = _gen_cmap_registry()
116+
locals().update(_cmap_registry)
76117
# This is no longer considered public API
77-
cmap_d = _cmap_d
118+
cmap_d = _DeprecatedCmapDictWrapper(_cmap_registry)
78119

79120

80121
# Continue with definitions ...
@@ -99,6 +140,13 @@ def register_cmap(name=None, cmap=None, data=None, lut=None):
99140
and the resulting colormap is registered. Instead of this implicit
100141
colormap creation, create a `.LinearSegmentedColormap` and use the first
101142
case: ``register_cmap(cmap=LinearSegmentedColormap(name, data, lut))``.
143+
144+
Notes
145+
-----
146+
Registering a colormap stores a reference to the colormap object
147+
which can currently be modified and inadvertantly change the global
148+
colormap state. This behavior is deprecated and in Matplotlib 3.5
149+
the registered colormap will be immutable.
102150
"""
103151
cbook._check_isinstance((str, None), name=name)
104152
if name is None:
@@ -109,7 +157,7 @@ def register_cmap(name=None, cmap=None, data=None, lut=None):
109157
"Colormap") from err
110158
if isinstance(cmap, colors.Colormap):
111159
cmap._global = True
112-
_cmap_d[name] = cmap
160+
_cmap_registry[name] = cmap
113161
return
114162
if lut is not None or data is not None:
115163
cbook.warn_deprecated(
@@ -123,7 +171,7 @@ def register_cmap(name=None, cmap=None, data=None, lut=None):
123171
lut = mpl.rcParams['image.lut']
124172
cmap = colors.LinearSegmentedColormap(name, data, lut)
125173
cmap._global = True
126-
_cmap_d[name] = cmap
174+
_cmap_registry[name] = cmap
127175

128176

129177
def get_cmap(name=None, lut=None):
@@ -133,15 +181,18 @@ def get_cmap(name=None, lut=None):
133181
Colormaps added with :func:`register_cmap` take precedence over
134182
built-in colormaps.
135183
184+
Notes
185+
-----
186+
Currently, this returns the global colormap object, which is deprecated.
187+
In Matplotlib 3.5, you will no longer be able to modify the global
188+
colormaps in-place.
189+
136190
Parameters
137191
----------
138192
name : `matplotlib.colors.Colormap` or str or None, default: None
139-
If a `.Colormap` instance, it will be returned.
140-
Otherwise, the name of a colormap known to Matplotlib, which will
141-
be resampled by *lut*. Currently, this returns the global colormap
142-
object, which is deprecated. In Matplotlib 3.5, you will no longer be
143-
able to modify the global colormaps in-place.
144-
The default, None, means :rc:`image.cmap`.
193+
If a `.Colormap` instance, it will be returned. Otherwise, the name of
194+
a colormap known to Matplotlib, which will be resampled by *lut*. The
195+
default, None, means :rc:`image.cmap`.
145196
lut : int or None, default: None
146197
If *name* is not already a Colormap instance and *lut* is not None, the
147198
colormap will be resampled to have *lut* entries in the lookup table.
@@ -150,11 +201,11 @@ def get_cmap(name=None, lut=None):
150201
name = mpl.rcParams['image.cmap']
151202
if isinstance(name, colors.Colormap):
152203
return name
153-
cbook._check_in_list(sorted(_cmap_d), name=name)
204+
cbook._check_in_list(sorted(_cmap_registry), name=name)
154205
if lut is None:
155-
return _cmap_d[name]
206+
return _cmap_registry[name]
156207
else:
157-
return _cmap_d[name]._resample(lut)
208+
return _cmap_registry[name]._resample(lut)
158209

159210

160211
class ScalarMappable:

lib/matplotlib/colors.py

+9-12
Original file line numberDiff line numberDiff line change
@@ -484,18 +484,15 @@ def makeMappingArray(N, data, gamma=1.0):
484484
return _create_lookup_table(N, data, gamma)
485485

486486

487-
def _deprecate_global_cmap(cmap):
488-
if hasattr(cmap, '_global') and cmap._global:
487+
def _warn_if_global_cmap_modified(cmap):
488+
if getattr(cmap, '_global', False):
489489
cbook.warn_deprecated(
490490
"3.3",
491491
message="You are modifying the state of a globally registered "
492-
"colormap. In future versions, you will not be able "
493-
"to modify a globally registered colormap directly. "
494-
"To eliminate this warning until then, you can make "
495-
"a copy of the requested colormap before modifying it. ",
496-
alternative="To modify a colormap without overwriting the "
497-
"global state, you can make a copy of the colormap "
498-
f"first. cmap = copy.copy(mpl.cm.get_cmap({cmap.name})"
492+
"colormap. In future versions, you will not be able to "
493+
"modify a registered colormap in-place. To remove this "
494+
"warning, you can make a copy of the colormap first. "
495+
f"cmap = mpl.cm.get_cmap({cmap.name}).copy()"
499496
)
500497

501498

@@ -619,28 +616,28 @@ def __copy__(self):
619616

620617
def set_bad(self, color='k', alpha=None):
621618
"""Set the color for masked values."""
619+
_warn_if_global_cmap_modified(self)
622620
self._rgba_bad = to_rgba(color, alpha)
623621
if self._isinit:
624622
self._set_extremes()
625-
_deprecate_global_cmap(self)
626623

627624
def set_under(self, color='k', alpha=None):
628625
"""
629626
Set the color for low out-of-range values when ``norm.clip = False``.
630627
"""
628+
_warn_if_global_cmap_modified(self)
631629
self._rgba_under = to_rgba(color, alpha)
632630
if self._isinit:
633631
self._set_extremes()
634-
_deprecate_global_cmap(self)
635632

636633
def set_over(self, color='k', alpha=None):
637634
"""
638635
Set the color for high out-of-range values when ``norm.clip = False``.
639636
"""
637+
_warn_if_global_cmap_modified(self)
640638
self._rgba_over = to_rgba(color, alpha)
641639
if self._isinit:
642640
self._set_extremes()
643-
_deprecate_global_cmap(self)
644641

645642
def _set_extremes(self):
646643
if self._rgba_under:

lib/matplotlib/pyplot.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1948,7 +1948,7 @@ def colormaps():
19481948
<https://www.mathworks.com/matlabcentral/fileexchange/2662-cmrmap-m>`_
19491949
by Carey Rappaport
19501950
"""
1951-
return sorted(cm.cmap_d)
1951+
return sorted(cm._cmap_registry)
19521952

19531953

19541954
def _setup_pyplot_info_docstrings():

lib/matplotlib/tests/test_colors.py

+12-33
Original file line numberDiff line numberDiff line change
@@ -70,38 +70,6 @@ def test_register_cmap():
7070
cm.register_cmap()
7171

7272

73-
@pytest.mark.skipif(matplotlib.__version__ < "3.5",
74-
reason="This test modifies the global state of colormaps.")
75-
def test_colormap_global_unchanged_state():
76-
new_cm = plt.get_cmap('viridis')
77-
new_cm.set_over('b')
78-
# Make sure that this didn't mess with the original viridis cmap
79-
assert new_cm != plt.get_cmap('viridis')
80-
81-
# Also test that pyplot access doesn't mess the original up
82-
new_cm = plt.cm.viridis
83-
new_cm.set_over('b')
84-
assert new_cm != plt.get_cmap('viridis')
85-
86-
87-
@pytest.mark.skipif(matplotlib.__version__ < "3.4",
88-
reason="This test modifies the global state of colormaps.")
89-
def test_colormap_global_get_warn():
90-
new_cm = plt.get_cmap('viridis')
91-
# Store the old value so we don't override the state later on.
92-
orig_cmap = copy.copy(new_cm)
93-
new_cm.set_under('k')
94-
with pytest.warns(cbook.MatplotlibDeprecationWarning,
95-
match="You are modifying the state of a globally"):
96-
# This should warn now because we've modified the global state
97-
# without registering it
98-
plt.get_cmap('viridis')
99-
100-
# Test that re-registering the original cmap clears the warning
101-
plt.register_cmap(cmap=orig_cmap)
102-
plt.get_cmap('viridis')
103-
104-
10573
def test_colormap_global_set_warn():
10674
new_cm = plt.get_cmap('viridis')
10775
# Store the old value so we don't override the state later on.
@@ -126,6 +94,17 @@ def test_colormap_global_set_warn():
12694
plt.register_cmap(cmap=orig_cmap)
12795

12896

97+
def test_colormap_dict_deprecate():
98+
# Make sure we warn on get and set access into cmap_d
99+
with pytest.warns(cbook.MatplotlibDeprecationWarning,
100+
match="The global colormaps dictionary is no longer"):
101+
cm = plt.cm.cmap_d['viridis']
102+
103+
with pytest.warns(cbook.MatplotlibDeprecationWarning,
104+
match="The global colormaps dictionary is no longer"):
105+
plt.cm.cmap_d['test'] = cm
106+
107+
129108
def test_colormap_copy():
130109
cm = plt.cm.Reds
131110
cm_copy = copy.copy(cm)
@@ -874,7 +853,7 @@ def test_pandas_iterable(pd):
874853
assert_array_equal(cm1.colors, cm2.colors)
875854

876855

877-
@pytest.mark.parametrize('name', sorted(cm.cmap_d))
856+
@pytest.mark.parametrize('name', sorted(plt.colormaps()))
878857
def test_colormap_reversing(name):
879858
"""
880859
Check the generated _lut data of a colormap and corresponding reversed

lib/matplotlib/tests/test_pickle.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def test_shared():
190190
assert fig.axes[1].get_xlim() == (10, 20)
191191

192192

193-
@pytest.mark.parametrize("cmap", cm.cmap_d.values())
193+
@pytest.mark.parametrize("cmap", cm._cmap_registry.values())
194194
def test_cmap(cmap):
195195
pickle.dumps(cmap)
196196

0 commit comments

Comments
 (0)