Skip to content

Commit 102ce74

Browse files
committed
Changing get_cmap to return copies of the registered colormaps.
1 parent c292827 commit 102ce74

File tree

4 files changed

+47
-12
lines changed

4 files changed

+47
-12
lines changed

doc/api/next_api_changes/behaviour.rst

+11-3
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,9 @@ deprecation warning.
9898
`~.Axes.errorbar` now color cycles when only errorbar color is set
9999
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
100100

101-
Previously setting the *ecolor* would turn off automatic color cycling for the plot, leading to the
102-
the lines and markers defaulting to whatever the first color in the color cycle was in the case of
103-
multiple plot calls.
101+
Previously setting the *ecolor* would turn off automatic color cycling for the plot, leading to the
102+
the lines and markers defaulting to whatever the first color in the color cycle was in the case of
103+
multiple plot calls.
104104

105105
`.rcsetup.validate_color_for_prop_cycle` now always raises TypeError for bytes input
106106
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -147,3 +147,11 @@ The parameter ``s`` to `.Axes.annotate` and `.pyplot.annotate` is renamed to
147147
The old parameter name remains supported, but
148148
support for it will be dropped in a future Matplotlib release.
149149

150+
`pyplot.get_cmap()` now returns a copy of the colormap
151+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
152+
Previously, calling ``.pyplot.get_cmap()`` would return a pointer to
153+
the built-in Colormap. If you made modifications to that colormap, the
154+
changes would be propagated in the global state. This function now
155+
returns a copy of all registered colormaps to keep the built-in
156+
colormaps untouched.
157+

lib/matplotlib/cm.py

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

18+
import copy
1819
import functools
1920

2021
import numpy as np
@@ -70,7 +71,7 @@ def _gen_cmap_d():
7071

7172

7273
cmap_d = _gen_cmap_d()
73-
locals().update(cmap_d)
74+
locals().update(copy.deepcopy(cmap_d))
7475

7576

7677
# Continue with definitions ...
@@ -95,6 +96,9 @@ def register_cmap(name=None, cmap=None, data=None, lut=None):
9596
and the resulting colormap is registered. Instead of this implicit
9697
colormap creation, create a `.LinearSegmentedColormap` and use the first
9798
case: ``register_cmap(cmap=LinearSegmentedColormap(name, data, lut))``.
99+
100+
If *name* is the same as a built-in colormap this will replace the
101+
built-in Colormap of the same name.
98102
"""
99103
cbook._check_isinstance((str, None), name=name)
100104
if name is None:
@@ -124,26 +128,27 @@ def get_cmap(name=None, lut=None):
124128
"""
125129
Get a colormap instance, defaulting to rc values if *name* is None.
126130
127-
Colormaps added with :func:`register_cmap` take precedence over
128-
built-in colormaps.
131+
Colormaps added with :func:`register_cmap` with the same name as
132+
built-in colormaps will replace them.
129133
130134
Parameters
131135
----------
132136
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`.
137+
If a `.Colormap` instance, a copy of it will be returned.
138+
Otherwise, the name of a colormap known to Matplotlib, which will
139+
be resampled by *lut*. A copy of the requested Colormap is always
140+
returned. The default, None, means :rc:`image.cmap`.
136141
lut : int or None, default: None
137142
If *name* is not already a Colormap instance and *lut* is not None, the
138143
colormap will be resampled to have *lut* entries in the lookup table.
139144
"""
140145
if name is None:
141146
name = mpl.rcParams['image.cmap']
142147
if isinstance(name, colors.Colormap):
143-
return name
148+
return copy.copy(name)
144149
cbook._check_in_list(sorted(cmap_d), name=name)
145150
if lut is None:
146-
return cmap_d[name]
151+
return copy.copy(cmap_d[name])
147152
else:
148153
return cmap_d[name]._resample(lut)
149154

lib/matplotlib/colors.py

+10
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,16 @@ def __copy__(self):
601601
cmapobject._lut = np.copy(self._lut)
602602
return cmapobject
603603

604+
def __eq__(self, other):
605+
if isinstance(other, Colormap):
606+
# To compare lookup tables the Colormaps have to be initialized
607+
if not self._isinit:
608+
self._init()
609+
if not other._isinit:
610+
other._init()
611+
return np.all(self._lut == other._lut)
612+
return False
613+
604614
def set_bad(self, color='k', alpha=None):
605615
"""Set the color for masked values."""
606616
self._rgba_bad = to_rgba(color, alpha)

lib/matplotlib/tests/test_colors.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def test_resample():
6161

6262

6363
def test_register_cmap():
64-
new_cm = copy.copy(plt.cm.viridis)
64+
new_cm = plt.get_cmap('viridis')
6565
cm.register_cmap('viridis2', new_cm)
6666
assert plt.get_cmap('viridis2') == new_cm
6767

@@ -70,6 +70,18 @@ def test_register_cmap():
7070
cm.register_cmap()
7171

7272

73+
def test_colormap_builtin_immutable():
74+
new_cm = plt.get_cmap('viridis')
75+
new_cm.set_over('b')
76+
# Make sure that this didn't mess with the original viridis cmap
77+
assert new_cm != plt.get_cmap('viridis')
78+
79+
# Also test that pyplot access doesn't mess the original up
80+
new_cm = plt.cm.viridis
81+
new_cm.set_over('b')
82+
assert new_cm != plt.get_cmap('viridis')
83+
84+
7385
def test_colormap_copy():
7486
cm = plt.cm.Reds
7587
cm_copy = copy.copy(cm)

0 commit comments

Comments
 (0)