Repository: holoviz/holoviews

Issue:
New .redim.range error for datashade operation
The file 3_Interactivity.ipynb from Datashader worked with the last HoloViews release it was built on, but it fails with current HoloViews master.  The code below (simplified slightly from the original) is sufficient to reproduce the issue, but it works fine if omitting `.redim.range(x=(-5,5),y=(-5,5))`.

```
from collections import OrderedDict as odict

import holoviews as hv, holoviews.operation.datashader as hd
import pandas as pd, numpy as np, datashader as ds
import datashader.transfer_functions as tf
hv.extension("bokeh") 

num=100000
np.random.seed(1)

dists = {cat: pd.DataFrame(odict([('x',np.random.normal(x,s,num)), 
                                  ('y',np.random.normal(y,s,num)), 
                                  ('val',val), 
                                  ('cat',cat)]))      
         for x,  y,  s,  val, cat in 
         [(  2,  2, 0.03, 10, "d1"), 
          (  2, -2, 0.10, 20, "d2"), 
          ( -2, -2, 0.50, 30, "d3"), 
          ( -2,  2, 1.00, 40, "d4"), 
          (  0,  0, 3.00, 50, "d5")] }

df = pd.concat(dists,ignore_index=True)
df["cat"]=df["cat"].astype("category")

points = hv.Points(df.sample(10000))
hd.datashade(points, aggregator=ds.count_cat('cat')).redim.range(x=(-5,5),y=(-5,5))
```

