Repository: holoviz/panel

Issue:
Ensure constant dimensions are skipped when generating slider
As discussed in [on stackoverflow](https://stackoverflow.com/a/54771221/2005869), this `hvplot` object works fine outside of `panel`:

```
import xarray as xr
ds = xr.open_dataset('http://thredds.ucar.edu/thredds/dodsC/grib/NCEP/HRRR/CONUS_2p5km/Best')
import panel as pn
import hvplot.xarray
var = 'Temperature_height_above_ground'
mesh = ds[var].hvplot.quadmesh(x='x', y='y', rasterize=True, 
             width=600, height=400, groupby=list(ds[var].dims[:-2]))
```
![2019-02-20_9-44-07](https://user-images.githubusercontent.com/1872600/53099581-1c1f7900-34f4-11e9-8d0f-673fdc60920a.png)

But when used in a `panel` element like `Row`, errors with `ValueError: Slider 'start' and 'end' cannot be equal.` 
```
pn.Row(mesh)
```
```python-traceback

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
/opt/conda/lib/python3.6/site-packages/IPython/core/formatters.py in __call__(self, obj, include, exclude)
    968 
    969             if method is not None:
--> 970                 return method(include=include, exclude=exclude)
    971             return None
    972         else:

/opt/conda/lib/python3.6/site-packages/panel/viewable.py in _repr_mimebundle_(self, include, exclude)
    109         doc = Document()
    110         comm = self._comm_manager.get_server_comm()
--> 111         model = self._get_root(doc, comm)
    112         Viewable._views[model.ref['id']] = (self, model)
    113         return render_mimebundle(model, doc, comm)

/opt/conda/lib/python3.6/site-packages/panel/viewable.py in _get_root(self, doc, comm)
     85           Optional pyviz_comms when working in notebook
     86         """
---> 87         root = self._get_model(doc, comm=comm)
     88         self._preprocess(root)
     89         return root

/opt/conda/lib/python3.6/site-packages/panel/layout.py in _get_model(self, doc, root, parent, comm)
    131         model = self._bokeh_model()
    132         root = model if root is None else root
--> 133         objects = self._get_objects(model, [], doc, root, comm)
    134 
    135         # HACK ALERT: Insert Spacer if last item in Column has no height

/opt/conda/lib/python3.6/site-packages/panel/layout.py in _get_objects(self, model, old_objects, doc, root, comm)
    124                 child = pane._models[root.ref['id']]
    125             else:
--> 126                 child = pane._get_model(doc, root, model, comm)
    127             new_models.append(child)
    128         return new_models

/opt/conda/lib/python3.6/site-packages/panel/layout.py in _get_model(self, doc, root, parent, comm)
    131         model = self._bokeh_model()
    132         root = model if root is None else root
--> 133         objects = self._get_objects(model, [], doc, root, comm)
    134 
    135         # HACK ALERT: Insert Spacer if last item in Column has no height

/opt/conda/lib/python3.6/site-packages/panel/layout.py in _get_objects(self, model, old_objects, doc, root, comm)
    124                 child = pane._models[root.ref['id']]
    125             else:
--> 126                 child = pane._get_model(doc, root, model, comm)
    127             new_models.append(child)
    128         return new_models

/opt/conda/lib/python3.6/site-packages/panel/layout.py in _get_model(self, doc, root, parent, comm)
    131         model = self._bokeh_model()
    132         root = model if root is None else root
--> 133         objects = self._get_objects(model, [], doc, root, comm)
    134 
    135         # HACK ALERT: Insert Spacer if last item in Column has no height

/opt/conda/lib/python3.6/site-packages/panel/layout.py in _get_objects(self, model, old_objects, doc, root, comm)
    244                 child = pane._models[root.ref['id']]
    245             else:
--> 246                 child = pane._get_model(doc, root, model, comm)
    247 
    248             if isinstance(child, BkWidgetBox):

/opt/conda/lib/python3.6/site-packages/panel/widgets.py in _get_model(self, doc, root, parent, comm)
    656         div = _BkDiv(text=msg['text'])
    657         slider = _BkSlider(start=msg['start'], end=msg['end'], value=msg['value'],
--> 658                            title=None, step=1, show_value=False, tooltips=None)
    659 
    660         # Link parameters and bokeh model

/opt/conda/lib/python3.6/site-packages/bokeh/models/widgets/sliders.py in __init__(self, **kwargs)
     59         if 'start' in kwargs and 'end' in kwargs:
     60             if kwargs['start'] == kwargs['end']:
---> 61                 raise ValueError("Slider 'start' and 'end' cannot be equal.")
     62         super(Widget, self).__init__(**kwargs)
     63 

ValueError: Slider 'start' and 'end' cannot be equal.

Row
    [0] Row
        [0] HoloViews(DynamicMap)
        [1] WidgetBox
            [0] DiscreteSlider(name='GRIB forecast o..., options=OrderedDict([('2019-02-17 ...]), value=numpy.datetime64('2019-02-...)
            [1] DiscreteSlider(name='Specified height l..., options=OrderedDict([('2', 2.0)]), value=2.0)
```
note this workflow is reproducible, as it accesses an OPeNDAP service for the data. 


Discussion:
Thanks for filing this, I'll try to get this fix in today and then cut a dev release.

Resolving patch:
diff --git a/panel/holoviews.py b/panel/holoviews.py
--- a/panel/holoviews.py
+++ b/panel/holoviews.py
@@ -186,7 +186,7 @@ def widgets_from_dimensions(cls, object, widget_types={}, widgets_type='individu
                                      'dimension widget declared as %s.' % 
                                      (dim, widget))
             if vals:
-                if all(isnumeric(v) for v in vals):
+                if all(isnumeric(v) for v in vals) and len(vals) > 1:
                     vals = sorted(vals)
                     labels = [unicode(dim.pprint_value(v)) for v in vals]
                     options = OrderedDict(zip(labels, vals))
@@ -197,6 +197,8 @@ def widgets_from_dimensions(cls, object, widget_types={}, widgets_type='individu
                 default = vals[0] if dim.default is None else dim.default
                 widget = widget_type(name=dim.label, options=options, value=default)
             elif dim.range != (None, None):
+                if dim.range[0] == dim.range[1]:
+                    continue
                 default = dim.range[0] if dim.default is None else dim.default
                 step = 0.1 if dim.step is None else dim.step
                 widget_type = widget_type or FloatSlider
@@ -206,6 +208,7 @@ def widgets_from_dimensions(cls, object, widget_types={}, widgets_type='individu
                 widgets.append(widget)
         if widgets_type == 'scrubber':
             widgets = [Player(length=nframes)]
+        print(widgets)
         return widgets, dim_values
 
 


Test patch: