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

Skip to content

Commit ebb7a9d

Browse files
committed
Merge pull request #2050 from pelson/colormap_api
Added the from_levels_and_colors function.
1 parent 3ac8144 commit ebb7a9d

File tree

6 files changed

+187
-10
lines changed

6 files changed

+187
-10
lines changed

doc/api/api_changes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ Changes in 1.3.x
104104
Deep copying a `Path` always creates an editable (i.e. non-readonly)
105105
`Path`.
106106

107+
* matplotlib.colors.normalize and matplotlib.colors.no_norm have been
108+
deprecated in favour of matplotlib.colors.Normalize and
109+
matplotlib.colors.NoNorm respectively.
110+
107111
* The `font.*` rcParams now affect only text objects created after the
108112
rcParam has been set, and will not retroactively affect already
109113
existing text objects. This brings their behavior in line with most

doc/users/whats_new.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@ rcParam has been set, and will not retroactively affect already
8888
existing text objects. This brings their behavior in line with most
8989
other rcParams.
9090

91+
Easier creation of colormap and normalizer for levels with colors
92+
-----------------------------------------------------------------
93+
Phil Elson added the :func:`matplotlib.colors.from_levels_and_colors`
94+
function to easily create a colormap and normalizer for representation
95+
of discrete colors for plot types such as
96+
:func:`matplotlib.pyplot.pcolormesh`, with a similar interface to that of
97+
contourf.
98+
9199
Catch opening too many figures using pyplot
92100
-------------------------------------------
93101
Figures created through `pyplot.figure` are retained until they are

lib/matplotlib/colorbar.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -852,8 +852,8 @@ def __init__(self, ax, mappable, **kw):
852852
mappable.autoscale_None()
853853

854854
self.mappable = mappable
855-
kw['cmap'] = mappable.cmap
856-
kw['norm'] = mappable.norm
855+
kw['cmap'] = cmap = mappable.cmap
856+
kw['norm'] = norm = mappable.norm
857857

858858
if isinstance(mappable, contour.ContourSet):
859859
CS = mappable
@@ -868,6 +868,9 @@ def __init__(self, ax, mappable, **kw):
868868
if not CS.filled:
869869
self.add_lines(CS)
870870
else:
871+
if getattr(cmap, 'colorbar_extend', False) is not False:
872+
kw.setdefault('extend', cmap.colorbar_extend)
873+
871874
if isinstance(mappable, martist.Artist):
872875
kw['alpha'] = mappable.get_alpha()
873876

lib/matplotlib/colors.py

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,12 @@ def __init__(self, name, N=256):
508508
self._i_bad = N + 2
509509
self._isinit = False
510510