```
WARNING:param.dynamic_operation: Callable raised "ValueError('vdims: list length must be between 3 and 4 (inclusive)')".
Invoked as dynamic_operation(height=400, scale=1.0, width=400, x_range=(nan, nan), y_range=(nan, nan))
WARNING:param.dynamic_operation: Callable raised "ValueError('vdims: list length must be between 3 and 4 (inclusive)')".
Invoked as dynamic_operation(height=400, scale=1.0, width=400, x_range=(nan, nan), y_range=(nan, nan))
Traceback (most recent call last):
  File "/Users/jbednar/holoviews/holoviews/plotting/util.py", line 273, in get_plot_frame
    return map_obj[key]
  File "/Users/jbednar/holoviews/holoviews/core/spaces.py", line 1332, in __getitem__
    val = self._execute_callback(*tuple_key)
  File "/Users/jbednar/holoviews/holoviews/core/spaces.py", line 1106, in _execute_callback
    retval = self.callback(*args, **kwargs)
  File "/Users/jbednar/holoviews/holoviews/core/spaces.py", line 709, in __call__
    if not args and not kwargs and not any(kwarg_hash): return self.callable()
  File "/Users/jbednar/holoviews/holoviews/util/__init__.py", line 972, in dynamic_operation
    key, obj = resolve(key, kwargs)
  File "/Users/jbednar/holoviews/holoviews/util/__init__.py", line 965, in resolve
    return key, map_obj[key]
  File "/Users/jbednar/holoviews/holoviews/core/spaces.py", line 1332, in __getitem__
    val = self._execute_callback(*tuple_key)
  File "/Users/jbednar/holoviews/holoviews/core/spaces.py", line 1106, in _execute_callback
    retval = self.callback(*args, **kwargs)
  File "/Users/jbednar/holoviews/holoviews/core/spaces.py", line 709, in __call__
    if not args and not kwargs and not any(kwarg_hash): return self.callable()
  File "/Users/jbednar/holoviews/holoviews/util/__init__.py", line 972, in dynamic_operation
    key, obj = resolve(key, kwargs)
  File "/Users/jbednar/holoviews/holoviews/util/__init__.py", line 965, in resolve
    return key, map_obj[key]
  File "/Users/jbednar/holoviews/holoviews/core/spaces.py", line 1332, in __getitem__
    val = self._execute_callback(*tuple_key)
  File "/Users/jbednar/holoviews/holoviews/core/spaces.py", line 1106, in _execute_callback
    retval = self.callback(*args, **kwargs)
  File "/Users/jbednar/holoviews/holoviews/core/spaces.py", line 740, in __call__
    ret = self.callable(*args, **kwargs)
  File "/Users/jbednar/holoviews/holoviews/util/__init__.py", line 972, in dynamic_operation
    key, obj = resolve(key, kwargs)
  File "/Users/jbednar/holoviews/holoviews/util/__init__.py", line 965, in resolve
    return key, map_obj[key]
  File "/Users/jbednar/holoviews/holoviews/core/spaces.py", line 1332, in __getitem__
    val = self._execute_callback(*tuple_key)
  File "/Users/jbednar/holoviews/holoviews/core/spaces.py", line 1106, in _execute_callback
    retval = self.callback(*args, **kwargs)
  File "/Users/jbednar/holoviews/holoviews/core/spaces.py", line 740, in __call__
    ret = self.callable(*args, **kwargs)
  File "/Users/jbednar/holoviews/holoviews/util/__init__.py", line 973, in dynamic_operation
    return apply(obj, *key, **kwargs)
  File "/Users/jbednar/holoviews/holoviews/util/__init__.py", line 969, in apply
    return self._process(element, key, kwargs)
  File "/Users/jbednar/holoviews/holoviews/util/__init__.py", line 951, in _process
    return self.p.operation.process_element(element, key, **kwargs)
  File "/Users/jbednar/holoviews/holoviews/core/operation.py", line 169, in process_element
    return self._apply(element, key)
  File "/Users/jbednar/holoviews/holoviews/core/operation.py", line 129, in _apply
    ret = self._process(element, key)
  File "/Users/jbednar/holoviews/holoviews/operation/datashader.py", line 1413, in _process
    shaded = shade._process(self, agg, key)
  File "/Users/jbednar/holoviews/holoviews/operation/datashader.py", line 1255, in _process
    return RGB(self.uint32_to_uint8_xr(img), **params)
  File "/Users/jbednar/holoviews/holoviews/element/raster.py", line 738, in __init__
    super(RGB, self).__init__(data, kdims=kdims, vdims=vdims, **params)
  File "/Users/jbednar/holoviews/holoviews/element/raster.py", line 294, in __init__
    Dataset.__init__(self, data, kdims=kdims, vdims=vdims, extents=extents, **params)
  File "/Users/jbednar/holoviews/holoviews/core/data/__init__.py", line 301, in __init__
    super(Dataset, self).__init__(data, **dict(kwargs, **dict(dims, **extra_kws)))
  File "/Users/jbednar/holoviews/holoviews/element/raster.py", line 51, in __init__
    super(Raster, self).__init__(data, kdims=kdims, vdims=vdims, extents=extents, **params)
  File "/Users/jbednar/holoviews/holoviews/core/dimension.py", line 853, in __init__
    super(Dimensioned, self).__init__(data, **params)
  File "/Users/jbednar/holoviews/holoviews/core/dimension.py", line 508, in __init__
    super(LabelledData, self).__init__(**params)
  File "/Users/jbednar/miniconda3/envs/pyviz/lib/python3.7/site-packages/param/parameterized.py", line 2340, in __init__
    self.param._setup_params(**params)
  File "/Users/jbednar/miniconda3/envs/pyviz/lib/python3.7/site-packages/param/parameterized.py", line 991, in override_initialization
    fn(parameterized_instance,*args,**kw)
  File "/Users/jbednar/miniconda3/envs/pyviz/lib/python3.7/site-packages/param/parameterized.py", line 1202, in _setup_params
    setattr(self,name,val)
  File "/Users/jbednar/miniconda3/envs/pyviz/lib/python3.7/site-packages/param/parameterized.py", line 296, in _f
    return f(self, obj, val)
  File "/Users/jbednar/miniconda3/envs/pyviz/lib/python3.7/site-packages/param/parameterized.py", line 823, in __set__
    self._validate(val)
  File "/Users/jbednar/miniconda3/envs/pyviz/lib/python3.7/site-packages/param/__init__.py", line 1380, in _validate
    raise ValueError("%s: list length must be between %s and %s (inclusive)"%(self.name,min_length,max_length))
ValueError: vdims: list length must be between 3 and 4 (inclusive)
```



Discussion:

