Open
Description
It seems UltraPlot doesn't handle non-standard calendars when dealing with DataArray time series.
xarray makes use of cftime instead of numpy.datetime64
for non-standard calendars (see xarray docs, Time series data
import numpy as np
import xarray as xr
import ultraplot as uplt
# Time axis centered at mid-month
# Standard calendar
time1=xr.date_range("2000-01", periods=120, freq="MS")
time2=xr.date_range("2000-02", periods=120, freq="MS")
time=time1+0.5*(time2-time1)
# Non-standard calendar (uses cftime)
time1=xr.date_range("2000-01", periods=120, freq="MS", calendar="noleap")
time2=xr.date_range("2000-02", periods=120, freq="MS", calendar="noleap")
time_noleap=time1+0.5*(time2-time1)
da=xr.DataArray(data=np.sin(np.arange(0.,2*np.pi,np.pi/60.)),
dims=("time",),
coords={"time":time,},
attrs={"long_name":"low freq signal", "units":"normalized"})
da_noleap=xr.DataArray(data=np.sin(2.*np.arange(0.,2*np.pi,np.pi/60.)),
dims=("time",),
coords={"time":time_noleap,},
attrs={"long_name":"high freq signal", "units":"normalized"})
fig,axs = uplt.subplots(ncols=2)
axs.format(title=("Standard calendar", "Non-standard calendar"))
axs[0].plot(da)
axs[1].plot(da_noleap)
Result:
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Cell In[1], line 28
26 axs.format(title=("Standard calendar", "Non-standard calendar"))
27 axs[0].plot(da)
---> 28 axs[1].plot(da_noleap)
File [~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/ultraplot/internals/inputs.py:365](http://localhost:8888/lab/tree/tmp/~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/ultraplot/internals/inputs.py#line=364), in _preprocess_or_redirect.<locals>._decorator.<locals>._preprocess_or_redirect(self, *args, **kwargs)
363 kwargs["color"] = color
364 # Call main function
--> 365 return func(self, *args, **kwargs)
File [~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/ultraplot/axes/plot.py:3673](http://localhost:8888/lab/tree/tmp/~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/ultraplot/axes/plot.py#line=3672), in PlotAxes.plot(self, *args, **kwargs)
3669 """
3670 %(plot.plot)s
3671 """
3672 kwargs = _parse_vert(default_vert=True, **kwargs)
-> 3673 return self._apply_plot(*args, **kwargs)
File [~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/ultraplot/axes/plot.py:3390](http://localhost:8888/lab/tree/tmp/~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/ultraplot/axes/plot.py#line=3389), in PlotAxes._apply_plot(self, vert, *pairs, **kwargs)
3388 if fmt is not None: # x1, y1, fmt1, x2, y2, fm2... style input
3389 a.append(fmt)
-> 3390 (obj,) = self._call_native("plot", *a, **kw)
3391 self._inbounds_xylim(extents, x, y)
3392 objs.append((*eb, *es, obj) if eb or es else obj)
File [~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/ultraplot/axes/plot.py:1531](http://localhost:8888/lab/tree/tmp/~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/ultraplot/axes/plot.py#line=1530), in PlotAxes._call_native(self, name, *args, **kwargs)
1529 obj = getattr(self.projection, name)(*args, ax=self, **kwargs)
1530 else:
-> 1531 obj = getattr(super(), name)(*args, **kwargs)
1532 return obj
File [~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/matplotlib/axes/_axes.py:1779](http://localhost:8888/lab/tree/tmp/~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/matplotlib/axes/_axes.py#line=1778), in Axes.plot(self, scalex, scaley, data, *args, **kwargs)
1777 lines = [*self._get_lines(self, *args, data=data, **kwargs)]
1778 for line in lines:
-> 1779 self.add_line(line)
1780 if scalex:
1781 self._request_autoscale_view("x")
File [~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/matplotlib/axes/_base.py:2407](http://localhost:8888/lab/tree/tmp/~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/matplotlib/axes/_base.py#line=2406), in _AxesBase.add_line(self, line)
2404 if line.get_clip_path() is None:
2405 line.set_clip_path(self.patch)
-> 2407 self._update_line_limits(line)
2408 if not line.get_label():
2409 line.set_label(f'_child{len(self._children)}')
File [~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/matplotlib/axes/_base.py:2430](http://localhost:8888/lab/tree/tmp/~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/matplotlib/axes/_base.py#line=2429), in _AxesBase._update_line_limits(self, line)
2426 def _update_line_limits(self, line):
2427 """
2428 Figures out the data limit of the given line, updating `.Axes.dataLim`.
2429 """
-> 2430 path = line.get_path()
2431 if path.vertices.size == 0:
2432 return
File [~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/matplotlib/lines.py:1052](http://localhost:8888/lab/tree/tmp/~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/matplotlib/lines.py#line=1051), in Line2D.get_path(self)
1050 """Return the `~matplotlib.path.Path` associated with this line."""
1051 if self._invalidy or self._invalidx:
-> 1052 self.recache()
1053 return self._path
File [~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/matplotlib/lines.py:689](http://localhost:8888/lab/tree/tmp/~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/matplotlib/lines.py#line=688), in Line2D.recache(self, always)
687 if always or self._invalidx:
688 xconv = self.convert_xunits(self._xorig)
--> 689 x = _to_unmasked_float_array(xconv).ravel()
690 else:
691 x = self._x
File [~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/matplotlib/cbook.py:1355](http://localhost:8888/lab/tree/tmp/~/Library/local/miniforge3/envs/py3cf/lib/python3.13/site-packages/matplotlib/cbook.py#line=1354), in _to_unmasked_float_array(x)
1353 return np.ma.asarray(x, float).filled(np.nan)
1354 else:
-> 1355 return np.asarray(x, float)
TypeError: float() argument must be a string or a real number, not 'cftime._cftime.DatetimeNoLeap'
Setup:
cftime 1.6.4 py313h93df234_1 conda-forge
numpy 2.3.0 py313h41a2e72_0 conda-forge
python 3.13.1 h4f43103_102_cp313 conda-forge
matplotlib 3.10.3 py313h39782a4_0 conda-forge
ultraplot 1.57.1 pyhd8ed1ab_0 conda-forge
xarray 2025.6.1 pyhd8ed1ab_1 conda-forge