Repository: holoviz/hvplot

Issue:
Explorer raises error when all values are the same
The sliders used for xlim/ylim fails if all the values of x/y values are the same.  I would rather see the sliders disabled for this case and everything work as "normal".  

``` python
import numpy as np
import pandas as pd

data = dict(
    x=np.ones(10),
    y=np.ones(10) + 10,
)
df = pd.DataFrame(data)
hvplot.explorer(df)
```

``` python-traceback
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/miniconda3/envs/holoviz/lib/python3.10/site-packages/IPython/core/formatters.py:972, in MimeBundleFormatter.__call__(self, obj, include, exclude)
    969     method = get_real_method(obj, self.print_method)
    971     if method is not None:
--> 972         return method(include=include, exclude=exclude)
    973     return None
    974 else:

File ~/Development/holoviz/repos/panel/panel/viewable.py:931, in Viewer._repr_mimebundle_(self, include, exclude)
    930 def _repr_mimebundle_(self, include=None, exclude=None):
--> 931     return self._create_view()._repr_mimebundle_(include, exclude)

File ~/Development/holoviz/repos/panel/panel/viewable.py:674, in Viewable._repr_mimebundle_(self, include, exclude)
    672 doc = Document()
    673 comm = state._comm_manager.get_server_comm()
--> 674 model = self._render_model(doc, comm)
    675 ref = model.ref['id']
    676 manager = CommManager(comm_id=comm.id, plot_id=ref)

File ~/Development/holoviz/repos/panel/panel/viewable.py:508, in Renderable._render_model(self, doc, comm)
    506 if comm is None:
    507     comm = state._comm_manager.get_server_comm()
--> 508 model = self.get_root(doc, comm)
    510 if config.embed:
    511     embed_state(self, model, doc,
    512                 json=config.embed_json,
    513                 json_prefix=config.embed_json_prefix,
    514                 save_path=config.embed_save_path,
    515                 load_path=config.embed_load_path,
    516                 progress=False)

File ~/Development/holoviz/repos/panel/panel/viewable.py:559, in Renderable.get_root(self, doc, comm, preprocess)
    542 """
    543 Returns the root model and applies pre-processing hooks
    544 
   (...)
    556 Returns the bokeh model corresponding to this panel object
    557 """
    558 doc = init_doc(doc)
--> 559 root = self._get_model(doc, comm=comm)
    560 if preprocess:
    561     self._preprocess(root)

File ~/Development/holoviz/repos/panel/panel/layout/base.py:146, in Panel._get_model(self, doc, root, parent, comm)
    144 if root is None:
    145     root = model
--> 146 objects = self._get_objects(model, [], doc, root, comm)
    147 props = dict(self._init_params(), objects=objects)
    148 model.update(**self._process_param_change(props))

File ~/Development/holoviz/repos/panel/panel/layout/base.py:131, in Panel._get_objects(self, model, old_objects, doc, root, comm)
    129 else:
    130     try:
--> 131         child = pane._get_model(doc, root, model, comm)
    132     except RerenderError:
    133         return self._get_objects(model, current_objects[:i], doc, root, comm)

File ~/Development/holoviz/repos/panel/panel/layout/base.py:146, in Panel._get_model(self, doc, root, parent, comm)
    144 if root is None:
    145     root = model
--> 146 objects = self._get_objects(model, [], doc, root, comm)
    147 props = dict(self._init_params(), objects=objects)
    148 model.update(**self._process_param_change(props))

File ~/Development/holoviz/repos/panel/panel/layout/base.py:131, in Panel._get_objects(self, model, old_objects, doc, root, comm)
    129 else:
    130     try:
--> 131         child = pane._get_model(doc, root, model, comm)
    132     except RerenderError:
    133         return self._get_objects(model, current_objects[:i], doc, root, comm)

File ~/Development/holoviz/repos/panel/panel/layout/base.py:146, in Panel._get_model(self, doc, root, parent, comm)
    144 if root is None:
    145     root = model
--> 146 objects = self._get_objects(model, [], doc, root, comm)
    147 props = dict(self._init_params(), objects=objects)
    148 model.update(**self._process_param_change(props))

File ~/Development/holoviz/repos/panel/panel/layout/tabs.py:204, in Tabs._get_objects(self, model, old_objects, doc, root, comm)
    202 else:
    203     try:
--> 204         rendered[pref] = child = pane._get_model(doc, root, model, comm)
    205     except RerenderError:
    206         return self._get_objects(model, current_objects[:i], doc, root, comm)

File ~/Development/holoviz/repos/panel/panel/param.py:683, in Param._get_model(self, doc, root, parent, comm)
    679 def _get_model(
    680     self, doc: Document, root: Optional[Model] = None,
    681     parent: Optional[Model] = None, comm: Optional[Comm] = None
    682 ) -> Model:
--> 683     model = self.layout._get_model(doc, root, parent, comm)
    684     self._models[root.ref['id']] = (model, parent)
    685     return model

File ~/Development/holoviz/repos/panel/panel/layout/base.py:146, in Panel._get_model(self, doc, root, parent, comm)
    144 if root is None:
    145     root = model
--> 146 objects = self._get_objects(model, [], doc, root, comm)
    147 props = dict(self._init_params(), objects=objects)
    148 model.update(**self._process_param_change(props))

File ~/Development/holoviz/repos/panel/panel/layout/base.py:131, in Panel._get_objects(self, model, old_objects, doc, root, comm)
    129 else:
    130     try:
--> 131         child = pane._get_model(doc, root, model, comm)
    132     except RerenderError:
    133         return self._get_objects(model, current_objects[:i], doc, root, comm)

File ~/Development/holoviz/repos/panel/panel/widgets/base.py:98, in Widget._get_model(self, doc, root, parent, comm)
     94 def _get_model(
     95     self, doc: Document, root: Optional[Model] = None,
     96     parent: Optional[Model] = None, comm: Optional[Comm] = None
     97 ) -> Model:
---> 98     model = self._widget_type(**self._process_param_change(self._init_params()))
     99     if root is None:
    100         root = model

File ~/miniconda3/envs/holoviz/lib/python3.10/site-packages/bokeh/models/widgets/sliders.py:75, in AbstractSlider.__init__(self, **kwargs)
     73 if 'start' in kwargs and 'end' in kwargs:
     74     if kwargs['start'] == kwargs['end']:
---> 75         raise ValueError("Slider 'start' and 'end' cannot be equal.")
     77 if "value" in kwargs and "value_throttled" not in kwargs:
     78     kwargs["value_throttled"] = kwargs["value"]

ValueError: Slider 'start' and 'end' cannot be equal.
```


Discussion:

Resolving patch:
diff --git a/hvplot/ui.py b/hvplot/ui.py
--- a/hvplot/ui.py
+++ b/hvplot/ui.py
@@ -178,13 +178,13 @@ def __init__(self, data, **params):
     @param.depends('explorer.xlim', 'explorer.ylim',  watch=True)
     def _update_ranges(self):
         xlim = self.explorer.xlim()
-        if xlim is not None and is_number(xlim[0]) and is_number(xlim[1]):
+        if xlim is not None and is_number(xlim[0]) and is_number(xlim[1]) and xlim[0] != xlim[1]:
             self.param.xlim.precedence = 0
             self.param.xlim.bounds = xlim
         else:
             self.param.xlim.precedence = -1
         ylim = self.explorer.ylim()
-        if ylim is not None and is_number(ylim[0]) and is_number(ylim[1]):
+        if ylim is not None and is_number(ylim[0]) and is_number(ylim[1]) and ylim[0] != ylim[1]:
             self.param.ylim.precedence = 0
             self.param.ylim.bounds = ylim
         else:


Test patch: