Skip to content

Commit

Permalink
Merge 4ca38fe into 1b63552
Browse files Browse the repository at this point in the history
  • Loading branch information
jlstevens committed May 8, 2017
2 parents 1b63552 + 4ca38fe commit 838e541
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 86 deletions.
2 changes: 1 addition & 1 deletion holoviews/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
GridSpace, GridMatrix)

from .interface import * # noqa (API import)
from .operation import ElementOperation, MapOperation, TreeOperation # noqa (API import)
from .operation import ElementOperation, TreeOperation # noqa (API import)
from .element import * # noqa (API import)
from .element import __all__ as elements_list
from . import util # noqa (API import)
Expand Down
160 changes: 89 additions & 71 deletions holoviews/core/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,55 @@

class Operation(param.ParameterizedFunction):
"""
Base class for all Operation types.
An ElementOperation process an Element or HoloMap at the level of
individual elements or overlays. If a holomap is passed in as
input, a processed holomap is returned as output where the
individual elements have been transformed accordingly. An
ElementOperation may turn overlays in new elements or vice versa.
An ElementOperation can be set to be dynamic, which will return a
DynamicMap with a callback that will apply the operation
dynamically. An ElementOperation may also supply a list of Stream
classes on a streams parameter, which can allow dynamic control
over the parameters on the operation.
"""

group = param.String(default='Operation', doc="""
The group string used to identify the output of the
Operation. By default this should match the operation name.""")


dynamic = param.ObjectSelector(default='default',
objects=['default', True, False], doc="""
Whether the operation should be applied dynamically when a
specific frame is requested, specified as a Boolean. If set to
'default' the mode will be determined based on the input type,
i.e. if the data is a DynamicMap it will stay dynamic.""")

input_ranges = param.ClassSelector(default={},
class_=(dict, tuple), doc="""
Ranges to be used for input normalization (if applicable) in a
format appropriate for the Normalization.ranges parameter.
By default, no normalization is applied. If key-wise
normalization is required, a 2-tuple may be supplied where the
first component is a Normalization.ranges list and the second
component is Normalization.keys. """)

link_inputs = param.Boolean(default=False, doc="""
If the operation is dynamic, whether or not linked streams
should be transferred from the operation inputs for backends
that support linked streams.
For example if an operation is applied to a DynamicMap with an
RangeXY, this switch determines whether the corresponding
visualization should update this stream with range changes
originating from the newly generated axes.""")

streams = param.List(default=[], doc="""
List of streams that are applied if dynamic=True, allowing
for dynamic interaction with the plot.""")

@classmethod
def search(cls, element, pattern):
"""
Expand Down Expand Up @@ -69,53 +110,6 @@ def get_overlay_bounds(cls, overlay):
raise ValueError("Extents across the overlay are inconsistent")



class ElementOperation(Operation):
"""
An ElementOperation process an Element or HoloMap at the level of
individual elements or overlays. If a holomap is passed in as
input, a processed holomap is returned as output where the
individual elements have been transformed accordingly. An
ElementOperation may turn overlays in new elements or vice versa.
An ElementOperation can be set to be dynamic, which will return a
DynamicMap with a callback that will apply the operation
dynamically. An ElementOperation may also supply a list of Stream
classes on a streams parameter, which can allow dynamic control
over the parameters on the operation.
"""

dynamic = param.ObjectSelector(default='default',
objects=['default', True, False], doc="""
Whether the operation should be applied dynamically when a
specific frame is requested, specified as a Boolean. If set to
'default' the mode will be determined based on the input type,
i.e. if the data is a DynamicMap it will stay dynamic.""")

input_ranges = param.ClassSelector(default={},
class_=(dict, tuple), doc="""
Ranges to be used for input normalization (if applicable) in a
format appropriate for the Normalization.ranges parameter.
By default, no normalization is applied. If key-wise
normalization is required, a 2-tuple may be supplied where the
first component is a Normalization.ranges list and the second
component is Normalization.keys. """)

link_inputs = param.Boolean(default=False, doc="""
If the operation is dynamic, whether or not linked streams
should be transferred from the operation inputs for backends
that support linked streams.
For example if an operation is applied to a DynamicMap with an
RangeXY, this switch determines whether the corresponding
visualization should update this stream with range changes
originating from the newly generated axes.""")

streams = param.List(default=[], doc="""
List of streams that are applied if dynamic=True, allowing
for dynamic interaction with the plot.""")

def _process(self, view, key=None):
"""
Process a single input element and outputs new single element
Expand Down Expand Up @@ -169,6 +163,13 @@ def __call__(self, element, **params):
return processed


class ElementOperation(Operation):

def __init__(self, *args, **kwargs):
self.warning('ElementOperation has been deprecated and renamed to Operation.')
super(ElementOperation, self).__init__(*args, **kwargs)


