Skip to content

Commit 04f4e88

Browse files
committed
Merge pull request #538 from rabernat/fix_contour_color
Fix contour color
2 parents d2e9bcc + ee4fab7 commit 04f4e88

File tree

4 files changed

+121
-20
lines changed

4 files changed

+121
-20
lines changed

doc/plotting.rst

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -286,13 +286,25 @@ discrete colormap:
286286
@savefig plotting_listed_levels.png width=4in
287287
air2d.plot(levels=[0, 12, 18, 30])
288288
289-
Finally, if you have `Seaborn <http://stanford.edu/~mwaskom/software/seaborn/>`_ installed, you can also specify a `seaborn` color palete or a list of colors as the ``cmap`` argument:
289+
You can also specify a list of discrete colors through the ``colors`` argument:
290290

291291
.. ipython:: python
292292
293293
flatui = ["#9b59b6", "#3498db", "#95a5a6", "#e74c3c", "#34495e", "#2ecc71"]
294294
@savefig plotting_custom_colors_levels.png width=4in
295-
air2d.plot(levels=[0, 12, 18, 30], cmap=flatui)
295+
air2d.plot(levels=[0, 12, 18, 30], colors=flatui)
296+
297+
Finally, if you have `Seaborn <http://stanford.edu/~mwaskom/software/seaborn/>`_
298+
installed, you can also specify a `seaborn` color palette to the ``cmap``
299+
argument. Note that ``levels`` *must* be specified with seaborn color palettes
300+
if using ``imshow`` or ``pcolormesh`` (but not with ``contour`` or ``contourf``,
301+
since levels are chosen automatically).
302+
303+
.. ipython:: python
304+
305+
@savefig plotting_seaborn_palette.png width=4in
306+
air2d.plot(levels=10, cmap='husl')
307+
296308
297309
Maps
298310
----

doc/whats-new.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ What's New
99
import xray
1010
np.random.seed(123456)
1111
12+
v0.6.1
13+
------
14+
15+
API Changes
16+
~~~~~~~~~~~
17+
18+
- The handling of colormaps and discrete color lists for 2D plots in
19+
:py:meth:`~xray.DataArray.plot` was changed to provide more compatibility
20+
with matplotlib's `contour` and `contourf` functions (:issue:`538`).
21+
Now discrete lists of colors should be specified using `colors` keyword,
22+
rather than `cmap`.
23+
1224
v0.6.0 (21 August 2015)
1325
-----------------------
1426

xray/plot/plot.py

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import pkg_resources
1010
import functools
11+
import warnings
1112

1213
import numpy as np
1314
import pandas as pd
@@ -287,21 +288,40 @@ def _determine_extend(calc_data, vmin, vmax):
287288

288289
def _color_palette(cmap, n_colors):
289290
import matplotlib.pyplot as plt
290-
try:
291-
from seaborn.apionly import color_palette
292-
pal = color_palette(cmap, n_colors=n_colors)
293-
except (TypeError, ImportError, ValueError):
294-
# TypeError is raised when LinearSegmentedColormap (viridis) is used
295-
# ImportError is raised when seaborn is not installed
296-
# ValueError is raised when seaborn doesn't like a colormap (e.g. jet)
297-
# Use homegrown solution if you don't have seaborn or are using viridis
298-
if isinstance(cmap, basestring):
299-
cmap = plt.get_cmap(cmap)
300-
301-
colors_i = np.linspace(0, 1., n_colors)
291+
from matplotlib.colors import ListedColormap
292+
colors_i = np.linspace(0, 1., n_colors)
293+
if isinstance(cmap, (list, tuple)):
294+
# we have a list of colors
295+
try:
296+
# first try to turn it into a palette with seaborn
297+
from seaborn.apionly import color_palette
298+
pal = color_palette(cmap, n_colors=n_colors)
299+
except ImportError:
300+
# if that fails, use matplotlib
301+
# in this case, is there any difference between mpl and seaborn?
302+
cmap = ListedColormap(cmap, N=n_colors)
303+
pal = cmap(colors_i)
304+
elif isinstance(cmap, basestring):
305+
# we have some sort of named palette
306+
try:
307+
# first try to turn it into a palette with seaborn
308+
from seaborn.apionly import color_palette
309+
pal = color_palette(cmap, n_colors=n_colors)
310+
except (ImportError, ValueError):
311+
# ValueError is raised when seaborn doesn't like a colormap (e.g. jet)
312+
# if that fails, use matplotlib
313+
try:
314+
# is this a matplotlib cmap?
315+
cmap = plt.get_cmap(cmap)
316+
except ValueError:
317+
# or maybe we just got a single color as a string
318+
cmap = ListedColormap([cmap], N=n_colors)
319+
pal = cmap(colors_i)
320+
else:
321+
# cmap better be a LinearSegmentedColormap (e.g. viridis)
302322
pal = cmap(colors_i)
303-
return pal
304323

324+
return pal
305325

