Repository: holoviz/holoviews

Issue:
holoviews fails with timedelta data
Hi,

I get an error plotting the simple example below.

#### ALL software version info

Python              :  3.10.12 | packaged by conda-forge | (main, Jun 23 2023, 22:41:52) [Clang 15.0.7 ]
Operating system    :  macOS-13.4.1-arm64-arm-64bit
holoviews           :  1.17.0

bokeh               :  3.2.1
colorcet            :  3.0.1
cudf                :  -
dask                :  -
datashader          :  -
geoviews            :  -
hvplot              :  -
ibis                :  -
IPython             :  8.7.0
jupyter_bokeh       :  -
jupyterlab          :  -
matplotlib          :  3.7.2
networkx            :  -
notebook            :  -
numba               :  -
numpy               :  1.25.1
pandas              :  2.0.3
panel               :  1.2.1
param               :  1.13.0
PIL                 :  9.4.0
plotly              :  -
pyarrow             :  -
scipy               :  -
skimage             :  -
spatialpandas       :  -
streamz             :  -
xarray              :  -

#### Complete, minimal, self-contained example code that reproduces the issue

```
import pandas as pd
import numpy as np

x = pd.timedelta_range(start=0, freq='S', periods=11)
y = np.arange(len(x))
df = pd.DataFrame({'x': x,'y': y})
hv.Curve(df, 'x', 'y')
```

#### Stack traceback and/or browser JavaScript console output

