From 298005b2ea30e40d52bc16f35d0f293390989ea3 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Tue, 12 Mar 2019 17:28:01 +0000 Subject: [PATCH 1/9] Allow using apply method with method strings --- holoviews/core/dimension.py | 14 ++++++++++++-- holoviews/tests/core/testapply.py | 19 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index 4815085a8a..4e2fd668b2 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -1387,6 +1387,18 @@ def apply(self, function, streams=[], link_inputs=True, dynamic=None, **kwargs): from .spaces import DynamicMap from ..util import Dynamic + if isinstance(function, basestring): + method_name = function + def function(object, **kwargs): + method = getattr(object, method_name, None) + if method is None: + raise AttributeError('Applied method %s does not exist.' + 'When declaring a method to apply ' + 'as a string ensure a corresponding ' + 'method exists on the object.' % + method_name) + return method(**kwargs) + applies = isinstance(self, (ViewableElement, DynamicMap)) params = {p: val for p, val in kwargs.items() if isinstance(val, param.Parameter) @@ -1411,8 +1423,6 @@ def apply(self, function, streams=[], link_inputs=True, dynamic=None, **kwargs): inner_kwargs[k] = getattr(v.owner, v.name) if hasattr(function, 'dynamic'): inner_kwargs['dynamic'] = False - if util.is_param_method(function) and util.get_method_owner(function) is self: - return function(**inner_kwargs) return function(self, **inner_kwargs) elif self._deep_indexable: mapped = OrderedDict() diff --git a/holoviews/tests/core/testapply.py b/holoviews/tests/core/testapply.py index 0c60e58cca..f9d96bea02 100644 --- a/holoviews/tests/core/testapply.py +++ b/holoviews/tests/core/testapply.py @@ -28,6 +28,10 @@ def test_element_apply_simple(self): applied = self.element.apply(lambda x: x.relabel('Test')) self.assertEqual(applied, self.element.relabel('Test')) + def test_element_apply_method_as_string(self): + applied = self.element.apply('relabel', label='Test') + self.assertEqual(applied, self.element.relabel('Test')) + def test_element_apply_with_kwarg(self): applied = self.element.apply(lambda x, label: x.relabel(label), label='Test') self.assertEqual(applied, self.element.relabel('Test')) @@ -37,9 +41,9 @@ def test_element_apply_not_dynamic_with_instance_param(self): applied = self.element.apply(lambda x, label: x.relabel(label), label=pinst.param.label, dynamic=False) self.assertEqual(applied, self.element.relabel('Test')) - def test_element_apply_not_dynamic_element_method(self): + def test_element_apply_not_dynamic_with_method_string(self): pinst = ParamClass() - applied = self.element.apply(self.element.relabel, dynamic=False, label=pinst.param.label) + applied = self.element.apply('relabel', dynamic=False, label=pinst.param.label) self.assertEqual(applied, self.element.relabel('Test')) def test_element_apply_not_dynamic_with_param_method(self): @@ -158,6 +162,10 @@ def test_dmap_apply_dynamic(self): self.assertEqual(len(applied.streams), 0) self.assertEqual(applied[1], self.dmap[1].relabel('Test')) + def test_element_apply_method_as_string(self): + applied = self.dmap.apply('relabel', label='Test') + self.assertEqual(applied[1], self.dmap[1].relabel('Test')) + def test_dmap_apply_dynamic_with_kwarg(self): applied = self.dmap.apply(lambda x, label: x.relabel(label), label='Test') self.assertEqual(len(applied.streams), 0) @@ -179,6 +187,13 @@ def test_dmap_apply_dynamic_with_instance_param(self): pinst.label = 'Another label' self.assertEqual(applied[1], self.dmap[1].relabel('Another label')) + def test_dmap_apply_method_as_string_with_instance_param(self): + pinst = ParamClass() + applied = self.dmap.apply('relabel', label=pinst.param.label) + self.assertEqual(applied[1], self.dmap[1].relabel('Test')) + pinst.label = 'Another label' + self.assertEqual(applied[1], self.dmap[1].relabel('Another label')) + def test_dmap_apply_param_method_with_dependencies(self): pinst = ParamClass() applied = self.dmap.apply(pinst.apply_label) From c01f443e127b227fc342863002feab972e849921 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 22 Mar 2019 00:56:57 +0000 Subject: [PATCH 2/9] Factored apply out into accessor --- holoviews/core/accessors.py | 453 ++++++++++++++++++++++++++++++++++++ holoviews/core/dimension.py | 241 +------------------ holoviews/core/options.py | 159 +------------ holoviews/core/spaces.py | 13 +- 4 files changed, 472 insertions(+), 394 deletions(-) create mode 100644 holoviews/core/accessors.py diff --git a/holoviews/core/accessors.py b/holoviews/core/accessors.py new file mode 100644 index 0000000000..056d0637c3 --- /dev/null +++ b/holoviews/core/accessors.py @@ -0,0 +1,453 @@ +""" +Module for accessor objects for viewable HoloViews objects. +""" +from __future__ import absolute_import, unicode_literals + +from collections import OrderedDict + +import param + +from . import util +from .pprint import PrettyPrinter +from .options import Options, Store + + +class Apply(object): + """ + Utility to apply a function or operation to all viewable elements + inside the object. + """ + + def __init__(self, obj, mode=None): + self._obj = obj + + def __call__(self, function, streams=[], link_inputs=True, dynamic=None, **kwargs): + """Applies a function to all (Nd)Overlay or Element objects. + + Any keyword arguments are passed through to the function. If + keyword arguments are instance parameters, or streams are + supplied the returned object will dynamically update in + response to changes in those objects. + + Args: + function: A callable function + The function will be passed the return value of the + DynamicMap as the first argument and any supplied + stream values or keywords as additional keyword + arguments. + streams (list, optional): A list of Stream objects + The Stream objects can dynamically supply values which + will be passed to the function as keywords. + link_inputs (bool, optional): Whether to link the inputs + Determines whether Streams and Links attached to + original object will be inherited. + dynamic (bool, optional): Whether to make object dynamic + By default object is made dynamic if streams are + supplied, an instance parameter is supplied as a + keyword argument, or the supplied function is a + parameterized method. + kwargs (dict, optional): Additional keyword arguments + Keyword arguments which will be supplied to the + function. + + Returns: + A new object where the function was applied to all + contained (Nd)Overlay or Element objects. + """ + from .dimension import ViewableElement + from .spaces import DynamicMap + from ..util import Dynamic + + if isinstance(function, util.basestring): + args = kwargs.pop('_args', ()) + method_name = function + def function(object, **kwargs): + method = getattr(object, method_name, None) + if method is None: + raise AttributeError('Applied method %s does not exist.' + 'When declaring a method to apply ' + 'as a string ensure a corresponding ' + 'method exists on the object.' % + method_name) + return method(*args, **kwargs) + + applies = isinstance(self._obj, (ViewableElement, DynamicMap)) + params = {p: val for p, val in kwargs.items() + if isinstance(val, param.Parameter) + and isinstance(val.owner, param.Parameterized)} + param_methods = {p: val for p, val in kwargs.items() + if util.is_param_method(val, has_deps=True)} + + if dynamic is None: + dynamic = (bool(streams) or isinstance(self._obj, DynamicMap) or + util.is_param_method(function, has_deps=True) or + params or param_methods) + + if applies and dynamic: + return Dynamic(self._obj, operation=function, streams=streams, + kwargs=kwargs, link_inputs=link_inputs) + elif applies: + inner_kwargs = dict(kwargs) + for k, v in kwargs.items(): + if util.is_param_method(v, has_deps=True): + inner_kwargs[k] = v() + elif k in params: + inner_kwargs[k] = getattr(v.owner, v.name) + if hasattr(function, 'dynamic'): + inner_kwargs['dynamic'] = False + return function(self._obj, **inner_kwargs) + elif self._obj._deep_indexable: + mapped = [] + for k, v in self._obj.data.items(): + new_val = v.apply(function, dynamic=dynamic, streams=streams, + link_inputs=link_inputs, **kwargs) + if new_val is not None: + mapped.append((k, new_val)) + return self._obj.clone(mapped, link=link_inputs) + + + def aggregate(self, dimensions=None, function=None, spreadfn=None, **kwargs): + """Applies a aggregate function to all ViewableElements. + + See :py:meth:`Dimensioned.opts` and :py:meth:`Apply.__call__` + for more information. + """ + kwargs['_args'] = (dimensions, function, spreadfn) + return self.__call__('aggregate', **kwargs) + + def opts(self, *args, **kwargs): + """Applies options to all ViewableElement objects. + + See :py:meth:`Dimensioned.opts` and :py:meth:`Apply.__call__` + for more information. + """ + kwargs['_args'] = args + return self.__call__('opts', **kwargs) + + def reduce(self, dimensions=[], function=None, spreadfn=None, **kwargs): + """Applies a reduce function to all ViewableElement objects. + + See :py:meth:`Dimensioned.opts` and :py:meth:`Apply.__call__` + for more information. + """ + kwargs['_args'] = (dimensions, function, spreadfn) + return self.__call__('reduce', **kwargs) + + def select(self, **kwargs): + """Applies a selection to all ViewableElement objects. + + See :py:meth:`Dimensioned.opts` and :py:meth:`Apply.__call__` + for more information. + """ + return self.__call__('select', **kwargs) + + + +class redim(object): + """ + Utility that supports re-dimensioning any HoloViews object via the + redim method. + """ + + def __init__(self, obj, mode=None): + self._obj = obj + # Can be 'dataset', 'dynamic' or None + self.mode = mode + + def __str__(self): + return "" + + @classmethod + def replace_dimensions(cls, dimensions, overrides): + """Replaces dimensions in list with dictionary of overrides. + + Args: + dimensions: List of dimensions + overrides: Dictionary of dimension specs indexed by name + + Returns: + list: List of dimensions with replacements applied + """ + from .dimension import Dimension + + replaced = [] + for d in dimensions: + if d.name in overrides: + override = overrides[d.name] + elif d.label in overrides: + override = overrides[d.label] + else: + override = None + + if override is None: + replaced.append(d) + elif isinstance(override, (util.basestring, tuple)): + replaced.append(d.clone(override)) + elif isinstance(override, Dimension): + replaced.append(override) + elif isinstance(override, dict): + replaced.append(d.clone(override.get('name',None), + **{k:v for k,v in override.items() if k != 'name'})) + else: + raise ValueError('Dimension can only be overridden ' + 'with another dimension or a dictionary ' + 'of attributes') + return replaced + + + def _filter_cache(self, dmap, kdims): + """ + Returns a filtered version of the DynamicMap cache leaving only + keys consistently with the newly specified values + """ + filtered = [] + for key, value in dmap.data.items(): + if not any(kd.values and v not in kd.values for kd, v in zip(kdims, key)): + filtered.append((key, value)) + return filtered + + + def __call__(self, specs=None, **dimensions): + """ + Replace dimensions on the dataset and allows renaming + dimensions in the dataset. Dimension mapping should map + between the old dimension name and a dictionary of the new + attributes, a completely new dimension or a new string name. + """ + obj = self._obj + redimmed = obj + if obj._deep_indexable and self.mode != 'dataset': + deep_mapped = [(k, v.redim(specs, **dimensions)) + for k, v in obj.items()] + redimmed = obj.clone(deep_mapped) + + if specs is not None: + if not isinstance(specs, list): + specs = [specs] + matches = any(obj.matches(spec) for spec in specs) + if self.mode != 'dynamic' and not matches: + return redimmed + + 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} + + if self.mode == 'dataset': + data = obj.data + if renames: + data = obj.interface.redim(obj, renames) + clone = obj.clone(data, kdims=kdims, vdims=vdims) + if self._obj.dimensions(label='name') == clone.dimensions(label='name'): + # Ensure that plot_id is inherited as long as dimension + # name does not change + clone._plot_id = self._obj._plot_id + return clone + + if self.mode != 'dynamic': + return redimmed.clone(kdims=kdims, vdims=vdims) + + from ..util import Dynamic + def dynamic_redim(obj, **dynkwargs): + return obj.redim(specs, **dimensions) + dmap = Dynamic(obj, streams=obj.streams, operation=dynamic_redim) + dmap.data = OrderedDict(self._filter_cache(redimmed, kdims)) + with util.disable_constant(dmap): + dmap.kdims = kdims + dmap.vdims = vdims + return dmap + + + def _redim(self, name, specs, **dims): + dimensions = {k:{name:v} for k,v in dims.items()} + return self(specs, **dimensions) + + def cyclic(self, specs=None, **values): + return self._redim('cyclic', specs, **values) + + def value_format(self, specs=None, **values): + return self._redim('value_format', specs, **values) + + def range(self, specs=None, **values): + return self._redim('range', specs, **values) + + def label(self, specs=None, **values): + for k, v in values.items(): + dim = self._obj.get_dimension(k) + if dim and dim.name != dim.label and dim.label != v: + raise ValueError('Cannot override an existing Dimension label') + return self._redim('label', specs, **values) + + def soft_range(self, specs=None, **values): + return self._redim('soft_range', specs, **values) + + def type(self, specs=None, **values): + return self._redim('type', specs, **values) + + def step(self, specs=None, **values): + return self._redim('step', specs, **values) + + def default(self, specs=None, **values): + return self._redim('default', specs, **values) + + def unit(self, specs=None, **values): + return self._redim('unit', specs, **values) + + def values(self, specs=None, **ranges): + return self._redim('values', specs, **ranges) + + + +class Opts(object): + + def __init__(self, obj, mode=None): + self._mode = mode + self._obj = obj + + + def get(self, group=None, backend=None): + """Returns the corresponding Options object. + + Args: + group: The options group. Flattens across groups if None. + backend: Current backend if None otherwise chosen backend. + + Returns: + Options object associated with the object containing the + applied option keywords. + """ + keywords = {} + groups = Options._option_groups if group is None else [group] + backend = backend if backend else Store.current_backend + for group in groups: + optsobj = Store.lookup_options(backend, self._obj, group) + keywords = dict(keywords, **optsobj.kwargs) + return Options(**keywords) + + + def __call__(self, *args, **kwargs): + """Applies nested options definition. + + Applies options on an object or nested group of objects in a + flat format. Unlike the .options method, .opts modifies the + options in place by default. If the options are to be set + directly on the object a simple format may be used, e.g.: + + obj.opts(cmap='viridis', show_title=False) + + If the object is nested the options must be qualified using + a type[.group][.label] specification, e.g.: + + obj.opts('Image', cmap='viridis', show_title=False) + + or using: + + obj.opts({'Image': dict(cmap='viridis', show_title=False)}) + + Args: + *args: Sets of options to apply to object + Supports a number of formats including lists of Options + objects, a type[.group][.label] followed by a set of + keyword options to apply and a dictionary indexed by + type[.group][.label] specs. + backend (optional): Backend to apply options to + Defaults to current selected backend + clone (bool, optional): Whether to clone object + Options can be applied in place with clone=False + **kwargs: Keywords of options + Set of options to apply to the object + + For backwards compatibility, this method also supports the + option group semantics now offered by the hv.opts.apply_groups + utility. This usage will be deprecated and for more + information see the apply_options_type docstring. + + Returns: + Returns the object or a clone with the options applied + """ + if self._mode is None: + apply_groups, _, _ = util.deprecated_opts_signature(args, kwargs) + if apply_groups and util.config.future_deprecations: + msg = ("Calling the .opts method with options broken down by options " + "group (i.e. separate plot, style and norm groups) is deprecated. " + "Use the .options method converting to the simplified format " + "instead or use hv.opts.apply_groups for backward compatibility.") + param.main.warning(msg) + + return self._dispatch_opts( *args, **kwargs) + + def _dispatch_opts(self, *args, **kwargs): + if self._mode is None: + return self._base_opts(*args, **kwargs) + elif self._mode == 'holomap': + return self._holomap_opts(*args, **kwargs) + elif self._mode == 'dynamicmap': + return self._dynamicmap_opts(*args, **kwargs) + + def clear(self, clone=False): + """Clears any options applied to the object. + + Args: + clone: Whether to return a cleared clone or clear inplace + + Returns: + The object cleared of any options applied to it + """ + return self._obj.opts(clone=clone) + + def info(self, show_defaults=False): + """Prints a repr of the object including any applied options. + + Args: + show_defaults: Whether to include default options + """ + pprinter = PrettyPrinter(show_options=True, show_defaults=show_defaults) + print(pprinter.pprint(self._obj)) + + def _holomap_opts(self, *args, **kwargs): + clone = kwargs.pop('clone', None) + apply_groups, _, _ = util.deprecated_opts_signature(args, kwargs) + data = OrderedDict([(k, v.opts(*args, **kwargs)) + for k, v in self._obj.data.items()]) + + # By default do not clone in .opts method + if (apply_groups if clone is None else clone): + return self._obj.clone(data) + else: + self._obj.data = data + return self._obj + + def _dynamicmap_opts(self, *args, **kwargs): + from ..util import Dynamic + + clone = kwargs.get('clone', None) + apply_groups, _, _ = util.deprecated_opts_signature(args, kwargs) + # By default do not clone in .opts method + clone = (apply_groups if clone is None else clone) + + obj = self._obj if clone else self._obj.clone() + dmap = Dynamic(obj, operation=lambda obj, **dynkwargs: obj.opts(*args, **kwargs), + streams=self._obj.streams, link_inputs=True) + if not clone: + with util.disable_constant(self._obj): + obj.callback = self._obj.callback + self._obj.callback = dmap.callback + dmap = self._obj + dmap.data = OrderedDict([(k, v.opts(*args, **kwargs)) + for k, v in self._obj.data.items()]) + return dmap + + + def _base_opts(self, *args, **kwargs): + apply_groups, options, new_kwargs = util.deprecated_opts_signature(args, kwargs) + + # By default do not clone in .opts method + clone = kwargs.get('clone', None) + if apply_groups: + from ..util import opts + if options is not None: + kwargs['options'] = options + return opts.apply_groups(self._obj, **dict(kwargs, **new_kwargs)) + + kwargs['clone'] = False if clone is None else clone + return self._obj.options(*args, **kwargs) diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index 4e2fd668b2..a691686884 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -18,7 +18,8 @@ import numpy as np from . import util -from .options import Store, Opts, Options, cleanup_custom_options +from .accessors import Opts, Apply, redim +from .options import Store, Options, cleanup_custom_options from .pprint import PrettyPrinter from .tree import AttrTree from .util import basestring, OrderedDict, bytes_to_unicode, unicode @@ -126,158 +127,6 @@ def process_dimensions(kdims, vdims): return dimensions -class redim(object): - """ - Utility that supports re-dimensioning any HoloViews object via the - redim method. - """ - - def __init__(self, parent, mode=None): - self.parent = parent - # Can be 'dataset', 'dynamic' or None - self.mode = mode - - def __str__(self): - return "" - - @classmethod - def replace_dimensions(cls, dimensions, overrides): - """Replaces dimensions in list with dictionary of overrides. - - Args: - dimensions: List of dimensions - overrides: Dictionary of dimension specs indexed by name - - Returns: - list: List of dimensions with replacements applied - """ - replaced = [] - for d in dimensions: - if d.name in overrides: - override = overrides[d.name] - elif d.label in overrides: - override = overrides[d.label] - else: - override = None - - if override is None: - replaced.append(d) - elif isinstance(override, (basestring, tuple)): - replaced.append(d.clone(override)) - elif isinstance(override, Dimension): - replaced.append(override) - elif isinstance(override, dict): - replaced.append(d.clone(override.get('name',None), - **{k:v for k,v in override.items() if k != 'name'})) - else: - raise ValueError('Dimension can only be overridden ' - 'with another dimension or a dictionary ' - 'of attributes') - return replaced - - - def _filter_cache(self, dmap, kdims): - """ - Returns a filtered version of the DynamicMap cache leaving only - keys consistently with the newly specified values - """ - filtered = [] - for key, value in dmap.data.items(): - if not any(kd.values and v not in kd.values for kd, v in zip(kdims, key)): - filtered.append((key, value)) - return filtered - - - def __call__(self, specs=None, **dimensions): - """ - Replace dimensions on the dataset and allows renaming - dimensions in the dataset. Dimension mapping should map - between the old dimension name and a dictionary of the new - attributes, a completely new dimension or a new string name. - """ - parent = self.parent - redimmed = parent - if parent._deep_indexable and self.mode != 'dataset': - deep_mapped = [(k, v.redim(specs, **dimensions)) - for k, v in parent.items()] - redimmed = parent.clone(deep_mapped) - - if specs is not None: - if not isinstance(specs, list): - specs = [specs] - matches = any(parent.matches(spec) for spec in specs) - if self.mode != 'dynamic' and not matches: - return redimmed - - kdims = self.replace_dimensions(parent.kdims, dimensions) - vdims = self.replace_dimensions(parent.vdims, dimensions) - zipped_dims = zip(parent.kdims+parent.vdims, kdims+vdims) - renames = {pk.name: nk for pk, nk in zipped_dims if pk != nk} - - if self.mode == 'dataset': - data = parent.data - if renames: - data = parent.interface.redim(parent, renames) - clone = parent.clone(data, kdims=kdims, vdims=vdims) - if self.parent.dimensions(label='name') == clone.dimensions(label='name'): - # Ensure that plot_id is inherited as long as dimension - # name does not change - clone._plot_id = self.parent._plot_id - return clone - - if self.mode != 'dynamic': - return redimmed.clone(kdims=kdims, vdims=vdims) - - from ..util import Dynamic - def dynamic_redim(obj, **dynkwargs): - return obj.redim(specs, **dimensions) - dmap = Dynamic(parent, streams=parent.streams, operation=dynamic_redim) - dmap.data = OrderedDict(self._filter_cache(redimmed, kdims)) - with util.disable_constant(dmap): - dmap.kdims = kdims - dmap.vdims = vdims - return dmap - - - def _redim(self, name, specs, **dims): - dimensions = {k:{name:v} for k,v in dims.items()} - return self(specs, **dimensions) - - def cyclic(self, specs=None, **values): - return self._redim('cyclic', specs, **values) - - def value_format(self, specs=None, **values): - return self._redim('value_format', specs, **values) - - def range(self, specs=None, **values): - return self._redim('range', specs, **values) - - def label(self, specs=None, **values): - for k, v in values.items(): - dim = self.parent.get_dimension(k) - if dim and dim.name != dim.label and dim.label != v: - raise ValueError('Cannot override an existing Dimension label') - return self._redim('label', specs, **values) - - def soft_range(self, specs=None, **values): - return self._redim('soft_range', specs, **values) - - def type(self, specs=None, **values): - return self._redim('type', specs, **values) - - def step(self, specs=None, **values): - return self._redim('step', specs, **values) - - def default(self, specs=None, **values): - return self._redim('default', specs, **values) - - def unit(self, specs=None, **values): - return self._redim('unit', specs, **values) - - def values(self, specs=None, **ranges): - return self._redim('values', specs, **ranges) - - class Dimension(param.Parameterized): """ @@ -999,9 +848,11 @@ def __init__(self, data, kdims=None, vdims=None, **params): cdims = [(d.name, val) for d, val in self.cdims.items()] self._cached_constants = OrderedDict(cdims) self._settings = None - self.redim = redim(self) + # Instantiate accessors + self.apply = Apply(self) self.opts = Opts(self) + self.redim = redim(self) def _valid_dimensions(self, dimensions): @@ -1351,88 +1202,6 @@ def __call__(self, options=None, **kwargs): return self.opts(options, **kwargs) - def apply(self, function, streams=[], link_inputs=True, dynamic=None, **kwargs): - """Applies a function to all (Nd)Overlay or Element objects. - - Any keyword arguments are passed through to the function. If - keyword arguments are instance parameters, or streams are - supplied the returned object will dynamically update in - response to changes in those objects. - - Args: - function: A callable function - The function will be passed the return value of the - DynamicMap as the first argument and any supplied - stream values or keywords as additional keyword - arguments. - streams (list, optional): A list of Stream objects - The Stream objects can dynamically supply values which - will be passed to the function as keywords. - link_inputs (bool, optional): Whether to link the inputs - Determines whether Streams and Links attached to - original object will be inherited. - dynamic (bool, optional): Whether to make object dynamic - By default object is made dynamic if streams are - supplied, an instance parameter is supplied as a - keyword argument, or the supplied function is a - parameterized method. - kwargs (dict, optional): Additional keyword arguments - Keyword arguments which will be supplied to the - function. - - Returns: - A new object where the function was applied to all - contained (Nd)Overlay or Element objects. - """ - from .spaces import DynamicMap - from ..util import Dynamic - - if isinstance(function, basestring): - method_name = function - def function(object, **kwargs): - method = getattr(object, method_name, None) - if method is None: - raise AttributeError('Applied method %s does not exist.' - 'When declaring a method to apply ' - 'as a string ensure a corresponding ' - 'method exists on the object.' % - method_name) - return method(**kwargs) - - applies = isinstance(self, (ViewableElement, DynamicMap)) - params = {p: val for p, val in kwargs.items() - if isinstance(val, param.Parameter) - and isinstance(val.owner, param.Parameterized)} - param_methods = {p: val for p, val in kwargs.items() - if util.is_param_method(val, has_deps=True)} - - if dynamic is None: - dynamic = (bool(streams) or isinstance(self, DynamicMap) or - util.is_param_method(function, has_deps=True) or - params or param_methods) - - if applies and dynamic: - return Dynamic(self, operation=function, streams=streams, - kwargs=kwargs, link_inputs=link_inputs) - elif applies: - inner_kwargs = dict(kwargs) - for k, v in kwargs.items(): - if util.is_param_method(v, has_deps=True): - inner_kwargs[k] = v() - elif k in params: - inner_kwargs[k] = getattr(v.owner, v.name) - if hasattr(function, 'dynamic'): - inner_kwargs['dynamic'] = False - return function(self, **inner_kwargs) - elif self._deep_indexable: - mapped = OrderedDict() - for k, v in self.data.items(): - new_val = v.apply(function, streams, link_inputs, dynamic, **kwargs) - if new_val is not None: - mapped[k] = new_val - return self.clone(mapped, link=link_inputs) - - def options(self, *args, **kwargs): """Applies simplified option definition returning a new object. diff --git a/holoviews/core/options.py b/holoviews/core/options.py index 70cedb77e1..2998b2b65b 100644 --- a/holoviews/core/options.py +++ b/holoviews/core/options.py @@ -43,9 +43,8 @@ import param from .tree import AttrTree -from .util import sanitize_identifier, group_sanitizer,label_sanitizer, basestring, OrderedDict, config -from .util import deprecated_opts_signature, disable_constant -from .pprint import InfoPrinter, PrettyPrinter +from .util import sanitize_identifier, group_sanitizer,label_sanitizer, basestring, OrderedDict +from .pprint import InfoPrinter def cleanup_custom_options(id, weakref=None): @@ -91,160 +90,6 @@ def __init__(self, message="", warn=True): super(SkipRendering, self).__init__(message) -class Opts(object): - - def __init__(self, obj, mode=None): - self._mode = mode - self._obj = obj - - - def get(self, group=None, backend=None): - """Returns the corresponding Options object. - - Args: - group: The options group. Flattens across groups if None. - backend: Current backend if None otherwise chosen backend. - - Returns: - Options object associated with the object containing the - applied option keywords. - """ - keywords = {} - groups = Options._option_groups if group is None else [group] - backend = backend if backend else Store.current_backend - for group in groups: - optsobj = Store.lookup_options(backend, self._obj, group) - keywords = dict(keywords, **optsobj.kwargs) - return Options(**keywords) - - - def __call__(self, *args, **kwargs): - """Applies nested options definition. - - Applies options on an object or nested group of objects in a - flat format. Unlike the .options method, .opts modifies the - options in place by default. If the options are to be set - directly on the object a simple format may be used, e.g.: - - obj.opts(cmap='viridis', show_title=False) - - If the object is nested the options must be qualified using - a type[.group][.label] specification, e.g.: - - obj.opts('Image', cmap='viridis', show_title=False) - - or using: - - obj.opts({'Image': dict(cmap='viridis', show_title=False)}) - - Args: - *args: Sets of options to apply to object - Supports a number of formats including lists of Options - objects, a type[.group][.label] followed by a set of - keyword options to apply and a dictionary indexed by - type[.group][.label] specs. - backend (optional): Backend to apply options to - Defaults to current selected backend - clone (bool, optional): Whether to clone object - Options can be applied in place with clone=False - **kwargs: Keywords of options - Set of options to apply to the object - - For backwards compatibility, this method also supports the - option group semantics now offered by the hv.opts.apply_groups - utility. This usage will be deprecated and for more - information see the apply_options_type docstring. - - Returns: - Returns the object or a clone with the options applied - """ - if self._mode is None: - apply_groups, _, _ = deprecated_opts_signature(args, kwargs) - if apply_groups and config.future_deprecations: - msg = ("Calling the .opts method with options broken down by options " - "group (i.e. separate plot, style and norm groups) is deprecated. " - "Use the .options method converting to the simplified format " - "instead or use hv.opts.apply_groups for backward compatibility.") - param.main.warning(msg) - - return self._dispatch_opts( *args, **kwargs) - - def _dispatch_opts(self, *args, **kwargs): - if self._mode is None: - return self._base_opts(*args, **kwargs) - elif self._mode == 'holomap': - return self._holomap_opts(*args, **kwargs) - elif self._mode == 'dynamicmap': - return self._dynamicmap_opts(*args, **kwargs) - - def clear(self, clone=False): - """Clears any options applied to the object. - - Args: - clone: Whether to return a cleared clone or clear inplace - - Returns: - The object cleared of any options applied to it - """ - return self._obj.opts(clone=clone) - - def info(self, show_defaults=False): - """Prints a repr of the object including any applied options. - - Args: - show_defaults: Whether to include default options - """ - pprinter = PrettyPrinter(show_options=True, show_defaults=show_defaults) - print(pprinter.pprint(self._obj)) - - def _holomap_opts(self, *args, **kwargs): - clone = kwargs.pop('clone', None) - apply_groups, _, _ = deprecated_opts_signature(args, kwargs) - data = OrderedDict([(k, v.opts(*args, **kwargs)) - for k, v in self._obj.data.items()]) - - # By default do not clone in .opts method - if (apply_groups if clone is None else clone): - return self._obj.clone(data) - else: - self._obj.data = data - return self._obj - - def _dynamicmap_opts(self, *args, **kwargs): - from ..util import Dynamic - - clone = kwargs.get('clone', None) - apply_groups, _, _ = deprecated_opts_signature(args, kwargs) - # By default do not clone in .opts method - clone = (apply_groups if clone is None else clone) - - obj = self._obj if clone else self._obj.clone() - dmap = Dynamic(obj, operation=lambda obj, **dynkwargs: obj.opts(*args, **kwargs), - streams=self._obj.streams, link_inputs=True) - if not clone: - with disable_constant(self._obj): - obj.callback = self._obj.callback - self._obj.callback = dmap.callback - dmap = self._obj - dmap.data = OrderedDict([(k, v.opts(*args, **kwargs)) - for k, v in self._obj.data.items()]) - return dmap - - - def _base_opts(self, *args, **kwargs): - apply_groups, options, new_kwargs = deprecated_opts_signature(args, kwargs) - - # By default do not clone in .opts method - clone = kwargs.get('clone', None) - if apply_groups: - from ..util import opts - if options is not None: - kwargs['options'] = options - return opts.apply_groups(self._obj, **dict(kwargs, **new_kwargs)) - - kwargs['clone'] = False if clone is None else clone - return self._obj.options(*args, **kwargs) - class OptionError(Exception): """ diff --git a/holoviews/core/spaces.py b/holoviews/core/spaces.py index b2b065c077..697f95ae3f 100644 --- a/holoviews/core/spaces.py +++ b/holoviews/core/spaces.py @@ -12,11 +12,12 @@ import param from . import traversal, util +from .accessors import Opts from .dimension import OrderedDict, Dimension, ViewableElement, redim from .layout import Layout, AdjointLayout, NdLayout, Empty from .ndmapping import UniformNdMapping, NdMapping, item_check from .overlay import Overlay, CompositeOverlay, NdOverlay, Overlayable -from .options import Store, StoreOptions, Opts +from .options import Store, StoreOptions from ..streams import Stream @@ -427,6 +428,11 @@ def sample(self, samples=[], bounds=None, **sample_values): Returns: A Table containing the sampled coordinates """ + if util.config.future_deprecations: + self.param.warning('The HoloMap.sample method is deprecated, ' + 'for equivalent functionality use ' + 'HoloMap.apply.sample().collapse().') + dims = self.last.ndims if isinstance(samples, tuple) or np.isscalar(samples): if dims == 1: @@ -491,6 +497,11 @@ def reduce(self, dimensions=None, function=None, spread_fn=None, **reduce_map): Returns: The Dataset after reductions have been applied. """ + if util.config.future_deprecations: + self.param.warning('The HoloMap.reduce method is deprecated, ' + 'for equivalent functionality use ' + 'HoloMap.apply.reduce().collapse().') + from ..element import Table reduced_items = [(k, v.reduce(dimensions, function, spread_fn, **reduce_map)) for k, v in self.items()] From 641605bbfc4157105e9f52cef50b9429331b6d97 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 22 Mar 2019 01:32:15 +0000 Subject: [PATCH 3/9] Fixed DynamicMap.apply --- holoviews/core/accessors.py | 12 +++++++++- holoviews/core/spaces.py | 45 ------------------------------------- 2 files changed, 11 insertions(+), 46 deletions(-) diff --git a/holoviews/core/accessors.py b/holoviews/core/accessors.py index 056d0637c3..a3b03a0f21 100644 --- a/holoviews/core/accessors.py +++ b/holoviews/core/accessors.py @@ -55,9 +55,19 @@ def __call__(self, function, streams=[], link_inputs=True, dynamic=None, **kwarg contained (Nd)Overlay or Element objects. """ from .dimension import ViewableElement - from .spaces import DynamicMap + from .spaces import HoloMap, DynamicMap from ..util import Dynamic + if isinstance(self._obj, DynamicMap) and dynamic == False: + samples = tuple(d.values for d in self._obj.kdims) + if not all(samples): + raise ValueError('Applying a function to a DynamicMap ' + 'and setting dynamic=False is only ' + 'possible if key dimensions define ' + 'a discrete parameter space.') + return HoloMap(self._obj[samples]).apply( + function, streams, link_inputs, dynamic, **kwargs) + if isinstance(function, util.basestring): args = kwargs.pop('_args', ()) method_name = function diff --git a/holoviews/core/spaces.py b/holoviews/core/spaces.py index 697f95ae3f..e43fe9781e 100644 --- a/holoviews/core/spaces.py +++ b/holoviews/core/spaces.py @@ -1399,51 +1399,6 @@ def _cache(self, key, val): self[key] = val - def apply(self, function, streams=[], link_inputs=True, dynamic=None, **kwargs): - """Applies a function to all (Nd)Overlay or Element objects. - - Any keyword arguments are passed through to the function. If - keyword arguments are instance parameters, or streams are - supplied the returned object will dynamically update in - response to changes in those objects. - - Args: - function: A callable function - The function will be passed the return value of the - DynamicMap as the first argument and any supplied - stream values or keywords as additional keyword - arguments. - streams (list, optional): A list of Stream objects - The Stream objects can dynamically supply values which - will be passed to the function as keywords. - link_inputs (bool, optional): Whether to link the inputs - Determines whether Streams and Links attached to - original object will be inherited. - dynamic (bool, optional): Whether to make object dynamic - By default object is made dynamic if streams are - supplied, an instance parameter is supplied as a - keyword argument, or the supplied function is a - parameterized method. - kwargs (dict, optional): Additional keyword arguments - Keyword arguments which will be supplied to the - function. - - Returns: - A new object where the function was applied to all - contained (Nd)Overlay or Element objects. - """ - if dynamic == False: - samples = tuple(d.values for d in self.kdims) - if not all(samples): - raise ValueError('Applying a function to a DynamicMap ' - 'and setting dynamic=False is only ' - 'possible if key dimensions define ' - 'a discrete parameter space.') - return HoloMap(self[samples]).apply( - function, streams, link_inputs, dynamic, **kwargs) - return super(DynamicMap, self).apply(function, streams, link_inputs, dynamic, **kwargs) - - def map(self, map_fn, specs=None, clone=True, link_inputs=True): """Map a function to all objects matching the specs From dcb9637c053713d247d8fdba676ab9d8b4a21fb9 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 22 Mar 2019 01:33:32 +0000 Subject: [PATCH 4/9] Fixed Graph.redim --- holoviews/element/graphs.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/holoviews/element/graphs.py b/holoviews/element/graphs.py index 0deece756d..4ae6f4ddf9 100644 --- a/holoviews/element/graphs.py +++ b/holoviews/element/graphs.py @@ -5,7 +5,7 @@ import numpy as np from ..core import Dimension, Dataset, Element2D -from ..core.dimension import redim +from ..core.accessors import redim from ..core.util import max_range, search_indices from ..core.operation import Operation from .chart import Points @@ -23,10 +23,10 @@ class redim_graph(redim): def __call__(self, specs=None, **dimensions): redimmed = super(redim_graph, self).__call__(specs, **dimensions) new_data = (redimmed.data,) - if self.parent.nodes: - new_data = new_data + (self.parent.nodes.redim(specs, **dimensions),) - if self.parent._edgepaths: - new_data = new_data + (self.parent.edgepaths.redim(specs, **dimensions),) + if self._obj.nodes: + new_data = new_data + (self._obj.nodes.redim(specs, **dimensions),) + if self._obj._edgepaths: + new_data = new_data + (self._obj.edgepaths.redim(specs, **dimensions),) return redimmed.clone(new_data) From ec52102fec76b4abd04de5e9b374c5c7a55a0061 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 22 Mar 2019 01:51:24 +0000 Subject: [PATCH 5/9] Inline imports for pickle compatibility --- holoviews/core/accessors.py | 2 +- holoviews/core/options.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/holoviews/core/accessors.py b/holoviews/core/accessors.py index a3b03a0f21..b6d6dcb5f0 100644 --- a/holoviews/core/accessors.py +++ b/holoviews/core/accessors.py @@ -9,7 +9,6 @@ from . import util from .pprint import PrettyPrinter -from .options import Options, Store class Apply(object): @@ -326,6 +325,7 @@ def get(self, group=None, backend=None): Options object associated with the object containing the applied option keywords. """ + from .options import Store, Options keywords = {} groups = Options._option_groups if group is None else [group] backend = backend if backend else Store.current_backend diff --git a/holoviews/core/options.py b/holoviews/core/options.py index 2998b2b65b..528e91b56a 100644 --- a/holoviews/core/options.py +++ b/holoviews/core/options.py @@ -42,6 +42,7 @@ import numpy as np import param +from .accessors import Opts # noqa (clean up in 2.0) from .tree import AttrTree from .util import sanitize_identifier, group_sanitizer,label_sanitizer, basestring, OrderedDict from .pprint import InfoPrinter From 8b19c783e220b19f2284db096fd12af6f9c01a14 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 22 Mar 2019 15:06:15 +0000 Subject: [PATCH 6/9] Renamed _args to _method_args --- holoviews/core/accessors.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/holoviews/core/accessors.py b/holoviews/core/accessors.py index b6d6dcb5f0..adc441586e 100644 --- a/holoviews/core/accessors.py +++ b/holoviews/core/accessors.py @@ -68,7 +68,7 @@ def __call__(self, function, streams=[], link_inputs=True, dynamic=None, **kwarg function, streams, link_inputs, dynamic, **kwargs) if isinstance(function, util.basestring): - args = kwargs.pop('_args', ()) + args = kwargs.pop('_method_args', ()) method_name = function def function(object, **kwargs): method = getattr(object, method_name, None) @@ -121,7 +121,7 @@ def aggregate(self, dimensions=None, function=None, spreadfn=None, **kwargs): See :py:meth:`Dimensioned.opts` and :py:meth:`Apply.__call__` for more information. """ - kwargs['_args'] = (dimensions, function, spreadfn) + kwargs['_method_args'] = (dimensions, function, spreadfn) return self.__call__('aggregate', **kwargs) def opts(self, *args, **kwargs): @@ -130,7 +130,7 @@ def opts(self, *args, **kwargs): See :py:meth:`Dimensioned.opts` and :py:meth:`Apply.__call__` for more information. """ - kwargs['_args'] = args + kwargs['_method_args'] = args return self.__call__('opts', **kwargs) def reduce(self, dimensions=[], function=None, spreadfn=None, **kwargs): @@ -139,7 +139,7 @@ def reduce(self, dimensions=[], function=None, spreadfn=None, **kwargs): See :py:meth:`Dimensioned.opts` and :py:meth:`Apply.__call__` for more information. """ - kwargs['_args'] = (dimensions, function, spreadfn) + kwargs['_method_args'] = (dimensions, function, spreadfn) return self.__call__('reduce', **kwargs) def select(self, **kwargs): From e53881272aaa69742ad30714cae6c280b6c49ff1 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 22 Mar 2019 15:22:10 +0000 Subject: [PATCH 7/9] Rename redim to Redim --- holoviews/core/accessors.py | 4 +++- holoviews/core/dimension.py | 4 ++-- holoviews/core/spaces.py | 6 +++--- holoviews/element/graphs.py | 11 ++++++----- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/holoviews/core/accessors.py b/holoviews/core/accessors.py index adc441586e..59c84bce32 100644 --- a/holoviews/core/accessors.py +++ b/holoviews/core/accessors.py @@ -152,7 +152,7 @@ def select(self, **kwargs): -class redim(object): +class Redim(object): """ Utility that supports re-dimensioning any HoloViews object via the redim method. @@ -461,3 +461,5 @@ def _base_opts(self, *args, **kwargs): kwargs['clone'] = False if clone is None else clone return self._obj.options(*args, **kwargs) + +redim = Redim # pickle compatibility - remove in 2.0 diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index a691686884..46a0b3b3d4 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -18,7 +18,7 @@ import numpy as np from . import util -from .accessors import Opts, Apply, redim +from .accessors import Opts, Apply, Redim from .options import Store, Options, cleanup_custom_options from .pprint import PrettyPrinter from .tree import AttrTree @@ -852,7 +852,7 @@ def __init__(self, data, kdims=None, vdims=None, **params): # Instantiate accessors self.apply = Apply(self) self.opts = Opts(self) - self.redim = redim(self) + self.redim = Redim(self) def _valid_dimensions(self, dimensions): diff --git a/holoviews/core/spaces.py b/holoviews/core/spaces.py index e43fe9781e..af105b32fb 100644 --- a/holoviews/core/spaces.py +++ b/holoviews/core/spaces.py @@ -12,8 +12,8 @@ import param from . import traversal, util -from .accessors import Opts -from .dimension import OrderedDict, Dimension, ViewableElement, redim +from .accessors import Opts, Redim +from .dimension import OrderedDict, Dimension, ViewableElement from .layout import Layout, AdjointLayout, NdLayout, Empty from .ndmapping import UniformNdMapping, NdMapping, item_check from .overlay import Overlay, CompositeOverlay, NdOverlay, Overlayable @@ -955,7 +955,7 @@ def __init__(self, callback, initial_items=None, streams=None, **params): for stream in self.streams: if stream.source is None: stream.source = self - self.redim = redim(self, mode='dynamic') + self.redim = Redim(self, mode='dynamic') self.periodic = periodic(self) @property diff --git a/holoviews/element/graphs.py b/holoviews/element/graphs.py index 4ae6f4ddf9..4508c19323 100644 --- a/holoviews/element/graphs.py +++ b/holoviews/element/graphs.py @@ -5,7 +5,7 @@ import numpy as np from ..core import Dimension, Dataset, Element2D -from ..core.accessors import redim +from ..core.accessors import Redim from ..core.util import max_range, search_indices from ..core.operation import Operation from .chart import Points @@ -14,14 +14,14 @@ connect_edges_pd, quadratic_bezier) -class redim_graph(redim): +class RedimGraph(Redim): """ Extension for the redim utility that allows re-dimensioning Graph objects including their nodes and edgepaths. """ def __call__(self, specs=None, **dimensions): - redimmed = super(redim_graph, self).__call__(specs, **dimensions) + redimmed = super(Redim, self).__call__(specs, **dimensions) new_data = (redimmed.data,) if self._obj.nodes: new_data = new_data + (self._obj.nodes.redim(specs, **dimensions),) @@ -29,6 +29,7 @@ def __call__(self, specs=None, **dimensions): new_data = new_data + (self._obj.edgepaths.redim(specs, **dimensions),) return redimmed.clone(new_data) +redim_graph = RedimGraph # pickle compatibility - remove in 2.0 class layout_nodes(Operation): @@ -154,7 +155,7 @@ def __init__(self, data, kdims=None, vdims=None, **params): if node_info is not None: self._add_node_info(node_info) self._validate() - self.redim = redim_graph(self, mode='dataset') + self.redim = RedimGraph(self, mode='dataset') def _add_node_info(self, node_info): @@ -780,7 +781,7 @@ def __init__(self, data, kdims=None, vdims=None, compute=True, **params): % type(edgepaths)) self._edgepaths = edgepaths self._validate() - self.redim = redim_graph(self, mode='dataset') + self.redim = RedimGraph(self, mode='dataset') @property From ff3ee9d515c5179faf9375d90fb7afd8ca1acae2 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 22 Mar 2019 15:50:10 +0000 Subject: [PATCH 8/9] Fixed import --- holoviews/core/data/__init__.py | 5 +++-- holoviews/element/graphs.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/holoviews/core/data/__init__.py b/holoviews/core/data/__init__.py index adb7b0d57a..41055f35bb 100644 --- a/holoviews/core/data/__init__.py +++ b/holoviews/core/data/__init__.py @@ -9,7 +9,8 @@ import param from .. import util -from ..dimension import redim, Dimension, process_dimensions +from ..accessors import Redim +from ..dimension import Dimension, process_dimensions from ..element import Element from ..ndmapping import OrderedDict from ..spaces import HoloMap, DynamicMap @@ -212,7 +213,7 @@ def __init__(self, data, kdims=None, vdims=None, **kwargs): super(Dataset, self).__init__(data, **dict(kwargs, **dict(dims, **extra_kws))) self.interface.validate(self, validate_vdims) - self.redim = redim(self, mode='dataset') + self.redim = Redim(self, mode='dataset') def closest(self, coords=[], **kwargs): diff --git a/holoviews/element/graphs.py b/holoviews/element/graphs.py index 4508c19323..0424a63031 100644 --- a/holoviews/element/graphs.py +++ b/holoviews/element/graphs.py @@ -21,7 +21,7 @@ class RedimGraph(Redim): """ def __call__(self, specs=None, **dimensions): - redimmed = super(Redim, self).__call__(specs, **dimensions) + redimmed = super(RedimGraph, self).__call__(specs, **dimensions) new_data = (redimmed.data,) if self._obj.nodes: new_data = new_data + (self._obj.nodes.redim(specs, **dimensions),) From 9a7bfbd0f4e3724649db80befc3cb9abe675932f Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 22 Mar 2019 16:55:48 +0000 Subject: [PATCH 9/9] Moved redim alias to dimension.py --- holoviews/core/accessors.py | 2 -- holoviews/core/dimension.py | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/holoviews/core/accessors.py b/holoviews/core/accessors.py index 59c84bce32..93dd17670c 100644 --- a/holoviews/core/accessors.py +++ b/holoviews/core/accessors.py @@ -461,5 +461,3 @@ def _base_opts(self, *args, **kwargs): kwargs['clone'] = False if clone is None else clone return self._obj.options(*args, **kwargs) - -redim = Redim # pickle compatibility - remove in 2.0 diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index 46a0b3b3d4..d323a7f98c 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -31,6 +31,8 @@ title_format = "{name}: {val}{unit}" +redim = Redim # pickle compatibility - remove in 2.0 + def param_aliases(d): """ Called from __setstate__ in LabelledData in order to load