511+
#: When this colormap exists on a scalar mappable and colorbar_extend
512+
#: is not False, colorbar creation will pick up ``colorbar_extend`` as
513+
#: the default value for the ``extend`` keyword in the
514+
#: :class:`matplotlib.colorbar.Colorbar` constructor.
515+
self.colorbar_extend = False
516+
511517
def __call__(self, X, alpha=None, bytes=False):
512518
"""
513519
Parameters
@@ -832,7 +838,7 @@ def _init(self):
832838
class Normalize(object):
833839
"""
834840
A class which, when called, can normalize data into
835-
the ``[0, 1]`` interval.
841+
the ``[0.0, 1.0]`` interval.
836842
837843
"""
838844
def __init__(self, vmin=None, vmax=None, clip=False):
@@ -1212,8 +1218,12 @@ def inverse(self, value):
12121218
return value
12131219

12141220
# compatibility with earlier class names that violated convention:
1215-
normalize = Normalize
1216-
no_norm = NoNorm
1221+
normalize = cbook.deprecated('1.3', alternative='Normalize',
1222+
name='normalize',
1223+
obj_type='class alias')(Normalize)
1224+
no_norm = cbook.deprecated('1.3', alternative='NoNorm',
1225+
name='no_norm',
1226+
obj_type='class alias')(NoNorm)
12171227

12181228

12191229
def rgb_to_hsv(arr):
@@ -1405,3 +1415,71 @@ def shade_rgb(self, rgb, elevation, fraction=1.):
14051415
hsv[:, :, 1:] = np.where(hsv[:, :, 1:] > 1., 1, hsv[:, :, 1:])
14061416
# convert modified hsv back to rgb.
14071417
return hsv_to_rgb(hsv)
1418+
1419+
1420+
def from_levels_and_colors(levels, colors, extend='neither'):
1421+
"""
1422+
A helper routine to generate a cmap and a norm instance which
1423+
behave similar to contourf's levels and colors arguments.
1424+
1425+
Parameters
1426+
----------
1427+
levels : sequence of numbers
1428+
The quantization levels used to construct the :class:`BoundaryNorm`.
1429+
Values ``v`` are quantizized to level ``i`` if
1430+
``lev[i] <= v < lev[i+1]``.
1431+
colors : sequence of colors
1432+
The fill color to use for each level. If `extend` is "neither" there
1433+
must be ``n_level - 1`` colors. For an `extend` of "min" or "max" add
1434+
one extra color, and for an `extend` of "both" add two colors.
1435+
extend : {'neither', 'min', 'max', 'both'}, optional
1436+
The behaviour when a value falls out of range of the given levels.
1437+
See :func:`~matplotlib.pyplot.contourf` for details.
1438+
1439+
Returns
1440+
-------
1441+
(cmap, norm) : tuple containing a :class:`Colormap` and a \
1442+
:class:`Normalize` instance
1443+
"""
1444+
colors_i0 = 0
1445+
colors_i1 = None
1446+
1447+
if extend == 'both':
1448+
colors_i0 = 1
1449+
colors_i1 = -1
1450+
extra_colors = 2
1451+
elif extend == 'min':
1452+
colors_i0 = 1
1453+
extra_colors = 1
1454+
elif extend == 'max':
1455+
colors_i1 = -1
1456+
extra_colors = 1
1457+
elif extend == 'neither':
1458+
extra_colors = 0
1459+
else:
1460+
raise ValueError('Unexpected value for extend: {0!r}'.format(extend))
1461+
1462+
n_data_colors = len(levels) - 1
1463+
n_expected_colors = n_data_colors + extra_colors
1464+
if len(colors) != n_expected_colors:
1465+
raise ValueError('With extend == {0!r} and n_levels == {1!r} expected'
1466+
' n_colors == {2!r}. Got {3!r}.'
1467+
''.format(extend, len(levels), n_expected_colors,
1468+
len(colors)))
1469+
1470+
cmap = ListedColormap(colors[colors_i0:colors_i1], N=n_data_colors)
1471+
1472+
if extend in ['min', 'both']:
1473+
cmap.set_under(colors[0])
1474+
else:
1475+
cmap.set_under('none')
1476+
1477+
if extend in ['max', 'both']:
1478+
cmap.set_over(colors[-1])
1479+
else:
1480+
cmap.set_over('none')
1481+
1482+
cmap.colorbar_extend = extend
1483+
1484+
norm = BoundaryNorm(levels, ncolors=n_data_colors)
1485+
return cmap, norm

lib/matplotlib/tests/test_colors.py

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
"""
2-
Tests for the colors module.
3-
"""
4-
51
from __future__ import print_function
2+
from nose.tools import assert_raises
63
import numpy as np
74
from numpy.testing.utils import assert_array_equal, assert_array_almost_equal
5+
6+
87
import matplotlib.colors as mcolors
98
import matplotlib.cm as cm
9+
import matplotlib.pyplot as plt
10+
from matplotlib.testing.decorators import image_comparison
11+
1012

1113
def test_colormap_endian():
1214
"""
@@ -23,6 +25,7 @@ def test_colormap_endian():
2325
#print(anative.dtype.isnative, aforeign.dtype.isnative)
2426
assert_array_equal(cmap(anative), cmap(aforeign))
2527

28+
2629
def test_BoundaryNorm():
2730
"""
2831
Github issue #1258: interpolation was failing with numpy
@@ -36,7 +39,8 @@ def test_BoundaryNorm():
3639
ncolors = len(boundaries)
3740
bn = mcolors.BoundaryNorm(boundaries, ncolors)
3841
assert_array_equal(bn(vals), expected)
39-
42+
43+
4044
def test_LogNorm():
4145
"""
4246
LogNorm igornoed clip, now it has the same
@@ -46,6 +50,7 @@ def test_LogNorm():
4650
ln = mcolors.LogNorm(clip=True, vmax=5)
4751
assert_array_equal(ln([1, 6]), [0, 1.0])
4852