```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
File ~/miniforge3/envs/analytics/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 ~/miniforge3/envs/analytics/lib/python3.10/site-packages/holoviews/core/dimension.py:1287, in Dimensioned._repr_mimebundle_(self, include, exclude)
   1280 def _repr_mimebundle_(self, include=None, exclude=None):
   1281     """
   1282     Resolves the class hierarchy for the class rendering the
   1283     object using any display hooks registered on Store.display
   1284     hooks.  The output of all registered display_hooks is then
   1285     combined and returned.
   1286     """
-> 1287     return Store.render(self)

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/holoviews/core/options.py:1423, in Store.render(cls, obj)
   1421 data, metadata = {}, {}
   1422 for hook in hooks:
-> 1423     ret = hook(obj)
   1424     if ret is None:
   1425         continue

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/holoviews/ipython/display_hooks.py:280, in pprint_display(obj)
    278 if not ip.display_formatter.formatters['text/plain'].pprint:
    279     return None
--> 280 return display(obj, raw_output=True)

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/holoviews/ipython/display_hooks.py:248, in display(obj, raw_output, **kwargs)
    246 elif isinstance(obj, (CompositeOverlay, ViewableElement)):
    247     with option_state(obj):
--> 248         output = element_display(obj)
    249 elif isinstance(obj, (Layout, NdLayout, AdjointLayout)):
    250     with option_state(obj):

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/holoviews/ipython/display_hooks.py:142, in display_hook.<locals>.wrapped(element)
    140 try:
    141     max_frames = OutputSettings.options['max_frames']
--> 142     mimebundle = fn(element, max_frames=max_frames)
    143     if mimebundle is None:
    144         return {}, {}

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/holoviews/ipython/display_hooks.py:188, in element_display(element, max_frames)
    185 if type(element) not in Store.registry[backend]:
    186     return None
--> 188 return render(element)

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/holoviews/ipython/display_hooks.py:69, in render(obj, **kwargs)
     66 if renderer.fig == 'pdf':
     67     renderer = renderer.instance(fig='png')
---> 69 return renderer.components(obj, **kwargs)

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/holoviews/plotting/renderer.py:397, in Renderer.components(self, obj, fmt, comm, **kwargs)
    394 embed = (not (dynamic or streams or self.widget_mode == 'live') or config.embed)
    396 if embed or config.comms == 'default':
--> 397     return self._render_panel(plot, embed, comm)
    398 return self._render_ipywidget(plot)

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/holoviews/plotting/renderer.py:404, in Renderer._render_panel(self, plot, embed, comm)
    402 doc = Document()
    403 with config.set(embed=embed):
--> 404     model = plot.layout._render_model(doc, comm)
    405 if embed:
    406     return render_model(model, comm)

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/panel/viewable.py:736, in Viewable._render_model(self, doc, comm)
    734 if comm is None:
    735     comm = state._comm_manager.get_server_comm()
--> 736 model = self.get_root(doc, comm)
    738 if self._design and self._design.theme.bokeh_theme:
    739     doc.theme = self._design.theme.bokeh_theme

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/panel/layout/base.py:305, in Panel.get_root(self, doc, comm, preprocess)
    301 def get_root(
    302     self, doc: Optional[Document] = None, comm: Optional[Comm] = None,
    303     preprocess: bool = True
    304 ) -> Model:
--> 305     root = super().get_root(doc, comm, preprocess)
    306     # ALERT: Find a better way to handle this
    307     if hasattr(root, 'styles') and 'overflow-x' in root.styles:

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/panel/viewable.py:658, in Renderable.get_root(self, doc, comm, preprocess)
    656 wrapper = self._design._wrapper(self)
    657 if wrapper is self:
--> 658     root = self._get_model(doc, comm=comm)
    659     if preprocess:
    660         self._preprocess(root)

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/panel/layout/base.py:173, in Panel._get_model(self, doc, root, parent, comm)
    171 root = root or model
    172 self._models[root.ref['id']] = (model, parent)
--> 173 objects, _ = self._get_objects(model, [], doc, root, comm)
    174 props = self._get_properties(doc)
    175 props[self._property_mapping['objects']] = objects

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/panel/layout/base.py:155, in Panel._get_objects(self, model, old_objects, doc, root, comm)
    153 else:
    154     try:
--> 155         child = pane._get_model(doc, root, model, comm)
    156     except RerenderError as e:
    157         if e.layout is not None and e.layout is not self:

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/panel/pane/holoviews.py:411, in HoloViews._get_model(self, doc, root, parent, comm)
    409     plot = self.object
    410 else:
--> 411     plot = self._render(doc, comm, root)
    413 plot.pane = self
    414 backend = plot.renderer.backend

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/panel/pane/holoviews.py:506, in HoloViews._render(self, doc, comm, root)
    503     if comm:
    504         kwargs['comm'] = comm
--> 506 return renderer.get_plot(self.object, **kwargs)

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/holoviews/plotting/bokeh/renderer.py:70, in BokehRenderer.get_plot(self_or_cls, obj, doc, renderer, **kwargs)
     63 @bothmethod
     64 def get_plot(self_or_cls, obj, doc=None, renderer=None, **kwargs):
     65     """
     66     Given a HoloViews Viewable return a corresponding plot instance.
     67     Allows supplying a document attach the plot to, useful when
     68     combining the bokeh model with another plot.
     69     """
---> 70     plot = super().get_plot(obj, doc, renderer, **kwargs)
     71     if plot.document is None:
     72         plot.document = Document() if self_or_cls.notebook_context else curdoc()

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/holoviews/plotting/renderer.py:241, in Renderer.get_plot(self_or_cls, obj, doc, renderer, comm, **kwargs)
    238     defaults = [kd.default for kd in plot.dimensions]
    239     init_key = tuple(v if d is None else d for v, d in
    240                      zip(plot.keys[0], defaults))
--> 241     plot.update(init_key)
    242 else:
    243     plot = obj

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/holoviews/plotting/plot.py:943, in DimensionedPlot.update(self, key)
    941 def update(self, key):
    942     if len(self) == 1 and ((key == 0) or (key == self.keys[0])) and not self.drawn:
--> 943         return self.initialize_plot()
    944     item = self.__getitem__(key)
    945     self.traverse(lambda x: setattr(x, '_updated', True))

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/holoviews/plotting/bokeh/element.py:1753, in ElementPlot.initialize_plot(self, ranges, plot, plots, source)
   1751 # Initialize plot, source and glyph
   1752 if plot is None:
-> 1753     plot = self._init_plot(key, style_element, ranges=ranges, plots=plots)
   1754     self._populate_axis_handles(plot)
   1755 else:

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/holoviews/plotting/bokeh/element.py:562, in ElementPlot._init_plot(self, key, element, plots, ranges)
    559 subplots = list(self.subplots.values()) if self.subplots else []
    561 axis_specs = {'x': {}, 'y': {}}
--> 562 axis_specs['x']['x'] = self._axis_props(plots, subplots, element, ranges, pos=0) + (self.xaxis, {})
    563 if self.multi_y:
    564     if not bokeh32:

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/holoviews/plotting/bokeh/element.py:485, in ElementPlot._axis_props(self, plots, subplots, element, ranges, pos, dim, range_tags_extras, extra_range_name)
    483     axis_type = 'auto'
    484     dim_range = FactorRange()
--> 485 elif None in [v0, v1] or any(True if isinstance(el, (str, bytes)+util.cftime_types) else np.isnan(el) for el in [v0, v1]):
    486    dim_range = range_type()
    487 else:

File ~/miniforge3/envs/analytics/lib/python3.10/site-packages/holoviews/plotting/bokeh/element.py:485, in <genexpr>(.0)
    483     axis_type = 'auto'
    484     dim_range = FactorRange()
--> 485 elif None in [v0, v1] or any(True if isinstance(el, (str, bytes)+util.cftime_types) else np.isnan(el) for el in [v0, v1]):
    486    dim_range = range_type()
    487 else:

TypeError: ufunc 'isnan' not supported for the input types, and the inputs could not be safely coerced to any supported types according to the casting rule ''safe''
```


Discussion:

Resolving patch:
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -482,8 +482,11 @@ def _axis_props(self, plots, subplots, element, ranges, pos, *, dim=None,
         elif categorical:
             axis_type = 'auto'
             dim_range = FactorRange()
-        elif None in [v0, v1] or any(True if isinstance(el, (str, bytes)+util.cftime_types) else np.isnan(el) for el in [v0, v1]):
-           dim_range = range_type()
+        elif None in [v0, v1] or any(
+            True if isinstance(el, (str, bytes)+util.cftime_types)
+            else not util.isfinite(el) for el in [v0, v1]
+        ):
+            dim_range = range_type()
         else:
             dim_range = range_type(start=v0, end=v1, name=dim.name if dim else None)
 


Test patch: