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

Appearance settings

Commit b79039c

Browse filesBrowse files
committed
Add a helper to copy a colormap and set its extreme colors.
See changelog. See also the number of explicit copies in the examples, which suggest that the old setter-based API is a bit of a footgun (as forgetting to copy seems easy).
1 parent 18a30ce commit b79039c
Copy full SHA for b79039c

File tree

Expand file treeCollapse file tree

7 files changed

+48
-33
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+48
-33
lines changed
+13Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
`.Colormap.set_extremes` and `.Colormap.with_extremes`
2+
``````````````````````````````````````````````````````
3+
4+
Because the `.Colormap.set_bad`, `.Colormap.set_under` and `.Colormap.set_over`
5+
methods modify the colormap in place, the user must be careful to first make a
6+
copy of the colormap if setting the extreme colors e.g. for a builtin colormap.
7+
8+
The new ``Colormap.with_extremes(bad=..., under=..., over=...)`` can be used to
9+
first copy the colormap and set the extreme colors on that copy.
10+
11+
The new `.Colormap.set_extremes` method is provided for API symmetry with
12+
`.Colormap.with_extremes`, but note that it suffers from the same issue as the
13+
earlier individual setters.

‎examples/images_contours_and_fields/image_masked.py

Copy file name to clipboardExpand all lines: examples/images_contours_and_fields/image_masked.py
+1-6Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
The second subplot illustrates the use of BoundaryNorm to
99
get a filled contour effect.
1010
"""
11-
from copy import copy
1211

1312
import numpy as np
1413
import matplotlib.pyplot as plt
@@ -25,11 +24,7 @@
2524
Z = (Z1 - Z2) * 2
2625

2726
# Set up a colormap:
28-
# use copy so that we do not mutate the global colormap instance
29-
palette = copy(plt.cm.gray)
30-
palette.set_over('r', 1.0)
31-
palette.set_under('g', 1.0)
32-
palette.set_bad('b', 1.0)
27+
palette = plt.cm.gray.with_extremes(over='r', under='g', bad='b')
3328
# Alternatively, we could use
3429
# palette.set_bad(alpha = 0.0)
3530
# to make the bad region transparent. This is the default.

‎examples/images_contours_and_fields/quadmesh_demo.py

Copy file name to clipboardExpand all lines: examples/images_contours_and_fields/quadmesh_demo.py
+2-6Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
This demo illustrates a bug in quadmesh with masked data.
1010
"""
1111

12-
import copy
13-
1412
from matplotlib import cm, pyplot as plt
1513
import numpy as np
1614

@@ -30,10 +28,8 @@
3028
axs[0].pcolormesh(Qx, Qz, Z, shading='gouraud')
3129
axs[0].set_title('Without masked values')
3230

33-
# You can control the color of the masked region. We copy the default colormap
34-
# before modifying it.
35-
cmap = copy.copy(cm.get_cmap(plt.rcParams['image.cmap']))
36-
cmap.set_bad('y', 1.0)
31+
# You can control the color of the masked region.
32+
cmap = cm.get_cmap(plt.rcParams['image.cmap']).with_extremes(bad='y')
3733
axs[1].pcolormesh(Qx, Qz, Zm, shading='gouraud', cmap=cmap)
3834
axs[1].set_title('With masked values')
3935

‎examples/specialty_plots/leftventricle_bulleye.py

Copy file name to clipboardExpand all lines: examples/specialty_plots/leftventricle_bulleye.py
+2-3Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,8 @@ def bullseye_plot(ax, data, seg_bold=None, cmap=None, norm=None):
176176
# The second example illustrates the use of a ListedColormap, a
177177
# BoundaryNorm, and extended ends to show the "over" and "under"
178178
# value colors.
179-
cmap3 = mpl.colors.ListedColormap(['r', 'g', 'b', 'c'])
180-
cmap3.set_over('0.35')
181-
cmap3.set_under('0.75')
179+
cmap3 = (mpl.colors.ListedColormap(['r', 'g', 'b', 'c'])
180+
.with_extremes(over='0.35', under='0.75'))
182181

183182
# If a ListedColormap is used, the length of the bounds array must be
184183
# one greater than the length of the color list. The bounds must be

‎lib/matplotlib/colors.py