306326
def _build_discrete_cmap(cmap, levels, extend, filled):
307327
"""
@@ -386,8 +406,12 @@ def _plot2d(plotfunc):
386406
The mapping from data values to color space. If not provided, this
387407
will be either be ``viridis`` (if the function infers a sequential
388408
dataset) or ``RdBu_r`` (if the function infers a diverging dataset).
389-
When ``levels`` is provided and when `Seaborn` is installed, ``cmap``
390-
may also be a `seaborn` color palette or a list of colors.
409+
When when `Seaborn` is installed, ``cmap`` may also be a `seaborn`
410+
color palette. If ``cmap`` is seaborn color palette and the plot type
411+
is not ``contour`` or ``contourf``, ``levels`` must also be specified.
412+
colors : discrete colors to plot, optional
413+
A single color or a list of colors. If the plot type is not ``contour``
414+
or ``contourf``, the ``levels`` argument is required.
391415
center : float, optional
392416
The value at which to center the colormap. Passing this value implies
393417
use of a diverging colormap.
@@ -415,13 +439,27 @@ def _plot2d(plotfunc):
415439
@functools.wraps(plotfunc)
416440
def newplotfunc(darray, ax=None, xincrease=None, yincrease=None,
417441
add_colorbar=True, add_labels=True, vmin=None, vmax=None, cmap=None,
418-
center=None, robust=False, extend=None, levels=None,
442+
center=None, robust=False, extend=None, levels=None, colors=None,
419443
**kwargs):
420444
# All 2d plots in xray share this function signature.
421445
# Method signature below should be consistent.
422446

423447
import matplotlib.pyplot as plt
424448

449+
# colors is mutually exclusive with cmap
450+
if cmap and colors:
451+
raise ValueError("Can't specify both cmap and colors.")
452+
# colors is only valid when levels is supplied or the plot is of type
453+
# contour or contourf
454+
if colors and (('contour' not in plotfunc.__name__) and (not levels)):
455+
raise ValueError("Can only specify colors with contour or levels")
456+
# we should not be getting a list of colors in cmap anymore
457+
# is there a better way to do this test?
458+
if isinstance(cmap, (list, tuple)):
459+
warnings.warn("Specifying a list of colors in cmap is deprecated. "
460+
"Use colors keyword instead.",
461+
DeprecationWarning, stacklevel=3)
462+
425463
if ax is None:
426464
ax = plt.gca()
427465

@@ -444,9 +482,9 @@ def newplotfunc(darray, ax=None, xincrease=None, yincrease=None,
444482
levels = 7 # this is the matplotlib default
445483
filled = plotfunc.__name__ != 'contour'
446484

485+
cmap = colors if colors else cmap
447486
cmap_params = _determine_cmap_params(z.data, vmin, vmax, cmap, center,
448487
robust, extend, levels, filled)
449-
450488
if 'contour' in plotfunc.__name__:
451489
# extend is a keyword argument only for contour and contourf, but
452490
# passing it to the colorbar is sufficient for imshow and
@@ -482,7 +520,7 @@ def newplotfunc(darray, ax=None, xincrease=None, yincrease=None,
482520
@functools.wraps(newplotfunc)
483521
def plotmethod(_PlotMethods_obj, ax=None, xincrease=None, yincrease=None,
484522
add_colorbar=True, add_labels=True, vmin=None, vmax=None, cmap=None,
485-
center=None, robust=False, extend=None, levels=None,
523+
colors=None, center=None, robust=False, extend=None, levels=None,
486524
**kwargs):
487525
'''
488526
The method should have the same signature as the function.

xray/test/test_plot.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,15 @@ def test_default_cmap(self):
376376
cmap_name = self.plotfunc(abs(self.darray)).get_cmap().name
377377
self.assertEqual('viridis', cmap_name)
378378

379+
def test_seaborn_palette_as_cmap(self):
380+
try:
381+
import seaborn
382+
cmap_name = self.plotmethod(
383+
levels=2, cmap='husl').get_cmap().name
384+
self.assertEqual('husl', cmap_name)
385+
except ImportError:
386+
pass
387+
379388
def test_can_change_default_cmap(self):
380389
cmap_name = self.plotmethod(cmap='Blues').get_cmap().name
381390
self.assertEqual('Blues', cmap_name)
@@ -446,6 +455,28 @@ class TestContour(Common2dMixin, PlotTestCase):
446455

447456
plotfunc = staticmethod(xplt.contour)
448457

458+
def test_colors(self):
459+
# matplotlib cmap.colors gives an rgbA ndarray
460+
# when seaborn is used, instead we get an rgb tuble
461+
def _color_as_tuple(c):
462+
return tuple(c[:3])
463+
artist = self.plotmethod(colors='k')
464+
self.assertEqual(
465+
_color_as_tuple(artist.cmap.colors[0]),
466+
(0.0,0.0,0.0))
467+
468+
artist = self.plotmethod(colors=['k','b'])
469+
self.assertEqual(
470+
_color_as_tuple(artist.cmap.colors[1]),
471+
(0.0,0.0,1.0))
472+
473+
def test_cmap_and_color_both(self):
474+
with self.assertRaises(ValueError):
475+
self.plotmethod(colors='k', cmap='RdBu')
476+
477+
def list_of_colors_in_cmap_deprecated(self):
478+
with self.assertRaises(DeprecationError):
479+
self.plotmethod(cmap=['k','b'])
449480

450481
class TestPcolormesh(Common2dMixin, PlotTestCase):
451482

@@ -485,3 +516,11 @@ def test_can_change_aspect(self):
485516
def test_primitive_artist_returned(self):
486517
artist = self.plotmethod()
487518
self.assertTrue(isinstance(artist, mpl.image.AxesImage))
519+
520+
def test_seaborn_palette_needs_levels(self):
521+
try:
522+
import seaborn
523+
with self.assertRaises(ValueError):
524+
self.plotmethod(cmap='husl')
525+
except ImportError:
526+
pass

0 commit comments

Comments
 (0)