Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added the from_levels_and_colors function. #2050

Merged
merged 1 commit into from
May 28, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/api/api_changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ Changes in 1.3.x
Deep copying a `Path` always creates an editable (i.e. non-readonly)
`Path`.

* matplotlib.colors.normalize and matplotlib.colors.no_norm have been
deprecated in favour of matplotlib.colors.Normalize and
matplotlib.colors.NoNorm respectively.

* The `font.*` rcParams now affect only text objects created after the
rcParam has been set, and will not retroactively affect already
existing text objects. This brings their behavior in line with most
Expand Down
8 changes: 8 additions & 0 deletions doc/users/whats_new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ rcParam has been set, and will not retroactively affect already
existing text objects. This brings their behavior in line with most
other rcParams.

Easier creation of colormap and normalizer for levels with colors
-----------------------------------------------------------------
Phil Elson added the :func:`matplotlib.colors.from_levels_and_colors`
function to easily create a colormap and normalizer for representation
of discrete colors for plot types such as
:func:`matplotlib.pyplot.pcolormesh`, with a similar interface to that of
contourf.

Catch opening too many figures using pyplot
-------------------------------------------
Figures created through `pyplot.figure` are retained until they are
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/cbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ def deprecate(func, message=message, name=name, alternative=alternative,
name = func.__name__

message = _generate_deprecation_message(
since, message, name, alternative, pending, 'function')
since, message, name, alternative, pending, obj_type)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching that.


@functools.wraps(func)
def deprecated_func(*args, **kwargs):
Expand Down
7 changes: 5 additions & 2 deletions lib/matplotlib/colorbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -853,8 +853,8 @@ def __init__(self, ax, mappable, **kw):
mappable.autoscale_None()

self.mappable = mappable
kw['cmap'] = mappable.cmap
kw['norm'] = mappable.norm
kw['cmap'] = cmap = mappable.cmap
kw['norm'] = norm = mappable.norm

if isinstance(mappable, contour.ContourSet):
CS = mappable
Expand All @@ -869,6 +869,9 @@ def __init__(self, ax, mappable, **kw):
if not CS.filled:
self.add_lines(CS)
else:
if getattr(cmap, 'colorbar_extend', False) is not False:
kw.setdefault('extend', cmap.colorbar_extend)

if isinstance(mappable, martist.Artist):
kw['alpha'] = mappable.get_alpha()

Expand Down
84 changes: 81 additions & 3 deletions lib/matplotlib/colors.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,12 @@ def __init__(self, name, N=256):
self._i_bad = N + 2
self._isinit = False

#: When this colormap exists on a scalar mappable and colorbar_extend
#: is not False, colorbar creation will pick up ``colorbar_extend`` as
#: the default value for the ``extend`` keyword in the
#: :class:`matplotlib.colorbar.Colorbar` constructor.
self.colorbar_extend = False

def __call__(self, X, alpha=None, bytes=False):
"""
Parameters
Expand Down Expand Up @@ -832,7 +838,7 @@ def _init(self):
class Normalize(object):
"""
A class which, when called, can normalize data into
the ``[0, 1]`` interval.
the ``[0.0, 1.0]`` interval.

"""
def __init__(self, vmin=None, vmax=None, clip=False):
Expand Down Expand Up @@ -1212,8 +1218,12 @@ def inverse(self, value):
return value

# compatibility with earlier class names that violated convention:
normalize = Normalize
no_norm = NoNorm
normalize = cbook.deprecated('1.3', alternative='Normalize',
name='normalize',
obj_type='class alias')(Normalize)
no_norm = cbook.deprecated('1.3', alternative='NoNorm',
name='no_norm',
obj_type='class alias')(NoNorm)


def rgb_to_hsv(arr):
Expand Down Expand Up @@ -1405,3 +1415,71 @@ def shade_rgb(self, rgb, elevation, fraction=1.):
hsv[:, :, 1:] = np.where(hsv[:, :, 1:] > 1., 1, hsv[:, :, 1:])
# convert modified hsv back to rgb.
return hsv_to_rgb(hsv)


def from_levels_and_colors(levels, colors, extend='neither'):
"""
A helper routine to generate a cmap and a norm instance which
behave similar to contourf's levels and colors arguments.

Parameters
----------
levels : sequence of numbers
The quantization levels used to construct the :class:`BoundaryNorm`.
Values ``v`` are quantizized to level ``i`` if
``lev[i] <= v < lev[i+1]``.
colors : sequence of colors
The fill color to use for each level. If `extend` is "neither" there
must be ``n_level - 1`` colors. For an `extend` of "min" or "max" add
one extra color, and for an `extend` of "both" add two colors.
extend : {'neither', 'min', 'max', 'both'}, optional
The behaviour when a value falls out of range of the given levels.
See :func:`~matplotlib.pyplot.contourf` for details.

