Skip to content

Commit

Permalink
Integer levels and vmin/vmax (#1191)
Browse files Browse the repository at this point in the history
  • Loading branch information
fmaussion authored Jan 3, 2017
1 parent feed922 commit 71d6a0e
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 15 deletions.
4 changes: 4 additions & 0 deletions doc/whats-new.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ Breaking changes
- Coordinates used to index a dimension are now loaded eagerly into
:py:class:`pandas.Index` objects, instead of loading the values lazily.
By `Guido Imperiale <https://github.com/crusaderky>`_.
- Automatic levels for 2d plots are now guaranteed to land on ``vmin`` and
``vmax`` when these kwargs are explicitly provided (:issue:`1191`). The
automated level selection logic also slightly changed.
By `Fabien Maussion <https://github.com/fmaussion>`_.
- xarray no longer supports python 3.3 or versions of dask prior to v0.9.0.

Deprecations
Expand Down
6 changes: 5 additions & 1 deletion xarray/plot/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,11 @@ def _plot2d(plotfunc):
How to draw arrows extending the colorbar beyond its limits. If not
provided, extend is inferred from vmin, vmax and the data limits.
levels : int or list-like object, optional
Split the colormap (cmap) into discrete color intervals.
Split the colormap (cmap) into discrete color intervals. If an integer
is provided, "nice" levels are chosen based on the data range: this can
imply that the final number of levels is not exactly the expected one.
Setting ``vmin`` and/or ``vmax`` with ``levels=N`` is equivalent to
setting ``levels=np.linspace(vmin, vmax, N)``.
infer_intervals : bool, optional
Only applies to pcolormesh. If True, the coordinate intervals are
passed to pcolormesh. If False, the original coordinates are used
Expand Down
14 changes: 11 additions & 3 deletions xarray/plot/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import pandas as pd

from ..core.pycompat import basestring
from ..core.utils import is_scalar


def _load_default_cmap(fname='default_colormap.csv'):
Expand Down Expand Up @@ -139,6 +140,9 @@ def _determine_cmap_params(plot_data, vmin=None, vmax=None, cmap=None,
if (vmin is not None) and (vmax is not None):
possibly_divergent = False

# Setting vmin or vmax implies linspaced levels
user_minmax = (vmin is not None) or (vmax is not None)

# vlim might be computed below
vlim = None

Expand Down Expand Up @@ -187,9 +191,13 @@ def _determine_cmap_params(plot_data, vmin=None, vmax=None, cmap=None,

# Handle discrete levels
if levels is not None:
if isinstance(levels, int):
ticker = mpl.ticker.MaxNLocator(levels)
levels = ticker.tick_values(vmin, vmax)
if is_scalar(levels):
if user_minmax or levels == 1:
levels = np.linspace(vmin, vmax, levels)
else:
# N in MaxNLocator refers to bins, not ticks
ticker = mpl.ticker.MaxNLocator(levels-1)
levels = ticker.tick_values(vmin, vmax)
vmin, vmax = levels[0], levels[-1]

if extend is None:
Expand Down
44 changes: 33 additions & 11 deletions xarray/test/test_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
from __future__ import division
from __future__ import print_function

# import mpl and change the backend before other mpl imports
try:
import matplotlib as mpl
# Using a different backend makes Travis CI work
mpl.use('Agg')
# Order of imports is important here.
import matplotlib.pyplot as plt
except ImportError:
pass

import inspect

import numpy as np
Expand All @@ -17,15 +27,6 @@

from . import TestCase, requires_matplotlib

try:
import matplotlib as mpl
# Using a different backend makes Travis CI work.
mpl.use('Agg')
# Order of imports is important here.
import matplotlib.pyplot as plt
except ImportError:
pass


def text_in_fig():
'''
Expand Down Expand Up @@ -322,20 +323,41 @@ def test_center(self):

def test_integer_levels(self):
data = self.data + 1

# default is to cover full data range but with no guarantee on Nlevels
for level in np.arange(2, 10, dtype=int):
cmap_params = _determine_cmap_params(data, levels=level)
self.assertEqual(cmap_params['vmin'], cmap_params['levels'][0])
self.assertEqual(cmap_params['vmax'], cmap_params['levels'][-1])
self.assertEqual(cmap_params['extend'], 'neither')

# with min max we are more strict
cmap_params = _determine_cmap_params(data, levels=5, vmin=0, vmax=5,
cmap='Blues')
self.assertEqual(cmap_params['vmin'], 0)
self.assertEqual(cmap_params['vmax'], 5)
self.assertEqual(cmap_params['vmin'], cmap_params['levels'][0])
self.assertEqual(cmap_params['vmax'], cmap_params['levels'][-1])
self.assertEqual(cmap_params['cmap'].name, 'Blues')
self.assertEqual(cmap_params['extend'], 'neither')
self.assertEqual(cmap_params['cmap'].N, 5)
self.assertEqual(cmap_params['norm'].N, 6)
self.assertEqual(cmap_params['cmap'].N, 4)
self.assertEqual(cmap_params['norm'].N, 5)

cmap_params = _determine_cmap_params(data, levels=5,
vmin=0.5, vmax=1.5)
self.assertEqual(cmap_params['cmap'].name, 'viridis')
self.assertEqual(cmap_params['extend'], 'max')

cmap_params = _determine_cmap_params(data, levels=5,
vmin=1.5)
self.assertEqual(cmap_params['cmap'].name, 'viridis')
self.assertEqual(cmap_params['extend'], 'min')

cmap_params = _determine_cmap_params(data, levels=5,
vmin=1.3, vmax=1.5)
self.assertEqual(cmap_params['cmap'].name, 'viridis')
self.assertEqual(cmap_params['extend'], 'both')

def test_list_levels(self):
data = self.data + 1

Expand Down

0 comments on commit 71d6a0e

Please sign in to comment.