diff --git a/holoviews/core/__init__.py b/holoviews/core/__init__.py index e7aacdbc18..399e96c099 100644 --- a/holoviews/core/__init__.py +++ b/holoviews/core/__init__.py @@ -1,3 +1,5 @@ +from datetime import date, datetime + from .boundingregion import * # noqa (API import) from .data import * # noqa (API import) from .dimension import * # noqa (API import) @@ -24,6 +26,8 @@ Dimension.type_formatters[np.float32] = "%.5g" Dimension.type_formatters[np.float64] = "%.5g" Dimension.type_formatters[np.datetime64] = '%Y-%m-%d %H:%M:%S' +Dimension.type_formatters[datetime] = '%Y-%m-%d %H:%M:%S' +Dimension.type_formatters[date] = '%Y-%m-%d' try: import pandas as pd diff --git a/holoviews/core/data/grid.py b/holoviews/core/data/grid.py index 9a1ed8613e..de5a1a1eff 100644 --- a/holoviews/core/data/grid.py +++ b/holoviews/core/data/grid.py @@ -587,7 +587,7 @@ def range(cls, dataset, dimension): column = cls.coords(dataset, dimension, expanded=expanded, edges=True) else: column = dataset.dimension_values(dimension) - if dataset.get_dimension_type(dimension) is np.datetime64: + if column.dtype.kind == 'M': return column.min(), column.max() elif len(column) == 0: return np.NaN, np.NaN diff --git a/holoviews/core/data/interface.py b/holoviews/core/data/interface.py index 923cfbce42..1eee94421e 100644 --- a/holoviews/core/data/interface.py +++ b/holoviews/core/data/interface.py @@ -281,7 +281,7 @@ def indexed(cls, dataset, selection): @classmethod def range(cls, dataset, dimension): column = dataset.dimension_values(dimension) - if dataset.get_dimension_type(dimension) is np.datetime64: + if column.dtype.kind == 'M': return column.min(), column.max() elif len(column) == 0: return np.NaN, np.NaN diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index 8c8453c5ed..18290fe054 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -449,7 +449,7 @@ def pprint_value(self, value): if callable(formatter): return formatter(value) elif isinstance(formatter, basestring): - if isinstance(value, dt.datetime): + if isinstance(value, (dt.datetime, dt.date)): return value.strftime(formatter) elif isinstance(value, np.datetime64): return dt64_to_dt(value).strftime(formatter) diff --git a/holoviews/core/util.py b/holoviews/core/util.py index 3a0b1b991f..b56018df76 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -28,7 +28,7 @@ except: import builtins as builtins # noqa (compatibility) -datetime_types = (np.datetime64, dt.datetime) +datetime_types = (np.datetime64, dt.datetime, dt.date) timedelta_types = (np.timedelta64, dt.timedelta,) try: diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 5d6d6bf57b..9478848901 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -292,22 +292,26 @@ def _axes_props(self, plots, subplots, element, ranges): if plots and self.shared_axes and not norm_opts.get('axiswise', False): plot_ranges = self._merge_ranges(plots, xlabel, ylabel) - if el.get_dimension_type(0) in util.datetime_types: - x_axis_type = 'datetime' - else: - x_axis_type = 'log' if self.logx else 'auto' - - if len(dims) > 1 and el.get_dimension_type(1) in util.datetime_types: - y_axis_type = 'datetime' - else: - y_axis_type = 'log' if self.logy else 'auto' - # Get the Element that determines the range and get_extents range_el = el if self.batched and not isinstance(self, OverlayPlot) else element l, b, r, t = self.get_extents(range_el, ranges) if self.invert_axes: l, b, r, t = b, l, t, r + xtype = el.get_dimension_type(0) + if ((xtype is np.object_ and type(l) in util.datetime_types) or + xtype in util.datetime_types): + x_axis_type = 'datetime' + else: + x_axis_type = 'log' if self.logx else 'auto' + + y_axis_type = 'log' if self.logy else 'auto' + if len(dims) > 1: + ytype = el.get_dimension_type(1) + if ((ytype is np.object_ and type(b) in util.datetime_types) + or ytype in util.datetime_types): + y_axis_type = 'datetime' + # Declare shared axes if 'x_range' in plot_ranges: self._shared['x'] = True diff --git a/holoviews/plotting/bokeh/tabular.py b/holoviews/plotting/bokeh/tabular.py index 04f531401e..4cbbdfef9d 100644 --- a/holoviews/plotting/bokeh/tabular.py +++ b/holoviews/plotting/bokeh/tabular.py @@ -1,10 +1,14 @@ -from bokeh.models.widgets import DataTable, TableColumn - import param -from ...core import Dataset +from bokeh.models.widgets import ( + DataTable, TableColumn, NumberEditor, NumberFormatter, DateFormatter, + TimeEditor, StringFormatter, StringEditor, IntEditor +) + +from ...core import Dataset, Dimension from ...element import ItemTable from ...streams import Buffer +from ...core.util import dimension_sanitizer, datetime_types from ..plot import GenericElementPlot from .plot import BokehPlot @@ -46,12 +50,8 @@ def _execute_hooks(self, element): def get_data(self, element, ranges, style): - dims = element.dimensions() - mapping = {d.name: d.name for d in dims} - data = {d: element.dimension_values(d) for d in dims} - data = {d.name: values if values.dtype.kind in "if" else list(map(d.pprint_value, values)) - for d, values in data.items()} - return data, mapping, style + return ({dimension_sanitizer(d.name): element.dimension_values(d) + for d in element.dimensions()}, {}, style) def initialize_plot(self, ranges=None, plot=None, plots=None, source=None): @@ -70,8 +70,28 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None): source = self._init_datasource(data) self.handles['source'] = source + columns = [] dims = element.dimensions() - columns = [TableColumn(field=d.name, title=d.pprint_label) for d in dims] + for d in dims: + col = dimension_sanitizer(d.name) + kind = data[col].dtype.kind + if kind == 'i': + formatter = NumberFormatter() + editor = IntEditor() + elif kind == 'f': + formatter = NumberFormatter(format='0,0.0[00000]') + editor = NumberEditor() + elif kind == 'M' or (kind == 'O' and type(data[col][0]) in datetime_types): + dimtype = element.get_dimension_type(0) + dformat = Dimension.type_formatters.get(dimtype, '%Y-%m-%d %H:%M:%S') + formatter = DateFormatter(format=dformat) + editor = TimeEditor() + else: + formatter = StringFormatter() + editor = StringEditor() + column = TableColumn(field=d.name, title=d.pprint_label, + editor=editor, formatter=formatter) + columns.append(column) style['reorderable'] = False table = DataTable(source=source, columns=columns, height=self.height, width=self.width, **style) diff --git a/holoviews/plotting/bokeh/util.py b/holoviews/plotting/bokeh/util.py index 41316e01d5..73c1ae75e0 100644 --- a/holoviews/plotting/bokeh/util.py +++ b/holoviews/plotting/bokeh/util.py @@ -597,7 +597,7 @@ def date_to_integer(date): date = dt64_to_dt(date) elif pd and isinstance(date, pd.Timestamp): date = date.to_pydatetime() - if isinstance(date, dt.datetime): + if isinstance(date, (dt.datetime, dt.date)): dt_int = time.mktime(date.timetuple())*1000 else: raise ValueError('Datetime type not recognized') diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py index 5cce4baf08..0741e2069e 100644 --- a/holoviews/plotting/mpl/chart.py +++ b/holoviews/plotting/mpl/chart.py @@ -72,7 +72,8 @@ def get_data(self, element, ranges, style): ys = element.dimension_values(1) dims = element.dimensions() if xs.dtype.kind == 'M': - dt_format = Dimension.type_formatters[np.datetime64] + dimtype = element.get_dimension_type(0) + dt_format = Dimension.type_formatters.get(dimtype, '%Y-%m-%d %H:%M:%S') dims[0] = dims[0](value_format=DateFormatter(dt_format)) coords = (ys, xs) if self.invert_axes else (xs, ys) return coords, style, {'dimensions': dims}