Returns
-------
(cmap, norm) : tuple containing a :class:`Colormap` and a \
:class:`Normalize` instance
"""
colors_i0 = 0
colors_i1 = None

if extend == 'both':
colors_i0 = 1
colors_i1 = -1
extra_colors = 2
elif extend == 'min':
colors_i0 = 1
extra_colors = 1
elif extend == 'max':
colors_i1 = -1
extra_colors = 1
elif extend == 'neither':
extra_colors = 0
else:
raise ValueError('Unexpected value for extend: {0!r}'.format(extend))

n_data_colors = len(levels) - 1
n_expected_colors = n_data_colors + extra_colors
if len(colors) != n_expected_colors:
raise ValueError('With extend == {0!r} and n_levels == {1!r} expected'
' n_colors == {2!r}. Got {3!r}.'
''.format(extend, len(levels), n_expected_colors,
len(colors)))

cmap = ListedColormap(colors[colors_i0:colors_i1], N=n_data_colors)

if extend in ['min', 'both']:
cmap.set_under(colors[0])
else:
cmap.set_under('none')

if extend in ['max', 'both']:
cmap.set_over(colors[-1])
else:
cmap.set_over('none')

cmap.colorbar_extend = extend

norm = BoundaryNorm(levels, ncolors=n_data_colors)
return cmap, norm
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
94 changes: 89 additions & 5 deletions lib/matplotlib/tests/test_colors.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
"""
Tests for the colors module.
"""

from __future__ import print_function
from nose.tools import assert_raises
import numpy as np
from numpy.testing.utils import assert_array_equal, assert_array_almost_equal


import matplotlib.colors as mcolors
import matplotlib.cm as cm
import matplotlib.pyplot as plt
from matplotlib.testing.decorators import image_comparison


def test_colormap_endian():
"""
Expand All @@ -23,6 +25,7 @@ def test_colormap_endian():
#print(anative.dtype.isnative, aforeign.dtype.isnative)
assert_array_equal(cmap(anative), cmap(aforeign))


def test_BoundaryNorm():
"""
Github issue #1258: interpolation was failing with numpy
Expand All @@ -36,7 +39,8 @@ def test_BoundaryNorm():
ncolors = len(boundaries)
bn = mcolors.BoundaryNorm(boundaries, ncolors)
assert_array_equal(bn(vals), expected)



def test_LogNorm():
"""
LogNorm igornoed clip, now it has the same
Expand All @@ -46,6 +50,7 @@ def test_LogNorm():
ln = mcolors.LogNorm(clip=True, vmax=5)
assert_array_equal(ln([1, 6]), [0, 1.0])


def test_Normalize():
norm = mcolors.Normalize()
vals = np.arange(-10, 10, 1, dtype=np.float)
Expand Down Expand Up @@ -74,6 +79,7 @@ def _inverse_tester(norm_instance, vals):
"""
assert_array_almost_equal(norm_instance.inverse(norm_instance(vals)), vals)


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


def _mask_tester(norm_instance, vals):
"""
Checks mask handling
"""
masked_array = np.ma.array(vals)
masked_array[0] = np.ma.masked
assert_array_equal(masked_array.mask, norm_instance(masked_array).mask)


@image_comparison(baseline_images=['levels_and_colors'],
extensions=['png'])
def test_cmap_and_norm_from_levels_and_colors():
data = np.linspace(-2, 4, 49).reshape(7, 7)
levels = [-1, 2, 2.5, 3]
colors = ['red', 'green', 'blue', 'yellow', 'black']
extend = 'both'
cmap, norm = mcolors.from_levels_and_colors(levels, colors, extend=extend)

ax = plt.axes()
m = plt.pcolormesh(data, cmap=cmap, norm=norm)
plt.colorbar(m)

# Hide the axes labels (but not the colorbar ones, as they are useful)
for lab in ax.get_xticklabels() + ax.get_yticklabels():
lab.set_visible(False)


def test_cmap_and_norm_from_levels_and_colors2():
levels = [-1, 2, 2.5, 3]
colors = ['red', (0, 1, 0), 'blue', (0.5, 0.5, 0.5), (0.0, 0.0, 0.0, 1.0)]
clr = mcolors.colorConverter.to_rgba_array(colors)
bad = (0.1, 0.1, 0.1, 0.1)
no_color = (0.0, 0.0, 0.0, 0.0)

# Define the test values which are of interest.
# Note: levels are lev[i] <= v < lev[i+1]
tests = [('both', None, {-2: clr[0],
-1: clr[1],
2: clr[2],
2.25: clr[2],
3: clr[4],
3.5: clr[4],
np.ma.array(1, mask=True): bad}),

('min', -1, {-2: clr[0],
-1: clr[1],
2: clr[2],
2.25: clr[2],
3: no_color,
3.5: no_color,
np.ma.array(1, mask=True): bad}),

('max', -1, {-2: no_color,
-1: clr[0],
2: clr[1],
2.25: clr[1],
3: clr[3],
3.5: clr[3],
np.ma.array(1, mask=True): bad}),

('neither', -2, {-2: no_color,
-1: clr[0],
2: clr[1],
2.25: clr[1],
3: no_color,
3.5: no_color,
np.ma.array(1, mask=True): bad}),
]

for extend, i1, cases in tests:
cmap, norm = mcolors.from_levels_and_colors(levels, colors[0:i1],
extend=extend)
cmap.set_bad(bad)
for d_val, expected_color in sorted(cases.items()):
assert_array_equal(expected_color, cmap(norm([d_val]))[0],
'Wih extend={0!r} and data '
'value={1!r}'.format(extend, d_val))

assert_raises(ValueError, mcolors.from_levels_and_colors, levels, colors)


if __name__ == '__main__':
import nose
nose.runmodule(argv=['-s', '--with-doctest'], exit=False)