diff --git a/holoviews/element/comparison.py b/holoviews/element/comparison.py index b7afb1cb61..487ccb2cb4 100644 --- a/holoviews/element/comparison.py +++ b/holoviews/element/comparison.py @@ -251,6 +251,10 @@ def compare_floats(cls, arr1, arr2, msg='Floats'): @classmethod def compare_arrays(cls, arr1, arr2, msg='Arrays'): try: + if arr1.dtype.kind == 'M': + arr1 = arr1.astype('datetime64[ns]').astype('int64') + if arr2.dtype.kind == 'M': + arr2 = arr2.astype('datetime64[ns]').astype('int64') assert_array_equal(arr1, arr2) except: try: diff --git a/holoviews/operation/element.py b/holoviews/operation/element.py index 5cb505700e..07cdf83073 100644 --- a/holoviews/operation/element.py +++ b/holoviews/operation/element.py @@ -674,32 +674,52 @@ class interpolate_curve(Operation): Controls the transition point of the step along the x-axis.""") @classmethod - def pts_to_prestep(cls, x, y): - steps = np.zeros((2, 2 * len(x) - 1)) - steps[0, 0::2] = x - steps[0, 1::2] = steps[0, 0:-2:2] - steps[1:, 0::2] = y - steps[1:, 1::2] = steps[1:, 2::2] - return steps + def pts_to_prestep(cls, x, values): + steps = np.zeros(2 * len(x) - 1) + value_steps = tuple(np.empty(2 * len(x) - 1, dtype=v.dtype) for v in values) + + steps[0::2] = x + steps[1::2] = steps[0:-2:2] + + val_arrays = [] + for v, s in zip(values, value_steps): + s[0::2] = v + s[1::2] = s[2::2] + val_arrays.append(s) + + return steps, tuple(val_arrays) @classmethod - def pts_to_midstep(cls, x, y): - steps = np.zeros((2, 2 * len(x))) - x = np.asanyarray(x) - steps[0, 1:-1:2] = steps[0, 2::2] = (x[:-1] + x[1:]) / 2 - steps[0, 0], steps[0, -1] = x[0], x[-1] - steps[1:, 0::2] = y - steps[1:, 1::2] = steps[1:, 0::2] - return steps + def pts_to_midstep(cls, x, values): + steps = np.zeros(2 * len(x)) + value_steps = tuple(np.empty(2 * len(x), dtype=v.dtype) for v in values) + + steps[1:-1:2] = steps[2::2] = x[:-1] + (x[1:] - x[:-1])/2 + steps[0], steps[-1] = x[0], x[-1] + + val_arrays = [] + for v, s in zip(values, value_steps): + s[0::2] = v + s[1::2] = s[0::2] + val_arrays.append(s) + + return steps, tuple(val_arrays) @classmethod - def pts_to_poststep(cls, x, y): - steps = np.zeros((2, 2 * len(x) - 1)) - steps[0, 0::2] = x - steps[0, 1::2] = steps[0, 2::2] - steps[1:, 0::2] = y - steps[1:, 1::2] = steps[1:, 0:-2:2] - return steps + def pts_to_poststep(cls, x, values): + steps = np.zeros(2 * len(x) - 1) + value_steps = tuple(np.empty(2 * len(x) - 1, dtype=v.dtype) for v in values) + + steps[0::2] = x + steps[1::2] = steps[2::2] + + val_arrays = [] + for v, s in zip(values, value_steps): + s[0::2] = v + s[1::2] = s[0:-2:2] + val_arrays.append(s) + + return steps, tuple(val_arrays) def _process_layer(self, element, key=None): INTERPOLATE_FUNCS = {'steps-pre': self.pts_to_prestep, @@ -707,12 +727,17 @@ def _process_layer(self, element, key=None): 'steps-post': self.pts_to_poststep} if self.p.interpolation not in INTERPOLATE_FUNCS: return element - x, y = element.dimension_values(0), element.dimension_values(1) - if 'f' in (x.dtype.kind, y.dtype.kind): - x, y = x.astype('float'), y.astype('float') - array = INTERPOLATE_FUNCS[self.p.interpolation](x, y) - dvals = tuple(element.dimension_values(d) for d in element.dimensions()[2:]) - return element.clone((array[0, :].astype(x.dtype), array[1, :].astype(y.dtype))+dvals) + x = element.dimension_values(0) + dtype = x.dtype + is_datetime = dtype.kind == 'M' or isinstance(x[0], datetime_types) + if is_datetime: + dt_type = dtype if dtype.kind == 'M' else 'datetime64[ns]' + x = x.astype(dt_type).astype('int64') + dvals = tuple(element.dimension_values(d) for d in element.dimensions()[1:]) + xs, dvals = INTERPOLATE_FUNCS[self.p.interpolation](x.astype('f'), dvals) + if is_datetime: + xs = xs.astype(dt_type) + return element.clone((xs,)+dvals) def _process(self, element, key=None): return element.map(self._process_layer, Element) @@ -833,4 +858,3 @@ def _process(self, p, element, ranges={}): datatype=['dataframe', 'dictionary']) data[(d1.name, d2.name)] = el return data - diff --git a/tests/operation/testoperation.py b/tests/operation/testoperation.py index 7b59174b77..e1d709fec5 100644 --- a/tests/operation/testoperation.py +++ b/tests/operation/testoperation.py @@ -192,16 +192,77 @@ def test_interpolate_curve_pre(self): curve = Curve([(0, 0), (0, 0.5), (1, 0.5), (1, 1), (2, 1)]) self.assertEqual(interpolated, curve) + def test_interpolate_curve_pre_with_values(self): + interpolated = interpolate_curve(Curve([(0, 0, 'A'), (1, 0.5, 'B'), (2, 1, 'C')], vdims=['y', 'z']), + interpolation='steps-pre') + curve = Curve([(0, 0, 'A'), (0, 0.5, 'B'), (1, 0.5, 'B'), (1, 1, 'C'), (2, 1, 'C')], vdims=['y', 'z']) + self.assertEqual(interpolated, curve) + + def test_interpolate_datetime_curve_pre(self): + dates = np.array([dt.datetime(2017, 1, i) for i in range(1, 5)]).astype('M') + values = [0, 1, 2, 3] + interpolated = interpolate_curve(Curve((dates, values)), interpolation='steps-pre') + dates_interp = np.array([ + '2017-01-01T00:00:16.364011520', '2017-01-01T00:00:16.364011520', + '2017-01-02T00:01:05.465745408', '2017-01-02T00:01:05.465745408', + '2017-01-02T23:59:37.128525824', '2017-01-02T23:59:37.128525824', + '2017-01-04T00:00:26.230259712' + ], dtype='datetime64[ns]') + curve = Curve((dates_interp, [0, 1, 1, 2, 2, 3, 3])) + self.assertEqual(interpolated, curve) + def test_interpolate_curve_mid(self): interpolated = interpolate_curve(Curve([0, 0.5, 1]), interpolation='steps-mid') curve = Curve([(0, 0), (0.5, 0), (0.5, 0.5), (1.5, 0.5), (1.5, 1), (2, 1)]) self.assertEqual(interpolated, curve) + def test_interpolate_curve_mid_with_values(self): + interpolated = interpolate_curve(Curve([(0, 0, 'A'), (1, 0.5, 'B'), (2, 1, 'C')], vdims=['y', 'z']), + interpolation='steps-mid') + curve = Curve([(0, 0, 'A'), (0.5, 0, 'A'), (0.5, 0.5, 'B'), + (1.5, 0.5, 'B'), (1.5, 1, 'C'), (2, 1, 'C')], + vdims=['y', 'z']) + self.assertEqual(interpolated, curve) + + def test_interpolate_datetime_curve_mid(self): + dates = np.array([dt.datetime(2017, 1, i) for i in range(1, 5)]).astype('M') + values = [0, 1, 2, 3] + interpolated = interpolate_curve(Curve((dates, values)), interpolation='steps-mid') + dates_interp = np.array([ + '2017-01-01T00:00:16.364011520', '2017-01-01T11:59:32.195401728', + '2017-01-01T11:59:32.195401728', '2017-01-02T12:00:21.297135616', + '2017-01-02T12:00:21.297135616', '2017-01-03T12:01:10.398869504', + '2017-01-03T12:01:10.398869504', '2017-01-04T00:00:26.230259712' + ], dtype='datetime64[ns]') + curve = Curve((dates_interp, [0, 0, 1, 1, 2, 2, 3, 3])) + self.assertEqual(interpolated, curve) + def test_interpolate_curve_post(self): interpolated = interpolate_curve(Curve([0, 0.5, 1]), interpolation='steps-post') curve = Curve([(0, 0), (1, 0), (1, 0.5), (2, 0.5), (2, 1)]) self.assertEqual(interpolated, curve) + def test_interpolate_curve_post_with_values(self): + interpolated = interpolate_curve(Curve([(0, 0, 'A'), (1, 0.5, 'B'), (2, 1, 'C')], vdims=['y', 'z']), + interpolation='steps-post') + curve = Curve([(0, 0, 'A'), (1, 0, 'A'), (1, 0.5, 'B'), + (2, 0.5, 'B'), (2, 1, 'C')], + vdims=['y', 'z']) + self.assertEqual(interpolated, curve) + + def test_interpolate_datetime_curve_post(self): + dates = np.array([dt.datetime(2017, 1, i) for i in range(1, 5)]).astype('M') + values = [0, 1, 2, 3] + interpolated = interpolate_curve(Curve((dates, values)), interpolation='steps-post') + dates_interp = np.array([ + '2017-01-01T00:00:16.364011520', '2017-01-02T00:01:05.465745408', + '2017-01-02T00:01:05.465745408', '2017-01-02T23:59:37.128525824', + '2017-01-02T23:59:37.128525824', '2017-01-04T00:00:26.230259712', + '2017-01-04T00:00:26.230259712' + ], dtype='datetime64[ns]') + curve = Curve((dates_interp, [0, 0, 1, 1, 2, 2, 3])) + self.assertEqual(interpolated, curve) + def test_stack_area_overlay(self): areas = Area([1, 2, 3]) * Area([1, 2, 3]) stacked = Area.stack(areas)