Resolving patch:
diff --git a/holoviews/operation/datashader.py b/holoviews/operation/datashader.py
--- a/holoviews/operation/datashader.py
+++ b/holoviews/operation/datashader.py
@@ -25,7 +25,7 @@
 from ..core.data import PandasInterface, XArrayInterface, DaskInterface
 from ..core.util import (
     LooseVersion, basestring, cftime_types, cftime_to_timestamp,
-    datetime_types, dt_to_int, get_param_values, max_range)
+    datetime_types, dt_to_int, isfinite, get_param_values, max_range)
 from ..element import (Image, Path, Curve, RGB, Graph, TriMesh,
                        QuadMesh, Contours, Spikes, Area, Spread,
                        Segments, Scatter, Points, Polygons)
@@ -131,26 +131,33 @@ def _get_sampling(self, element, x, y, ndim=2, default=None):
             if x is None:
                 x_range = self.p.x_range or (-0.5, 0.5)
             elif self.p.expand or not self.p.x_range:
-                x_range = self.p.x_range or max_range([element.range(xd) for xd in x])
+                if self.p.x_range and all(isfinite(v) for v in self.p.x_range):
+                    x_range = self.p.x_range
+                else:
+                    x_range = max_range([element.range(xd) for xd in x])
             else:
                 x0, x1 = self.p.x_range
                 ex0, ex1 = max_range([element.range(xd) for xd in x])
-                x_range = (np.min([np.max([x0, ex0]), ex1]),
-                           np.max([np.min([x1, ex1]), ex0]))
+                x_range = (np.nanmin([np.nanmax([x0, ex0]), ex1]),
+                           np.nanmax([np.nanmin([x1, ex1]), ex0]))
 
             if (y is None and ndim == 2):
                 y_range = self.p.y_range or default or (-0.5, 0.5)
             elif self.p.expand or not self.p.y_range:
-                y_range = self.p.y_range or (max_range([element.range(yd) for yd in y])
-                                             if default is None else default)
+                if self.p.y_range and all(isfinite(v) for v in self.p.y_range):
+                    y_range = self.p.y_range
+                elif default is None:
+                    y_range = max_range([element.range(yd) for yd in y])
+                else:
+                    y_range = default
             else:
                 y0, y1 = self.p.y_range
                 if default is None:
                     ey0, ey1 = max_range([element.range(yd) for yd in y])
                 else:
                     ey0, ey1 = default
-                y_range = (np.min([np.max([y0, ey0]), ey1]),
-                           np.max([np.min([y1, ey1]), ey0]))
+                y_range = (np.nanmin([np.nanmax([y0, ey0]), ey1]),
+                           np.nanmax([np.nanmin([y1, ey1]), ey0]))
             width, height = self.p.width, self.p.height
         (xstart, xend), (ystart, yend) = x_range, y_range
 
@@ -1241,17 +1248,21 @@ def _process(self, element, key=None):
         elif ds_version > '0.5.0' and self.p.normalization != 'eq_hist':
             shade_opts['span'] = element.range(vdim)
 
+        params = dict(get_param_values(element), kdims=kdims,
+                      bounds=bounds, vdims=RGB.vdims[:],
+                      xdensity=xdensity, ydensity=ydensity)
         with warnings.catch_warnings():
             warnings.filterwarnings('ignore', r'invalid value encountered in true_divide')
             if np.isnan(array.data).all():
-                arr = np.zeros(array.data.shape, dtype=np.uint32)
-                img = array.copy()
-                img.data = arr
+                xd, yd = kdims[:2]
+                arr = np.zeros(array.data.shape[:2]+(4,), dtype=np.uint8)
+                coords = {xd.name: element.data.coords[xd.name],
+                          yd.name: element.data.coords[yd.name],
+                          'band': [0, 1, 2, 3]}
+                img = xr.DataArray(arr, coords=coords, dims=(yd.name, xd.name, 'band'))
+                return RGB(img, **params)
             else:
                 img = tf.shade(array, **shade_opts)
-        params = dict(get_param_values(element), kdims=kdims,
-                      bounds=bounds, vdims=RGB.vdims[:],
-                      xdensity=xdensity, ydensity=ydensity)
         return RGB(self.uint32_to_uint8_xr(img), **params)
 
 


Test patch: