Skip to content

Commit

Permalink
Improved Path and Bounds datetime handling (#3662)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr authored Apr 29, 2019
1 parent f4cb592 commit 7a68bb5
Show file tree
Hide file tree
Showing 7 changed files with 60 additions and 31 deletions.
2 changes: 1 addition & 1 deletion holoviews/core/data/multipath.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ def split(cls, dataset, start, end, datatype, **kwargs):
obj = ds.dframe(**kwargs)
elif datatype == 'columns':
if ds.interface.datatype == 'dictionary':
obj = dict(d)
obj = dict(ds.data)
else:
obj = ds.columns(**kwargs)
else:
Expand Down
12 changes: 12 additions & 0 deletions holoviews/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,18 @@ def isfinite(val):
return np.isfinite(val)


def isdatetime(value):
"""
Whether the array or scalar is recognized datetime type.
"""
if isinstance(value, np.ndarray):
return (value.dtype.kind == "M" or
(value.dtype.kind == "O" and len(value) and
isinstance(value[0], datetime_types)))
else:
return isinstance(value, datetime_types)


def find_minmax(lims, olims):
"""
Takes (a1, a2) and (b1, b2) as input and returns
Expand Down
8 changes: 5 additions & 3 deletions holoviews/element/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from ..core import Element2D, Dataset
from ..core.data import MultiInterface
from ..core.dimension import Dimension, asdim
from ..core.util import config, disable_constant, isscalar
from ..core.util import OrderedDict, config, disable_constant, isscalar
from .geom import Geometry


Expand Down Expand Up @@ -476,7 +476,7 @@ class Bounds(BaseShape):
used to compute the corresponding lbrt tuple.
"""

lbrt = param.NumericTuple(default=(-0.5, -0.5, 0.5, 0.5), doc="""
lbrt = param.Tuple(default=(-0.5, -0.5, 0.5, 0.5), doc="""
The (left, bottom, right, top) coordinates of the bounding box.""")

group = param.String(default='Bounds', constant=True, doc="The assigned group name.")
Expand All @@ -489,4 +489,6 @@ def __init__(self, lbrt, **params):

super(Bounds, self).__init__(lbrt=lbrt, **params)
(l,b,r,t) = self.lbrt
self.data = [np.array([(l, b), (l, t), (r, t), (r, b),(l, b)])]
xdim, ydim = self.kdims
self.data = [OrderedDict([(xdim.name, np.array([l, l, r, r, l])),
(ydim.name, np.array([b, t, t, b, b]))])]
8 changes: 4 additions & 4 deletions holoviews/operation/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
HoloMap, Dataset, Element, Collator, Dimension)
from ..core.data import ArrayInterface, DictInterface, default_datatype
from ..core.util import (group_sanitizer, label_sanitizer, pd,
basestring, datetime_types, isfinite, dt_to_int)
basestring, datetime_types, isfinite, dt_to_int,
isdatetime)
from ..element.chart import Histogram, Scatter
from ..element.raster import Image, RGB
from ..element.path import Contours, Polygons
Expand Down Expand Up @@ -586,7 +587,7 @@ def _process(self, element, key=None):
bins = None if self.p.bins is None else np.asarray(self.p.bins)
steps = self.p.num_bins + 1
start, end = hist_range
if data.dtype.kind == 'M' or (data.dtype.kind == 'O' and isinstance(data[0], datetime_types)):
if isdatetime(data):
start, end = dt_to_int(start, 'ns'), dt_to_int(end, 'ns')
datetimes = True
data = data.astype('datetime64[ns]').astype('int64')
Expand Down Expand Up @@ -769,8 +770,7 @@ def _process_layer(self, element, key=None):
if self.p.interpolation not in INTERPOLATE_FUNCS:
return element
x = element.dimension_values(0)
dtype = x.dtype
is_datetime = dtype.kind == 'M' or isinstance(x[0], datetime_types)
is_datetime = isdatetime(x)
if is_datetime:
dt_type = 'datetime64[ns]'
x = x.astype(dt_type).astype('int64')
Expand Down
4 changes: 2 additions & 2 deletions holoviews/plotting/bokeh/tabular.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from ...core import Dataset, Dimension
from ...element import ItemTable
from ...streams import Buffer
from ...core.util import dimension_sanitizer, datetime_types
from ...core.util import dimension_sanitizer, isdatetime
from ..plot import GenericElementPlot
from .plot import BokehPlot

Expand Down Expand Up @@ -99,7 +99,7 @@ def _get_columns(self, element, data):
elif kind == 'f':
formatter = NumberFormatter(format='0,0.0[00000]')
editor = NumberEditor()
elif kind == 'M' or (kind == 'O' and len(data[col]) and type(data[col][0]) in datetime_types):
elif isdatetime(data[col]):
dimtype = element.get_dimension_type(col)
dformat = Dimension.type_formatters.get(dimtype, '%Y-%m-%d %H:%M:%S')
formatter = DateFormatter(format=dformat)
Expand Down
17 changes: 8 additions & 9 deletions holoviews/plotting/mpl/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from ...core.util import (
OrderedDict, match_spec, unique_iterator, basestring, max_range,
isfinite, datetime_types, dt_to_int, dt64_to_dt, search_indices,
unique_array, isscalar
unique_array, isscalar, isdatetime
)
from ...element import Raster, HeatMap
from ...operation import interpolate_curve
Expand Down Expand Up @@ -82,7 +82,7 @@ def get_data(self, element, ranges, style):
xs = element.dimension_values(0)
ys = element.dimension_values(1)
dims = element.dimensions()
if xs.dtype.kind == 'M' or (len(xs) and isinstance(xs[0], datetime_types)):
if isdatetime(xs):
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))
Expand All @@ -91,7 +91,7 @@ def get_data(self, element, ranges, style):

def init_artists(self, ax, plot_args, plot_kwargs):
xs, ys = plot_args
if xs.dtype.kind == 'M' or (len(xs) and isinstance(xs[0], datetime_types)):
if isdatetime(xs):
artist = ax.plot_date(xs, ys, '-', **plot_kwargs)[0]
else:
artist = ax.plot(xs, ys, **plot_kwargs)[0]
Expand Down Expand Up @@ -333,8 +333,8 @@ def initialize_plot(self, ranges=None):

# Get plot ranges and values
dims = hist.dimensions()[:2]
edges, hvals, widths, lims, isdatetime = self._process_hist(hist)
if isdatetime and not dims[0].value_format:
edges, hvals, widths, lims, is_datetime = self._process_hist(hist)
if is_datetime and not dims[0].value_format:
dt_format = Dimension.type_formatters[np.datetime64]
dims[0] = dims[0](value_format=DateFormatter(dt_format))

Expand Down Expand Up @@ -381,14 +381,13 @@ def _process_hist(self, hist):
hist_vals = np.array(values)
xlim = hist.range(0)
ylim = hist.range(1)
isdatetime = False
if edges.dtype.kind == 'M' or isinstance(edges[0], datetime_types):
is_datetime = isdatetime(edges)
if is_datetime:
edges = np.array([dt64_to_dt(e) if isinstance(e, np.datetime64) else e for e in edges])
edges = date2num(edges)
xlim = tuple(dt_to_int(v, 'D') for v in xlim)
isdatetime = True
widths = np.diff(edges)
return edges[:-1], hist_vals, widths, xlim+ylim, isdatetime
return edges[:-1], hist_vals, widths, xlim+ylim, is_datetime


def _compute_ticks(self, element, edges, widths, lims):
Expand Down
40 changes: 28 additions & 12 deletions holoviews/plotting/mpl/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
import param
import numpy as np

from matplotlib.dates import date2num, DateFormatter
from matplotlib.collections import PatchCollection, LineCollection

from ...core import util
from ...core.dimension import Dimension
from ...core.options import abbreviated_exception
from ...element import Polygons
from .element import ColorbarPlot
Expand All @@ -32,28 +34,42 @@ def get_data(self, element, ranges, style):
style = self._apply_transforms(element, ranges, style)

cdim = element.get_dimension(self.color_index)
if cdim: cidx = element.get_dimension_index(cdim)
style_mapping = any(True for v in style.values() if isinstance(v, np.ndarray))
if not (cdim or style_mapping):
paths = element.split(datatype='array', dimensions=element.kdims)
if self.invert_axes:
paths = [p[:, ::-1] for p in paths]
return (paths,), style, {}
paths, cvals = [], []
for path in element.split(datatype='array'):
length = len(path)
dims = element.kdims
xdim, ydim = dims
generic_dt_format = Dimension.type_formatters[np.datetime64]
paths, cvals, dims = [], [], {}
for path in element.split(datatype='columns'):
xarr, yarr = path[xdim.name], path[ydim.name]
if util.isdatetime(xarr):
dt_format = Dimension.type_formatters.get(type(xarr[0]), generic_dt_format)
xarr = date2num(xarr)
dims[0] = xdim(value_format=DateFormatter(dt_format))
if util.isdatetime(yarr):
dt_format = Dimension.type_formatters.get(type(yarr[0]), generic_dt_format)
yarr = date2num(yarr)
dims[1] = ydim(value_format=DateFormatter(dt_format))
arr = np.column_stack([xarr, yarr])
if not (cdim or style_mapping):
paths.append(arr)
continue
length = len(xarr)
for (s1, s2) in zip(range(length-1), range(1, length+1)):
if cdim:
cvals.append(path[s1, cidx])
paths.append(path[s1:s2+1, :2])
cvals.append(path[cdim.name])
paths.append(arr[s1:s2+1])
if self.invert_axes:
paths = [p[::-1] for p in paths]
if not (cdim or style_mapping):
return (paths,), style, {'dimensions': dims}
if cdim:
self._norm_kwargs(element, ranges, style, cdim)
style['array'] = np.array(cvals)
if 'c' in style:
style['array'] = style.pop('c')
if 'vmin' in style:
style['clim'] = style.pop('vmin', None), style.pop('vmax', None)
return (paths,), style, {}
return (paths,), style, {'dimensions': dims}

def init_artists(self, ax, plot_args, plot_kwargs):
line_segments = LineCollection(*plot_args, **plot_kwargs)
Expand Down

0 comments on commit 7a68bb5

Please sign in to comment.