class OperationCallable(Callable):
"""
OperationCallable allows wrapping an ElementOperation and the
Expand All @@ -185,42 +186,59 @@ def __init__(self, callable, **kwargs):
super(OperationCallable, self).__init__(callable, **kwargs)


class MapOperation(param.ParameterizedFunction):

class TreeOperation(Operation):
"""
A MapOperation takes a HoloMap containing elements or overlays and
processes them at the HoloMap level, returning arbitrary new
HoloMap objects as output. Unlike ElementOperation, MapOperations
can compute over all the keys and dimensions of the input map.
A TreeOperation is the most general Operation type; it accepts any
HoloViews datastructure and outputs a Layout containing one or
more elements.
"""

group = param.String(default='MapOperation', doc="""
The group string to identify the output of the MapOperation.
By default this will match the MapOperation name.""")
group = param.String(default='Operation', doc="""
The group string used to identify the output of the
Operation. By default this should match the operation name.""")

def __call__(self, vmap, **params):
self.p = param.ParamOverrides(self, params)

if not isinstance(vmap, HoloMap):
raise Exception('MapOperation can only process Maps.')
@classmethod
def search(cls, element, pattern):
"""
Helper method that returns a list of elements that match the
given path pattern of form {type}.{group}.{label}.
return self._process(vmap)
The input may be a Layout, an Overlay type or a single
Element.
"""
if isinstance(element, Layout):
return [el for cell in element for el in cls.search(cell, pattern)]
if isinstance(element, (NdOverlay, Overlay)):
return [el for el in element if el.matches(pattern)]
elif isinstance(element, Element):
return [element] if element.matches(pattern) else []


def _process(self, view):
@classmethod
def get_overlay_label(cls, overlay, default_label=''):
"""
Process a single input HoloMap, returning a new HoloMap
instance.
Returns a label if all the elements of an overlay agree on a
consistent label, otherwise returns the default label.
"""
raise NotImplementedError
if all(el.label==overlay.get(0).label for el in overlay):
return overlay.get(0).label
else:
return default_label


@classmethod
def get_overlay_bounds(cls, overlay):
"""
Returns the extents if all the elements of an overlay agree on
a consistent extents, otherwise raises an exception.
"""
if all(el.bounds==overlay.get(0).bounds for el in overlay):
return overlay.get(0).bounds
else:
raise ValueError("Extents across the overlay are inconsistent")

class TreeOperation(Operation):
"""
A TreeOperation is the most general Operation type; it accepts any
HoloViews datastructure and outputs a Layout containing one or
more elements.
"""

def process_element(self, element, key, **params):
"""
Expand Down
20 changes: 8 additions & 12 deletions holoviews/core/spaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,6 @@ def collapse(self, dimensions=None, function=None, spreadfn=None, **kwargs):
on the HoloMap. Homogenous Elements may be collapsed by
supplying a function, inhomogenous elements are merged.
"""
from .operation import MapOperation
if not dimensions:
dimensions = self.kdims
if not isinstance(dimensions, list): dimensions = [dimensions]
Expand All @@ -273,18 +272,15 @@ def collapse(self, dimensions=None, function=None, spreadfn=None, **kwargs):

collapsed = groups.clone(shared_data=False)
for key, group in groups.items():
if isinstance(function, MapOperation):
collapsed[key] = function(group, **kwargs)
else:
group_data = [el.data for el in group]
args = (group_data, function, group.last.kdims)
if hasattr(group.last, 'interface'):
col_data = group.type(group.table().aggregate(group.last.kdims, function, spreadfn, **kwargs))
group_data = [el.data for el in group]
args = (group_data, function, group.last.kdims)
if hasattr(group.last, 'interface'):
col_data = group.type(group.table().aggregate(group.last.kdims, function, spreadfn, **kwargs))

else:
data = group.type.collapse_data(*args, **kwargs)
col_data = group.last.clone(data)
collapsed[key] = col_data
else:
data = group.type.collapse_data(*args, **kwargs)
col_data = group.last.clone(data)
collapsed[key] = col_data
return collapsed if self.ndims > 1 else collapsed.last


Expand Down
4 changes: 2 additions & 2 deletions holoviews/operation/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from ..core.operation import ElementOperation, MapOperation, TreeOperation # noqa (API import)
from ..core.operation import ElementOperation, TreeOperation # noqa (API import)
from ..core.options import Compositor

from .element import * # noqa (API import)
from ..core import Overlay # noqa (API import)

def public(obj):
if not isinstance(obj, type): return False
baseclasses = [ElementOperation, MapOperation, TreeOperation]
baseclasses = [ElementOperation, TreeOperation]
return any([issubclass(obj, bc) for bc in baseclasses])


Expand Down

0 comments on commit 838e541

Please sign in to comment.