diff --git a/holoviews/core/__init__.py b/holoviews/core/__init__.py index 2146af3e90..2a4dd7deb2 100644 --- a/holoviews/core/__init__.py +++ b/holoviews/core/__init__.py @@ -17,6 +17,7 @@ Dimension.type_formatters[float] = "%.5g" Dimension.type_formatters[np.float32] = "%.5g" Dimension.type_formatters[np.float64] = "%.5g" +Dimension.type_formatters[np.datetime64] = '%Y-%m-%d %H:%M:%S' def public(obj): diff --git a/holoviews/core/data/__init__.py b/holoviews/core/data/__init__.py index a159f297a1..f266adbe45 100644 --- a/holoviews/core/data/__init__.py +++ b/holoviews/core/data/__init__.py @@ -463,7 +463,7 @@ def get_dimension_type(self, dim): dim_obj = self.get_dimension(dim) if dim_obj and dim_obj.type is not None: return dim_obj.type - return self.interface.dimension_type(self, dim) + return self.interface.dimension_type(self, dim_obj) def dframe(self, dimensions=None): diff --git a/holoviews/core/data/grid.py b/holoviews/core/data/grid.py index 36c533b1b7..e4df29006d 100644 --- a/holoviews/core/data/grid.py +++ b/holoviews/core/data/grid.py @@ -84,10 +84,8 @@ def validate(cls, dataset): @classmethod def dimension_type(cls, dataset, dim): - if dim in dataset.kdims: - arr = dataset.data[dim.name] - elif dim in dataset.vdims: - arr = dataset.data[dim.name] + if dim in dataset.dimensions(): + arr = cls.values(dataset, dim, False, False) else: return None return arr.dtype.type diff --git a/holoviews/core/data/xarray.py b/holoviews/core/data/xarray.py index d27ca8e2f2..a10ce1f930 100644 --- a/holoviews/core/data/xarray.py +++ b/holoviews/core/data/xarray.py @@ -78,7 +78,10 @@ def range(cls, dataset, dimension): dim = dataset.get_dimension(dimension).name if dim in dataset.data: data = dataset.data[dim] - return data.min().item(), data.max().item() + dmin, dmax = data.min().data, data.max().data + dmin = dmin if np.isscalar(dmin) else dmin.item() + dmax = dmax if np.isscalar(dmax) else dmax.item() + return dmin, dmax else: return np.NaN, np.NaN diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index 8925e693f9..089a0475de 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -5,6 +5,7 @@ """ from __future__ import unicode_literals import re +import datetime as dt from operator import itemgetter import numpy as np @@ -13,7 +14,7 @@ from ..core.util import (basestring, sanitize_identifier, group_sanitizer, label_sanitizer, max_range, find_range, dimension_sanitizer, OrderedDict, - safe_unicode, unicode) + safe_unicode, unicode, dt64_to_dt) from .options import Store, StoreOptions from .pprint import PrettyPrinter @@ -178,7 +179,11 @@ def pprint_value(self, value): if callable(formatter): return formatter(value) elif isinstance(formatter, basestring): - if re.findall(r"\{(\w+)\}", formatter): + if isinstance(value, dt.datetime): + return value.strftime(formatter) + elif isinstance(value, np.datetime64): + return dt64_to_dt(value).strftime(formatter) + elif re.findall(r"\{(\w+)\}", formatter): return formatter.format(value) else: return formatter % value diff --git a/holoviews/core/util.py b/holoviews/core/util.py index 7e558c268f..13c0d44324 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -3,6 +3,7 @@ import itertools import string, fnmatch import unicodedata +import datetime as dt from collections import defaultdict import numpy as np @@ -917,3 +918,10 @@ def expand_grid_coords(dataset, dim): idx = dataset.get_dimension_index(dim) return cartesian_product(arrays)[idx] + +def dt64_to_dt(dt64): + """ + Safely converts NumPy datetime64 to a datetime object. + """ + ts = (dt64 - np.datetime64('1970-01-01T00:00:00Z')) / np.timedelta64(1, 's') + return dt.datetime.utcfromtimestamp(ts) diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py index ec8153b4e2..9fd07dc992 100644 --- a/holoviews/plotting/mpl/chart.py +++ b/holoviews/plotting/mpl/chart.py @@ -1,14 +1,16 @@ from __future__ import unicode_literals + from itertools import product import numpy as np from matplotlib import cm from matplotlib import pyplot as plt from matplotlib.collections import LineCollection +from matplotlib.dates import date2num, DateFormatter import param -from ...core import OrderedDict +from ...core import OrderedDict, Dimension from ...core.util import (match_spec, unique_iterator, safe_unicode, basestring, max_range, unicode) from ...element import Points, Raster, Polygons, HeatMap @@ -59,8 +61,19 @@ class CurvePlot(ChartPlot): def get_data(self, element, ranges, style): xs = element.dimension_values(0) ys = element.dimension_values(1) - return (xs, ys), style, {} + dims = element.dimensions() + if xs.dtype.kind == 'M': + dt_format = Dimension.type_formatters[np.datetime64] + dims[0] = dims[0](value_format=DateFormatter(dt_format)) + return (xs, ys), style, {'dimensions': dims} + def init_artists(self, ax, plot_args, plot_kwargs): + xs, ys = plot_args + if xs.dtype.kind == 'M': + artist = ax.plot_date(xs, ys, '-', **plot_kwargs)[0] + else: + artist = ax.plot(xs, ys, **plot_kwargs)[0] + return {'artist': artist} def update_handles(self, key, axis, element, ranges, style): artist = self.handles['artist'] diff --git a/holoviews/plotting/mpl/element.py b/holoviews/plotting/mpl/element.py index a481e17dd3..d899082463 100644 --- a/holoviews/plotting/mpl/element.py +++ b/holoviews/plotting/mpl/element.py @@ -1,10 +1,11 @@ import math +import param +import numpy as np +import matplotlib.pyplot as plt from matplotlib import ticker from matplotlib import colors -import matplotlib.pyplot as plt -import numpy as np -import param +from matplotlib.dates import date2num from ...core import util from ...core import (OrderedDict, NdOverlay, DynamicMap, @@ -301,7 +302,9 @@ def _set_axis_limits(self, axis, view, subplots, ranges): scalex, scaley = True, True extents = self.get_extents(view, ranges) if extents and not self.overlaid: - coords = [coord if np.isreal(coord) else np.NaN for coord in extents] + coords = [coord if np.isreal(coord) or isinstance(coord, np.datetime64) else np.NaN for coord in extents] + coords = [date2num(util.dt64_to_dt(c)) if isinstance(c, np.datetime64) else c + for c in coords] valid_lim = lambda c: util.isnumeric(c) and not np.isnan(c) if self.projection == '3d' or len(extents) == 6: l, b, zmin, r, t, zmax = coords diff --git a/holoviews/plotting/mpl/plot.py b/holoviews/plotting/mpl/plot.py index faae2e9580..2d24f82ea5 100644 --- a/holoviews/plotting/mpl/plot.py +++ b/holoviews/plotting/mpl/plot.py @@ -262,9 +262,6 @@ class GridPlot(CompositePlot): show_legend = param.Boolean(default=False, doc=""" Legends add to much clutter in a grid and are disabled by default.""") - tick_format = param.String(default="%.2f", doc=""" - Formatting string for the GridPlot ticklabels.""") - xaxis = param.ObjectSelector(default='bottom', objects=['bottom', 'top', None], doc=""" Whether and where to display the xaxis, supported options are @@ -496,13 +493,15 @@ def _layout_axis(self, layout, axis): yticks = [(plot_height/2)+(r*(plot_height+border_height)) for r in range(self.rows)] layout_axis.set_xticks(xticks) - layout_axis.set_xticklabels(self._process_ticklabels(sorted(set(dim1_keys)), dims[0])) + layout_axis.set_xticklabels([dims[0].pprint_value(l) + for l in sorted(set(dim1_keys))]) for tick in layout_axis.get_xticklabels(): tick.set_rotation(self.xrotation) ydim = dims[1] if layout.ndims > 1 else None layout_axis.set_yticks(yticks) - layout_axis.set_yticklabels(self._process_ticklabels(sorted(set(dim2_keys)), ydim)) + layout_axis.set_yticklabels([ydim.pprint_value(l) if ydim else '' + for l in sorted(set(dim2_keys))]) for tick in layout_axis.get_yticklabels(): tick.set_rotation(self.yrotation) @@ -529,19 +528,6 @@ def _layout_axis(self, layout, axis): return layout_axis - def _process_ticklabels(self, labels, dim): - formatted_labels = [] - for k in labels: - if dim and dim.value_format: - k = dim.value_format(k) - elif not isinstance(k, (str, type(None))): - k = self.tick_format % k - elif k is None: - k = '' - formatted_labels.append(k) - return formatted_labels - - def _adjust_subplots(self, axis, subaxes): bbox = axis.get_position() l, b, w, h = bbox.x0, bbox.y0, bbox.width, bbox.height diff --git a/holoviews/plotting/mpl/raster.py b/holoviews/plotting/mpl/raster.py index a734fecf39..9f70fcd2bf 100644 --- a/holoviews/plotting/mpl/raster.py +++ b/holoviews/plotting/mpl/raster.py @@ -379,8 +379,9 @@ def update_frame(self, key, ranges=None): def _get_axis_kwargs(self): xdim = self.layout.kdims[0] ydim = self.layout.kdims[1] if self.layout.ndims > 1 else None - xticks = (self._xticks, self._process_ticklabels(self._xkeys, xdim)) - yticks = (self._yticks, self._process_ticklabels(self._ykeys, ydim)) + xticks = (self._xticks, [xdim.pprint_value(l) for l in self._xkeys]) + yticks = (self._yticks, [ydim.pprint_value(l) if ydim else '' + for l in self._ykeys]) return dict(xlabel=xdim.pprint_label, ylabel=ydim.pprint_label if ydim else '', xticks=xticks, yticks=yticks)