53+
4954
def test_Normalize():
5055
norm = mcolors.Normalize()
5156
vals = np.arange(-10, 10, 1, dtype=np.float)
@@ -74,6 +79,7 @@ def _inverse_tester(norm_instance, vals):
7479
"""
7580
assert_array_almost_equal(norm_instance.inverse(norm_instance(vals)), vals)
7681

82+
7783
def _scalar_tester(norm_instance, vals):
7884
"""
7985
Checks if scalars and arrays are handled the same way.
@@ -82,10 +88,88 @@ def _scalar_tester(norm_instance, vals):
8288
scalar_result = [norm_instance(float(v)) for v in vals]
8389
assert_array_almost_equal(scalar_result, norm_instance(vals))
8490

91+
8592
def _mask_tester(norm_instance, vals):
8693
"""
8794
Checks mask handling
8895
"""
8996
masked_array = np.ma.array(vals)
9097
masked_array[0] = np.ma.masked
9198
assert_array_equal(masked_array.mask, norm_instance(masked_array).mask)
99+
100+
101+
@image_comparison(baseline_images=['levels_and_colors'],
102+
extensions=['png'])
103+
def test_cmap_and_norm_from_levels_and_colors():
104+
data = np.linspace(-2, 4, 49).reshape(7, 7)
105+
levels = [-1, 2, 2.5, 3]
106+
colors = ['red', 'green', 'blue', 'yellow', 'black']
107+
extend = 'both'
108+
cmap, norm = mcolors.from_levels_and_colors(levels, colors, extend=extend)
109+
110+
ax = plt.axes()
111+
m = plt.pcolormesh(data, cmap=cmap, norm=norm)
112+
plt.colorbar(m)
113+
114+
# Hide the axes labels (but not the colorbar ones, as they are useful)
115+
for lab in ax.get_xticklabels() + ax.get_yticklabels():
116+
lab.set_visible(False)
117+
118+
119+
def test_cmap_and_norm_from_levels_and_colors2():
120+
levels = [-1, 2, 2.5, 3]
121+
colors = ['red', (0, 1, 0), 'blue', (0.5, 0.5, 0.5), (0.0, 0.0, 0.0, 1.0)]
122+
clr = mcolors.colorConverter.to_rgba_array(colors)
123+
bad = (0.1, 0.1, 0.1, 0.1)
124+
no_color = (0.0, 0.0, 0.0, 0.0)
125+
126+
# Define the test values which are of interest.
127+
# Note: levels are lev[i] <= v < lev[i+1]
128+
tests = [('both', None, {-2: clr[0],
129+
-1: clr[1],
130+
2: clr[2],
131+
2.25: clr[2],
132+
3: clr[4],
133+
3.5: clr[4],
134+
np.ma.array(1, mask=True): bad}),
135+
136+
('min', -1, {-2: clr[0],
137+
-1: clr[1],
138+
2: clr[2],
139+
2.25: clr[2],
140+
3: no_color,
141+
3.5: no_color,
142+
np.ma.array(1, mask=True): bad}),
143+
144+
('max', -1, {-2: no_color,
145+
-1: clr[0],
146+
2: clr[1],
147+
2.25: clr[1],
148+
3: clr[3],
149+
3.5: clr[3],
150+
np.ma.array(1, mask=True): bad}),
151+
152+
('neither', -2, {-2: no_color,
153+
-1: clr[0],
154+
2: clr[1],
155+
2.25: clr[1],
156+
3: no_color,
157+
3.5: no_color,
158+
np.ma.array(1, mask=True): bad}),
159+
]
160+
161+
for extend, i1, cases in tests:
162+
cmap, norm = mcolors.from_levels_and_colors(levels, colors[0:i1],
163+
extend=extend)
164+
cmap.set_bad(bad)
165+
for d_val, expected_color in sorted(cases.items()):
166+
assert_array_equal(expected_color, cmap(norm([d_val]))[0],
167+
'Wih extend={0!r} and data '
168+
'value={1!r}'.format(extend, d_val))
169+
170+
assert_raises(ValueError, mcolors.from_levels_and_colors, levels, colors)
171+
172+
173+
if __name__ == '__main__':
174+
import nose
175+
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)

0 commit comments

Comments
 (0)