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 ef7c088

Browse filesBrowse files
DivergingNorm Fair
1 parent 418984d commit ef7c088
Copy full SHA for ef7c088

File tree

Expand file treeCollapse file tree

5 files changed

+192
-93
lines changed
Filter options
Expand file treeCollapse file tree

5 files changed

+192
-93
lines changed
+27Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Fair DivergingNorm
2+
------------------
3+
`~.DivergingNorm` now has an argument ``fair``, which can be set to ``True``
4+
in order to create an off-centered normalization with equally spaced colors.
5+
6+
..plot::
7+
8+
import numpy as np
9+
import matplotlib.pyplot as plt
10+
from matplotlib.colors import DivergingNorm
11+
12+
np.random.seed(19680801)
13+
data = np.random.rand(4, 11)
14+
15+
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(7, 2))
16+
17+
norm1 = DivergingNorm(0.25, vmin=0, vmax=1, fair=False)
18+
im = ax1.imshow(data, cmap='RdBu', norm=norm1)
19+
cbar = fig.colorbar(im, ax=ax1, orientation="horizontal", aspect=15)
20+
21+
norm2 = DivergingNorm(0.25, vmin=0, vmax=1, fair=True)
22+
im = ax2.imshow(data, cmap='RdBu', norm=norm2)
23+
cbar = fig.colorbar(im, ax=ax2, orientation="horizontal", aspect=15)
24+
25+
ax1.set_title("DivergingNorm(.., fair=False)")
26+
ax2.set_title("DivergingNorm(.., fair=True)")
27+
plt.show()

‎examples/userdemo/colormap_normalizations_diverging.py

