From 9df4a55bc0c6976662cf0703c081c84cd8ae400e Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 2 Mar 2020 17:42:40 +0100 Subject: [PATCH] Implemented reverse selection expression transforms --- holoviews/core/accessors.py | 46 +++++++++++++++++++++++++++++++-- holoviews/core/data/__init__.py | 14 +++++++--- holoviews/core/dimension.py | 2 +- holoviews/streams.py | 3 +++ holoviews/util/transform.py | 14 ++++++++++ 5 files changed, 73 insertions(+), 6 deletions(-) diff --git a/holoviews/core/accessors.py b/holoviews/core/accessors.py index 6614818c1c..a5bed2bed8 100644 --- a/holoviews/core/accessors.py +++ b/holoviews/core/accessors.py @@ -306,6 +306,46 @@ def _filter_cache(self, dmap, kdims): filtered.append((key, value)) return filtered + def _transform_dimension(self, kdims, vdims, dimension): + if dimension in kdims: + idx = kdims.index(dimension) + dimension = self._obj.kdims[idx] + elif dimension in vdims: + idx = vdims.index(dimension) + dimension = self._obj.vdims[idx] + return dimension + + def _create_expression_transform(self, kdims, vdims, exclude=[]): + from .dimension import dimension_name, Dimension + from ..util.transform import dim + + def _transform_expression(expression): + if dimension_name(expression.dimension) in exclude: + dimension = expression.dimension + else: + dimension = self._transform_dimension( + kdims, vdims, expression.dimension + ) + expression = expression.clone(dimension) + ops = [] + for op in expression.ops: + new_op = dict(op) + new_args = [] + for arg in op['args']: + if isinstance(arg, dim): + arg = _transform_expression(arg) + new_args.append(arg) + new_op['args'] = tuple(new_args) + new_kwargs = {} + for kw, kwarg in op['kwargs'].items(): + if isinstance(kwarg, dim): + kwarg = _transform_expression(kwarg) + new_kwargs[kw] = kwarg + new_op['kwargs'] = new_kwargs + ops.append(new_op) + expression.ops = ops + return expression + return _transform_expression def __call__(self, specs=None, **dimensions): """ @@ -331,13 +371,15 @@ def __call__(self, specs=None, **dimensions): kdims = self.replace_dimensions(obj.kdims, dimensions) vdims = self.replace_dimensions(obj.vdims, dimensions) zipped_dims = zip(obj.kdims+obj.vdims, kdims+vdims) - renames = {pk.name: nk for pk, nk in zipped_dims if pk != nk} + renames = {pk.name: nk for pk, nk in zipped_dims if pk.name != nk.name} if self.mode == 'dataset': data = obj.data if renames: data = obj.interface.redim(obj, renames) - clone = obj.clone(data, kdims=kdims, vdims=vdims) + transform = self._create_expression_transform(kdims, vdims, list(renames.values())) + transforms = obj._transforms + [transform] + clone = obj.clone(data, kdims=kdims, vdims=vdims, transforms=transforms) if self._obj.dimensions(label='name') == clone.dimensions(label='name'): # Ensure that plot_id is inherited as long as dimension # name does not change diff --git a/holoviews/core/data/__init__.py b/holoviews/core/data/__init__.py index 6a4db9366d..4b538a0af3 100644 --- a/holoviews/core/data/__init__.py +++ b/holoviews/core/data/__init__.py @@ -283,14 +283,21 @@ def __init__(self, data, kdims=None, vdims=None, **kwargs): input_data = data dataset_provided = 'dataset' in kwargs input_dataset = kwargs.pop('dataset', None) - input_pipeline = kwargs.pop( - 'pipeline', None - ) + input_pipeline = kwargs.pop('pipeline', None) + input_transforms = kwargs.pop('transforms', None) if isinstance(data, Element): pvals = util.get_param_values(data) kwargs.update([(l, pvals[l]) for l in ['group', 'label'] if l in pvals and l not in kwargs]) + if isinstance(data, Dataset): + if not dataset_provided and data._dataset is not None: + input_dataset = data._dataset + if input_pipeline is None: + input_pipeline = data.pipeline + if input_transforms is None: + input_transforms = data._transforms + kwargs.update(process_dimensions(kdims, vdims)) kdims, vdims = kwargs.get('kdims'), kwargs.get('vdims') @@ -316,6 +323,7 @@ def __init__(self, data, kdims=None, vdims=None, **kwargs): operations=input_pipeline.operations + [init_op], output_type=type(self), ) + self._transforms = input_transforms or [] # Handle initializing the dataset property. self._dataset = None diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index cf5c0eec3e..b19448e2e6 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -965,7 +965,7 @@ def get_dimension(self, dimension, default=None, strict=False): if isinstance(dimension, Dimension): dims = [d for d in all_dims if dimension == d] if strict and not dims: - raise KeyError("Dimension %r not found." % dimension) + raise KeyError("%r not found." % dimension) elif dims: return dims[0] else: diff --git a/holoviews/streams.py b/holoviews/streams.py index f04b08dac3..75b3817fa1 100644 --- a/holoviews/streams.py +++ b/holoviews/streams.py @@ -818,6 +818,9 @@ def _set_expr(**params): params = dict(params, index_cols=self._index_cols) selection_expr, bbox, region_element = \ element._get_selection_expr_for_stream_value(**params) + for expr_transform in element._transforms[::-1]: + if selection_expr is not None: + selection_expr = expr_transform(selection_expr) self.event( selection_expr=selection_expr, diff --git a/holoviews/util/transform.py b/holoviews/util/transform.py index 8e85553526..8c38eb7ec5 100644 --- a/holoviews/util/transform.py +++ b/holoviews/util/transform.py @@ -230,6 +230,20 @@ def __init__(self, obj, *args, **kwargs): 'reverse': kwargs.pop('reverse', False)}] self.ops = ops + + def clone(self, dimension=None, ops=None): + """ + Creates a clone of the dim expression optionally overriding + the dim and ops. + """ + if dimension is None: + dimension = self.dimension + new_dim = dim(dimension) + if ops is None: + ops = list(self.ops) + new_dim.ops = ops + return new_dim + @classmethod def register(cls, key, function): """