Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 67ffc66

Browse files
committed
Warning when modifying the state of a global colormap.
1 parent c30129b commit 67ffc66

File tree

3 files changed

+94
-10
lines changed

3 files changed

+94
-10
lines changed

lib/matplotlib/cm.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,16 @@ def _gen_cmap_d():
6565
# Generate reversed cmaps.
6666
for cmap in list(cmap_d.values()):
6767
rmap = cmap.reversed()
68+
cmap._global = True
69+
rmap._global = True
6870
cmap_d[rmap.name] = rmap
6971
return cmap_d
7072

7173

72-
cmap_d = _gen_cmap_d()
73-
locals().update(cmap_d)
74+
_cmap_d = _gen_cmap_d()
75+
locals().update(_cmap_d)
76+
# This is no longer considered public API
77+
cmap_d = _cmap_d
7478

7579

7680
# Continue with definitions ...
@@ -104,7 +108,8 @@ def register_cmap(name=None, cmap=None, data=None, lut=None):
104108
raise ValueError("Arguments must include a name or a "
105109
"Colormap") from err
106110
if isinstance(cmap, colors.Colormap):
107-
cmap_d[name] = cmap
111+
cmap._global = True
112+
_cmap_d[name] = cmap
108113
return
109114
if lut is not None or data is not None:
110115
cbook.warn_deprecated(
@@ -117,7 +122,8 @@ def register_cmap(name=None, cmap=None, data=None, lut=None):
117122
if lut is None:
118123
lut = mpl.rcParams['image.lut']
119124
cmap = colors.LinearSegmentedColormap(name, data, lut)
120-
cmap_d[name] = cmap
125+
cmap._global = True
126+
_cmap_d[name] = cmap
121127

122128

123129
def get_cmap(name=None, lut=None):
@@ -130,9 +136,12 @@ def get_cmap(name=None, lut=None):
130136
Parameters
131137
----------
132138
name : `matplotlib.colors.Colormap` or str or None, default: None
133-
If a `.Colormap` instance, it will be returned. Otherwise, the name of
134-
a colormap known to Matplotlib, which will be resampled by *lut*. The
135-
default, None, means :rc:`image.cmap`.
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`.
136145
lut : int or None, default: None
137146
If *name* is not already a Colormap instance and *lut* is not None, the
138147
colormap will be resampled to have *lut* entries in the lookup table.
@@ -141,11 +150,11 @@ def get_cmap(name=None, lut=None):
141150
name = mpl.rcParams['image.cmap']
142151
if isinstance(name, colors.Colormap):
143152
return name
144-
cbook._check_in_list(sorted(cmap_d), name=name)
153+
cbook._check_in_list(sorted(_cmap_d), name=name)
145154
if lut is None:
146-
return cmap_d[name]
155+
return _cmap_d[name]
147156
else:
148-
return cmap_d[name]._resample(lut)
157+
return _cmap_d[name]._resample(lut)
149158

150159

151160
class ScalarMappable:

lib/matplotlib/colors.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,21 @@ 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:
489+
cbook.warn_deprecated(
490+
"3.3",
491+
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})"
499+
)
500+
501+
487502
class Colormap:
488503
"""
489504
Baseclass for all scalar to RGBA mappings.
@@ -599,13 +614,15 @@ def __copy__(self):
599614
cmapobject.__dict__.update(self.__dict__)
600615
if self._isinit:
601616
cmapobject._lut = np.copy(self._lut)
617+
cmapobject._global = False
602618
return cmapobject
603619

604620
def set_bad(self, color='k', alpha=None):
605621
"""Set the color for masked values."""
606622
self._rgba_bad = to_rgba(color, alpha)
607623
if self._isinit:
608624
self._set_extremes()
625+
_deprecate_global_cmap(self)
609626

610627
def set_under(self, color='k', alpha=None):
611628
"""
@@ -614,6 +631,7 @@ def set_under(self, color='k', alpha=None):
614631
self._rgba_under = to_rgba(color, alpha)
615632
if self._isinit:
616633
self._set_extremes()
634+
_deprecate_global_cmap(self)
617635

618636
def set_over(self, color='k', alpha=None):
619637
"""
@@ -622,6 +640,7 @@ def set_over(self, color='k', alpha=None):
622640
self._rgba_over = to_rgba(color, alpha)
623641
if self._isinit:
624642
self._set_extremes()
643+
_deprecate_global_cmap(self)
625644

626645
def _set_extremes(self):
627646
if self._rgba_under:

lib/matplotlib/tests/test_colors.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,62 @@ 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+
105+
def test_colormap_global_set_warn():
106+
new_cm = plt.get_cmap('viridis')
107+
# Store the old value so we don't override the state later on.
108+
orig_cmap = copy.copy(new_cm)
109+
with pytest.warns(cbook.MatplotlibDeprecationWarning,
110+
match="You are modifying the state of a globally"):
111+
# This should warn now because we've modified the global state
112+
new_cm.set_under('k')
113+
114+
# This shouldn't warn because it is a copy
115+
copy.copy(new_cm).set_under('b')
116+
117+
# Test that registering and then modifying warns
118+
plt.register_cmap(name='test_cm', cmap=copy.copy(orig_cmap))
119+
new_cm = plt.get_cmap('test_cm')
120+
with pytest.warns(cbook.MatplotlibDeprecationWarning,
121+
match="You are modifying the state of a globally"):
122+
# This should warn now because we've modified the global state
123+
new_cm.set_under('k')
124+
125+
# Re-register the original
126+
plt.register_cmap(cmap=orig_cmap)
127+
128+
73129
def test_colormap_copy():
74130
cm = plt.cm.Reds
75131
cm_copy = copy.copy(cm)

0 commit comments

Comments
 (0)