From 944b1b940d3ee7adad16a91cd24c0cdfad168d25 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 3 Oct 2019 14:09:35 +0200 Subject: [PATCH 1/7] Dataset clone passes along element as data --- holoviews/core/data/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/holoviews/core/data/__init__.py b/holoviews/core/data/__init__.py index 3dd6d9d4f3..6763107a9a 100644 --- a/holoviews/core/data/__init__.py +++ b/holoviews/core/data/__init__.py @@ -1010,6 +1010,9 @@ def clone(self, data=None, shared_data=True, new_type=None, *args, **overrides): if data is None: overrides['_validate_vdims'] = False + # Allows datatype conversions + data = self + if 'dataset' not in overrides: overrides['dataset'] = self.dataset From ea4a149a39f12a5e67799d70fc299ae7b5eadd7c Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 3 Oct 2019 14:10:37 +0200 Subject: [PATCH 2/7] Allow datatype casting of irregular meshes --- holoviews/core/data/interface.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/holoviews/core/data/interface.py b/holoviews/core/data/interface.py index 477c1b20f5..eae8bfb3ea 100644 --- a/holoviews/core/data/interface.py +++ b/holoviews/core/data/interface.py @@ -225,14 +225,19 @@ def initialize(cls, eltype, data, kdims, vdims, datatype=None): if not datatype: datatype = eltype.datatype - if data.interface.datatype in datatype and data.interface.datatype in eltype.datatype: + interface = data.interface + if interface.datatype in datatype and interface.datatype in eltype.datatype: data = data.data - elif data.interface.gridded and any(cls.interfaces[dt].gridded for dt in datatype): - gridded = OrderedDict([(kd.name, data.dimension_values(kd.name, expanded=False)) - for kd in data.kdims]) + elif interface.gridded and any(cls.interfaces[dt].gridded for dt in datatype): + new_data = [] + for kd in data.kdims: + irregular = interface.irregular(data, kd) + coords = data.dimension_values(kd.name, expanded=irregular, + flat=not irregular) + new_data.append(coords) for vd in data.vdims: - gridded[vd.name] = data.dimension_values(vd, flat=False) - data = tuple(gridded.values()) + new_data.append(interface.values(data, vd, flat=False, compute=False)) + data = tuple(new_data) else: data = tuple(data.columns().values()) elif isinstance(data, Element): From eab0536b569d50da8009325868c27893f36d8f59 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 3 Oct 2019 14:15:32 +0200 Subject: [PATCH 3/7] Add support for fast QuadMesh rasterization --- holoviews/operation/datashader.py | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/holoviews/operation/datashader.py b/holoviews/operation/datashader.py index b30d3dec80..65246da03f 100644 --- a/holoviews/operation/datashader.py +++ b/holoviews/operation/datashader.py @@ -1017,7 +1017,36 @@ class quadmesh_rasterize(trimesh_rasterize): """ def _precompute(self, element, agg): - return super(quadmesh_rasterize, self)._precompute(element.trimesh(), agg) + if ds_version <= '0.7.0': + return super(quadmesh_rasterize, self)._precompute(element.trimesh(), agg) + + def _process(self, element, key=None): + if ds_version <= '0.7.0': + return super(quadmesh_rasterize, self)._process(element, key) + + if element.interface.datatype != 'xarray': + element = element.clone(datatype=['xarray']) + data = element.data + + x, y = element.kdims + agg_fn = self._get_aggregator(element) + info = self._get_sampling(element, x, y) + (x_range, y_range), (xs, ys), (width, height), (xtype, ytype) = info + + bounds = (x_range[0], y_range[0], x_range[1], y_range[1]) + params = dict(get_param_values(element), datatype=['xarray'], + bounds=bounds) + + if width == 0 or height == 0: + return self._empty_agg(element, x, y, width, height, xs, ys, agg_fn, **params) + + cvs = ds.Canvas(plot_width=width, plot_height=height, + x_range=x_range, y_range=y_range) + + vdim = getattr(agg_fn, 'column', element.vdims[0].name) + agg = cvs.quadmesh(data[vdim], x.name, y.name, agg_fn) + + return Image(agg, **params) From a7f2bd24a88d855eaff9d158e1487d6e86c127d7 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 3 Oct 2019 14:29:06 +0200 Subject: [PATCH 4/7] Fix flake --- holoviews/core/data/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/core/data/interface.py b/holoviews/core/data/interface.py index eae8bfb3ea..5f6d955836 100644 --- a/holoviews/core/data/interface.py +++ b/holoviews/core/data/interface.py @@ -7,7 +7,7 @@ from .. import util from ..element import Element -from ..ndmapping import OrderedDict, NdMapping +from ..ndmapping import NdMapping def get_array_types(): From 831735f03090e8d5b2fab94ccf49cef8245ffb64 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 3 Oct 2019 14:51:34 +0200 Subject: [PATCH 5/7] Fixes for clone --- holoviews/core/data/__init__.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/holoviews/core/data/__init__.py b/holoviews/core/data/__init__.py index 6763107a9a..ed021e9887 100644 --- a/holoviews/core/data/__init__.py +++ b/holoviews/core/data/__init__.py @@ -990,13 +990,17 @@ def to(self): return self._conversion_interface(self) - def clone(self, data=None, shared_data=True, new_type=None, *args, **overrides): + def clone(self, data=None, shared_data=True, new_type=None, link=True, + *args, **overrides): """Clones the object, overriding data and parameters. Args: data: New data replacing the existing data shared_data (bool, optional): Whether to use existing data new_type (optional): Type to cast object to + link (bool, optional): Whether clone should be linked + Determines whether Streams and Links attached to + original object will be inherited. *args: Additional arguments to pass to constructor **overrides: New keyword arguments to pass to constructor @@ -1011,7 +1015,10 @@ def clone(self, data=None, shared_data=True, new_type=None, *args, **overrides): overrides['_validate_vdims'] = False # Allows datatype conversions - data = self + if shared_data: + data = self + if link: + overrides['plot_id'] = self._plot_id if 'dataset' not in overrides: overrides['dataset'] = self.dataset From 6870c05ffef0d513e18a54d8e91508f2537327fa Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Thu, 3 Oct 2019 17:02:27 +0200 Subject: [PATCH 6/7] Fixed QuadMesh rasterize tests --- holoviews/tests/operation/testdatashader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/holoviews/tests/operation/testdatashader.py b/holoviews/tests/operation/testdatashader.py index 8d016e64b2..99bfd38dc2 100644 --- a/holoviews/tests/operation/testdatashader.py +++ b/holoviews/tests/operation/testdatashader.py @@ -626,14 +626,14 @@ def test_rasterize_trimesh_string_aggregator(self): def test_rasterize_quadmesh(self): qmesh = QuadMesh(([0, 1], [0, 1], np.array([[0, 1], [2, 3]]))) img = rasterize(qmesh, width=3, height=3, dynamic=False, aggregator=ds.mean('z')) - image = Image(np.array([[2., 3., np.NaN], [0, 1, np.NaN], [np.NaN, np.NaN, np.NaN]]), + image = Image(np.array([[2, 3, 3], [2, 3, 3], [0, 1, 1]]), bounds=(-.5, -.5, 1.5, 1.5)) self.assertEqual(img, image) def test_rasterize_quadmesh_string_aggregator(self): qmesh = QuadMesh(([0, 1], [0, 1], np.array([[0, 1], [2, 3]]))) img = rasterize(qmesh, width=3, height=3, dynamic=False, aggregator='mean') - image = Image(np.array([[2., 3., np.NaN], [0, 1, np.NaN], [np.NaN, np.NaN, np.NaN]]), + image = Image(np.array([[2, 3, 3], [2, 3, 3], [0, 1, 1]]), bounds=(-.5, -.5, 1.5, 1.5)) self.assertEqual(img, image) From 4de605afa8c9b1009ef155d22d439219478f6cf1 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 4 Oct 2019 18:51:12 +0200 Subject: [PATCH 7/7] Add datetime support to quadmesh rasterize --- holoviews/operation/datashader.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/holoviews/operation/datashader.py b/holoviews/operation/datashader.py index 65246da03f..cb983dea7b 100644 --- a/holoviews/operation/datashader.py +++ b/holoviews/operation/datashader.py @@ -1032,10 +1032,17 @@ def _process(self, element, key=None): agg_fn = self._get_aggregator(element) info = self._get_sampling(element, x, y) (x_range, y_range), (xs, ys), (width, height), (xtype, ytype) = info + if xtype == 'datetime': + data[x.name] = data[x.name].astype('datetime64[us]').astype('int64') + if ytype == 'datetime': + data[y.name] = data[y.name].astype('datetime64[us]').astype('int64') - bounds = (x_range[0], y_range[0], x_range[1], y_range[1]) + # Compute bounds (converting datetimes) + ((x0, x1), (y0, y1)), (xs, ys) = self._dt_transform( + x_range, y_range, xs, ys, xtype, ytype + ) params = dict(get_param_values(element), datatype=['xarray'], - bounds=bounds) + bounds=(x0, y0, x1, y1)) if width == 0 or height == 0: return self._empty_agg(element, x, y, width, height, xs, ys, agg_fn, **params) @@ -1045,6 +1052,11 @@ def _process(self, element, key=None): vdim = getattr(agg_fn, 'column', element.vdims[0].name) agg = cvs.quadmesh(data[vdim], x.name, y.name, agg_fn) + xdim, ydim = list(agg.dims)[:2][::-1] + if xtype == "datetime": + agg[xdim] = (agg[xdim]/1e3).astype('datetime64[us]') + if ytype == "datetime": + agg[ydim] = (agg[ydim]/1e3).astype('datetime64[us]') return Image(agg, **params)