Copy file name to clipboardExpand all lines: lib/matplotlib/colors.py
+24-2Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"""
6363

6464
from collections.abc import Sized
65+
import copy
6566
import functools
6667
import itertools
6768
import re
@@ -609,8 +610,7 @@ def __copy__(self):
609610
return cmapobject
610611

611612
def set_bad(self, color='k', alpha=None):
612-
"""Set color to be used for masked values.
613-
"""
613+
"""Set color to be used for masked values."""
614614
self._rgba_bad = to_rgba(color, alpha)
615615
if self._isinit:
616616
self._set_extremes()
@@ -631,6 +631,28 @@ def set_over(self, color='k', alpha=None):
631631
if self._isinit:
632632
self._set_extremes()
633633

634+
def set_extremes(self, *, bad=None, under=None, over=None):
635+
"""
636+
Set the colors for masked (*bad*) values and, when ``norm.clip =
637+
False``, low (*under*) and high (*over*) out-of-range values.
638+
"""
639+
if bad is not None:
640+
self.set_bad(bad)
641+
if under is not None:
642+
self.set_under(under)
643+
if over is not None:
644+
self.set_over(over)
645+
646+
def with_extremes(self, *, bad=None, under=None, over=None):
647+
"""
648+
Return a copy of the colormap, for which the colors for masked (*bad*)
649+
values and, when ``norm.clip = False``, low (*under*) and high (*over*)
650+
out-of-range values, have been set accordingly.
651+
"""
652+
new_cm = copy.copy(self)
653+
new_cm.set_extremes(bad=bad, under=under, over=over)
654+
return new_cm
655+
634656
def _set_extremes(self):
635657
if self._rgba_under:
636658
self._lut[self._i_under] = self._rgba_under

‎lib/matplotlib/tests/test_image.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_image.py
+2-9Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from contextlib import ExitStack
2-
from copy import copy
32
import io
43
import os
54
from pathlib import Path
@@ -854,10 +853,7 @@ def test_mask_image_over_under():
854853
(2 * np.pi * 0.5 * 1.5))
855854
Z = 10*(Z2 - Z1) # difference of Gaussians
856855

857-
palette = copy(plt.cm.gray)
858-
palette.set_over('r', 1.0)
859-
palette.set_under('g', 1.0)
860-
palette.set_bad('b', 1.0)
856+
palette = plt.cm.gray.with_extremes(over='r', under='g', bad='b')
861857
Zm = ma.masked_where(Z > 1.2, Z)
862858
fig, (ax1, ax2) = plt.subplots(1, 2)
863859
im = ax1.imshow(Zm, interpolation='bilinear',
@@ -915,10 +911,7 @@ def test_imshow_endianess():
915911
remove_text=True, style='mpl20')
916912
def test_imshow_masked_interpolation():
917913

918-
cm = copy(plt.get_cmap('viridis'))
919-
cm.set_over('r')
920-
cm.set_under('b')
921-
cm.set_bad('k')
914+
cm = plt.get_cmap('viridis').with_extremes(over='r', under='b', bad='k')
922915

923916
N = 20
924917
n = colors.Normalize(vmin=0, vmax=N*N-1)

‎tutorials/colors/colorbar_only.py

Copy file name to clipboardExpand all lines: tutorials/colors/colorbar_only.py
+4-7Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,8 @@
6464
fig, ax = plt.subplots(figsize=(6, 1))
6565
fig.subplots_adjust(bottom=0.5)
6666

67-
cmap = mpl.colors.ListedColormap(['red', 'green', 'blue', 'cyan'])
68-
cmap.set_over('0.25')
69-
cmap.set_under('0.75')
67+
cmap = (mpl.colors.ListedColormap(['red', 'green', 'blue', 'cyan'])
68+
.with_extremes(over='0.25', under='0.75'))
7069

7170
bounds = [1, 2, 4, 7, 8]
7271
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)
@@ -91,10 +90,8 @@
9190
fig, ax = plt.subplots(figsize=(6, 1))
9291
fig.subplots_adjust(bottom=0.5)
9392

94-
cmap = mpl.colors.ListedColormap(['royalblue', 'cyan',
95-
'yellow', 'orange'])
96-
cmap.set_over('red')
97-
cmap.set_under('blue')
93+
cmap = (mpl.colors.ListedColormap(['royalblue', 'cyan', 'yellow', 'orange'])
94+
.with_extremes(over='red', under='blue'))
9895

9996
bounds = [-1.0, -0.5, 0.0, 0.5, 1.0]
10097
norm = mpl.colors.BoundaryNorm(bounds, cmap.N)

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.