Copy file name to clipboardExpand all lines: examples/userdemo/colormap_normalizations_diverging.py
+46-9Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,23 @@
22
=====================================
33
DivergingNorm colormap normalization
44
=====================================
5-
6-
Sometimes we want to have a different colormap on either side of a
7-
conceptual center point, and we want those two colormaps to have
8-
different linear scales. An example is a topographic map where the land
9-
and ocean have a center at zero, but land typically has a greater
10-
elevation range than the water has depth range, and they are often
11-
represented by a different colormap.
125
"""
136

7+
##############################################################################
8+
# ..divergingnorm_diffmap
9+
#
10+
# Different mapping on either side of a center
11+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
12+
#
13+
# Sometimes we want to have a different colormap on either side of a
14+
# conceptual center point, and we want those two colormaps to have
15+
# different linear scales. An example is a topographic map where the land
16+
# and ocean have a center at zero, but land typically has a greater
17+
# elevation range than the water has depth range, and they are often
18+
# represented by a different colormap.
19+
# This achieved with a `~.DivergingNorm` and by setting its ``vcenter``
20+
# argument to zero.
21+
1422
import numpy as np
1523
import matplotlib.pyplot as plt
1624
import matplotlib.cbook as cbook
@@ -29,16 +37,45 @@
2937
colors_land = plt.cm.terrain(np.linspace(0.25, 1, 256))
3038
all_colors = np.vstack((colors_undersea, colors_land))
3139
terrain_map = colors.LinearSegmentedColormap.from_list('terrain_map',
32-
all_colors)
40+
all_colors)
3341

3442
# make the norm: Note the center is offset so that the land has more
3543
# dynamic range:
3644
divnorm = colors.DivergingNorm(vmin=-500, vcenter=0, vmax=4000)
3745

3846
pcm = ax.pcolormesh(longitude, latitude, topo, rasterized=True, norm=divnorm,
39-
cmap=terrain_map,)
47+
cmap=terrain_map)
4048
ax.set_xlabel('Lon $[^o E]$')
4149
ax.set_ylabel('Lat $[^o N]$')
4250
ax.set_aspect(1 / np.cos(np.deg2rad(49)))
4351
fig.colorbar(pcm, shrink=0.6, extend='both', label='Elevation [m]')
4452
plt.show()
53+
54+
55+
##############################################################################
56+
# ..divergingnorm_fairmap
57+
#
58+
# Fair mapping on either side of a center
59+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
60+
#
61+
# On other occasions it may be useful to preserve the linear mapping to colors,
62+
# but still define a center point, such that the colormap extends to both sides
63+
# of the center equally. This can be achieved by using the ``fair=True``
64+
# argument of the `~.DivergingNorm`.
65+
66+
np.random.seed(19680801)
67+
data = np.random.rand(11, 11)
68+
69+
fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(8, 3.5))
70+
71+
norm1 = colors.DivergingNorm(0.25, vmin=0, vmax=1, fair=False)
72+
im = ax1.imshow(data, cmap='RdBu', norm=norm1)
73+
cbar = fig.colorbar(im, ax=ax1, ticks=[0, 0.25, 0.5, 0.75, 1])
74+
75+
norm2 = colors.DivergingNorm(0.25, vmin=0, vmax=1, fair=True)
76+
im = ax2.imshow(data, cmap='RdBu', norm=norm2)
77+
cbar = fig.colorbar(im, ax=ax2, ticks=[0, 0.25, 0.5, 0.75, 1])
78+
79+
ax1.set_title("DivergingNorm(.., fair=False)")
80+
ax2.set_title("DivergingNorm(.., fair=True)")
81+
plt.show()

‎lib/matplotlib/colors.py

Copy file name to clipboardExpand all lines: lib/matplotlib/colors.py
+37-26Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1061,11 +1061,11 @@ def scaled(self):
10611061

10621062

10631063
class DivergingNorm(Normalize):
1064-
def __init__(self, vcenter, vmin=None, vmax=None):
1064+
def __init__(self, vcenter, vmin=None, vmax=None, fair=False):
10651065
"""
10661066
Normalize data with a set center.
10671067
1068-
Useful when mapping data with an unequal rates of change around a
1068+
Useful when mapping data around a
10691069
conceptual center, e.g., data that range from -2 to 4, with 0 as
10701070
the midpoint.
10711071
@@ -1079,6 +1079,12 @@ def __init__(self, vcenter, vmin=None, vmax=None):
10791079
vmax : float, optional
10801080
The data value that defines ``1.0`` in the normalization.
10811081
Defaults to the the max value of the dataset.
1082+
fair : bool, optional
1083+
If *False* (default), the range between vmin and vmax will be
1084+
mapped to the normalized range ``[0,1]``. If *True*, the range
1085+
``[vcenter-d, vcenter+d]`` with
1086+
``d=max(abs(vcenter-vmin), abs(vmax-vcenter))`` is mapped to
1087+
``[0,1]``. This is useful to ensure colors are equally distributed.
10821088
10831089
Examples
10841090
--------
@@ -1091,40 +1097,45 @@ def __init__(self, vcenter, vmin=None, vmax=None):
10911097
>>> data = [-4000., -2000., 0., 2500., 5000., 7500., 10000.]
10921098
>>> offset(data)
10931099
array([0., 0.25, 0.5, 0.625, 0.75, 0.875, 1.0])
1100+
1101+
A more detailed example is found in
1102+
:doc:`/examples/userdemo/colormap_normalizations_diverging`
10941103
"""
10951104

10961105
self.vcenter = vcenter
1097-
self.vmin = vmin
1098-
self.vmax = vmax
1099-
if vcenter is not None and vmax is not None and vcenter >= vmax:
1100-
raise ValueError('vmin, vcenter, and vmax must be in '
1101-
'ascending order')
1102-
if vcenter is not None and vmin is not None and vcenter <= vmin:
1103-
raise ValueError('vmin, vcenter, and vmax must be in '
1104-
'ascending order')
1105-
1106-
def autoscale_None(self, A):
1107-
"""
1108-
Get vmin and vmax, and then clip at vcenter
1109-
"""
1110-
super().autoscale_None(A)
1111-
if self.vmin > self.vcenter:
1112-
self.vmin = self.vcenter
1113-
if self.vmax < self.vcenter:
1114-
self.vmax = self.vcenter
1106+
self.fair = fair
1107+
super().__init__(vmin=vmin, vmax=vmax)
11151108

11161109
def __call__(self, value, clip=None):
11171110
"""
1118-
Map value to the interval [0, 1]. The clip argument is unused.
1111+
Map value to (a subset of) the interval [0, 1].
1112+
The clip argument is unused.
11191113
"""
11201114
result, is_scalar = self.process_value(value)
11211115
self.autoscale_None(result) # sets self.vmin, self.vmax if None
11221116

1123-
if not self.vmin <= self.vcenter <= self.vmax:
1124-
raise ValueError("vmin, vcenter, vmax must increase monotonically")
1125-
result = np.ma.masked_array(
1126-
np.interp(result, [self.vmin, self.vcenter, self.vmax],
1127-
[0, 0.5, 1.]), mask=np.ma.getmask(result))
1117+
if self.vmin > self.vmax:
1118+
raise ValueError("vmin must be less or equal vmax")
1119+
elif self.vmin == self.vmax:
1120+
interp_x = [self.vmin, self.vmax]
1121+
interp_y = [0.0, 0.0]
1122+
elif self.vcenter >= self.vmax:
1123+
interp_x = [self.vmin, self.vcenter]
1124+
interp_y = [0.0, 0.5]
1125+
elif self.vcenter <= self.vmin:
1126+
interp_x = [self.vcenter, self.vmax]
1127+
interp_y = [0.5, 1.0]
1128+
elif self.fair:
1129+
maxrange = max(np.abs(self.vcenter - self.vmin),
1130+
np.abs(self.vmax - self.vcenter))
1131+
interp_x = [self.vcenter - maxrange, self.vcenter + maxrange]
1132+
interp_y = [0, 1.]
1133+
else:
1134+
interp_x = [self.vmin, self.vcenter, self.vmax]
1135+
interp_y = [0, 0.5, 1.]
1136+
1137+
result = np.ma.masked_array(np.interp(result, interp_x, interp_y),
1138+
mask=np.ma.getmask(result))
11281139
if is_scalar:
11291140
result = np.atleast_1d(result)[0]
11301141
return result

‎lib/matplotlib/tests/test_colors.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_colors.py
+34-48Lines changed: 34 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -324,58 +324,44 @@ def test_DivergingNorm_scale():
324324

325325

326326
def test_DivergingNorm_scaleout_center():
327-
# test the vmin never goes above vcenter
327+
# test vcenter outside [vmin, vmax]
328328
norm = mcolors.DivergingNorm(vcenter=0)
329-
norm([1, 2, 3, 5])
330-
assert norm.vmin == 0
331-
assert norm.vmax == 5
332-
329+
a = norm([1., 2., 3., 5.])
330+
assert norm.vmin == 1.
331+
assert norm.vmax == 5.
332+
assert_array_almost_equal(a, np.array([0.6, 0.7, 0.8, 1.0]))
333333

334-
def test_DivergingNorm_scaleout_center_max():
335-
# test the vmax never goes below vcenter
336334
norm = mcolors.DivergingNorm(vcenter=0)
337-
norm([-1, -2, -3, -5])
338-
assert norm.vmax == 0
339-
assert norm.vmin == -5
340-
341-
342-
def test_DivergingNorm_Even():
343-
norm = mcolors.DivergingNorm(vmin=-1, vcenter=0, vmax=4)
344-
vals = np.array([-1.0, -0.5, 0.0, 1.0, 2.0, 3.0, 4.0])
345-
expected = np.array([0.0, 0.25, 0.5, 0.625, 0.75, 0.875, 1.0])
346-
assert_array_equal(norm(vals), expected)
347-
348-
349-
def test_DivergingNorm_Odd():
350-
norm = mcolors.DivergingNorm(vmin=-2, vcenter=0, vmax=5)
351-
vals = np.array([-2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0])
352-
expected = np.array([0.0, 0.25, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0])
353-
assert_array_equal(norm(vals), expected)
354-
355-
356-
def test_DivergingNorm_VminEqualsVcenter():
357-
with pytest.raises(ValueError):
358-
mcolors.DivergingNorm(vmin=-2, vcenter=-2, vmax=2)
359-
360-
361-
def test_DivergingNorm_VmaxEqualsVcenter():
362-
with pytest.raises(ValueError):
363-
mcolors.DivergingNorm(vmin=-2, vcenter=2, vmax=2)
364-
365-
366-
def test_DivergingNorm_VminGTVcenter():
367-
with pytest.raises(ValueError):
368-
mcolors.DivergingNorm(vmin=10, vcenter=0, vmax=20)
369-
370-
371-
def test_DivergingNorm_DivergingNorm_VminGTVmax():
372-
with pytest.raises(ValueError):
373-
mcolors.DivergingNorm(vmin=10, vcenter=0, vmax=5)
374-
375-
376-
def test_DivergingNorm_VcenterGTVmax():
335+
a = norm([-1., -2., -3., -5.])
336+
assert norm.vmax == -1.
337+
assert norm.vmin == -5.
338+
assert_array_almost_equal(a, np.array([0.4, 0.3, 0.2, 0.0]))
339+
340+
341+
@pytest.mark.parametrize("vmin,vc,vmax,fair,vals,expect",
342+
[[-1, 0, 4, False, [-1.0, -0.5, 0.0, 1.0, 2.0, 3.0, 4.0],
343+
[0.0, 0.25, 0.5, 0.625, 0.75, 0.875, 1.0]],
344+
[-2, 0, 5, False, [-2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0, 5.0],
345+
[0.0, 0.25, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]],
346+
[-2, -2, 2, False, [-3, -2, -1, 0, 1, 2, 3],
347+
[0.5, 0.5, 0.625, 0.75, 0.875, 1., 1.]],
348+
[-2, 2, 2, False, [-3, -2, -1, 0, 1, 2, 3],
349+
[0., 0., 0.125, 0.25, 0.375, 0.5, 0.5]],
350+
[10, 0, 20, False, [0, 5, 10, 15, 20], [0.5, 0.625, 0.75, 0.875, 1.]],
351+
[10, 30, 20, False, [10, 15, 20, 25, 30], [0., 0.125, 0.25, 0.375, 0.5]],
352+
[-4, -4, -4, False, [-8, -6, -4, -2, 0], [0., 0., 0., 0., 0.]],
353+
[-6, 0, 12, True, [-12, -6, -3, 0, 6, 12],
354+
[0., 0.25, 0.375, 0.5, 0.75, 1.]]])
355+
def test_DivergingNorm_Misc(vmin, vc, vmax, fair, vals, expect):
356+
norm = mcolors.DivergingNorm(vmin=vmin, vcenter=vc, vmax=vmax, fair=fair)
357+
assert_array_equal(norm(vals), np.array(expect))
358+
359+
360+
def test_DivergingNorm_rasing():
361+
# test for vmin > vmax -> not allowed
377362
with pytest.raises(ValueError):
378-
mcolors.DivergingNorm(vmin=10, vcenter=25, vmax=20)
363+
norm = mcolors.DivergingNorm(vmin=10, vcenter=0, vmax=5)
364+
norm(np.array([-3, -2, -1, 0, 1, 2, 3]))
379365

380366

381367
def test_DivergingNorm_premature_scaling():

0 commit comments

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