From 15200e4cdd29092b950583596be1975a5283ef87 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 1 Dec 2015 17:15:49 +0000 Subject: [PATCH 01/15] Dimension.format_string now replaced with dimension.title_format --- holoviews/core/dimension.py | 14 +++++--------- holoviews/element/comparison.py | 3 --- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index e5121795f9..c9a62cd35f 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -25,6 +25,8 @@ ALIASES = {'key_dimensions': 'kdims', 'value_dimensions': 'vdims', 'constant_dimensions': 'cdims'} +title_format = "{name}: {val}{unit}" + def param_aliases(d): """ Called from __setstate__ in LabelledData in order to load @@ -89,12 +91,6 @@ class Dimension(param.Parameterized): be used to retain a categorical ordering. Setting values to 'initial' indicates that the values will be added during construction.""") - format_string = param.String(default="{name}: {val}{unit}", doc=""" - Format string to specify how pprint_value_string is generated. Valid - format keys include: 'name' (Dimension name), 'val' (a - particular dimension value to be presented) and 'unit' (the - unit string).""") - # Defines default formatting by type type_formatters = {} unit_format = ' ({unit})' @@ -150,13 +146,13 @@ def __repr__(self): def pprint_value_string(self, value): """ - Pretty prints the dimension name and value using the - format_string parameter, including the unit string (if + Pretty prints the dimension name and value using the global + title_format variable, including the unit string (if set). Numeric types are printed to the stated rounding level. """ unit = '' if self.unit is None else ' ' + self.unit value = self.pprint_value(value) - return self.format_string.format(name=self.name, val=value, unit=unit) + return title_format.format(name=self.name, val=value, unit=unit) def __hash__(self): diff --git a/holoviews/element/comparison.py b/holoviews/element/comparison.py index 9f05ae81ba..a1703d1280 100644 --- a/holoviews/element/comparison.py +++ b/holoviews/element/comparison.py @@ -248,9 +248,6 @@ def compare_dimensions(cls, dim1, dim2, msg=None): if dim1.values != dim2.values: raise cls.failureException("Dimension value declarations mismatched: %s != %s" % (dim1.values , dim2.values)) - if dim1.format_string != dim2.format_string: - raise cls.failureException("Dimension format string declarations mismatched: %s != %s" - % (dim1.format_string , dim2.format_string)) @classmethod def compare_labelled_data(cls, obj1, obj2, msg=None): From f22237ef78b180a815920fcb420a9c38f76218b8 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 1 Dec 2015 17:29:54 +0000 Subject: [PATCH 02/15] Renamed Dimension.formatter to Dimension.value_format --- holoviews/core/dimension.py | 5 +++-- holoviews/core/ndmapping.py | 4 ++-- holoviews/plotting/mpl/element.py | 8 ++++---- holoviews/plotting/mpl/plot.py | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index c9a62cd35f..df600562f5 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -65,7 +65,7 @@ class Dimension(param.Parameterized): maximum allowed value (defined by the range parameter) is continuous with the minimum allowed value.""") - formatter = param.Callable(default=None, doc=""" + value_format = param.Callable(default=None, doc=""" Formatting function applied to each value before display.""") range = param.Tuple(default=(None, None), doc=""" @@ -128,7 +128,8 @@ def pprint_value(self, value): Applies the defined formatting to the value. """ own_type = type(value) if self.type is None else self.type - formatter = self.formatter if self.formatter else self.type_formatters.get(own_type) + formatter = (self.value_format if self.value_format + else self.type_formatters.get(own_type)) if formatter: if callable(formatter): return formatter(value) diff --git a/holoviews/core/ndmapping.py b/holoviews/core/ndmapping.py index b0c210de5d..5c19632dcf 100644 --- a/holoviews/core/ndmapping.py +++ b/holoviews/core/ndmapping.py @@ -432,8 +432,8 @@ def info(self): info_str += '%s Dimensions: \n' % group.capitalize() for d in dimensions: dmin, dmax = self.range(d.name) - if d.formatter: - dmin, dmax = d.formatter(dmin), d.formatter(dmax) + if d.value_format: + dmin, dmax = d.value_format(dmin), d.value_format(dmax) info_str += '\t %s: %s...%s \n' % (str(d), dmin, dmax) print(info_str) diff --git a/holoviews/plotting/mpl/element.py b/holoviews/plotting/mpl/element.py index 1a40ccc21f..bf44a186d7 100644 --- a/holoviews/plotting/mpl/element.py +++ b/holoviews/plotting/mpl/element.py @@ -146,8 +146,8 @@ def _finalize_axis(self, key, title=None, ranges=None, xticks=None, yticks=None, xformat, yformat = None, None if xdim is None: pass - elif xdim.formatter: - xformat = xdim.formatter + elif xdim.value_format: + xformat = xdim.value_format elif xdim.type in xdim.type_formatters: xformat = xdim.type_formatters[xdim.type] if xformat: @@ -155,8 +155,8 @@ def _finalize_axis(self, key, title=None, ranges=None, xticks=None, yticks=None, if ydim is None: pass - elif ydim.formatter: - yformat = ydim.formatter + elif ydim.value_format: + yformat = ydim.value_format elif ydim.type in ydim.type_formatters: yformat = ydim.type_formatters[ydim.type] if yformat: diff --git a/holoviews/plotting/mpl/plot.py b/holoviews/plotting/mpl/plot.py index 20551ba13a..e878cd817c 100644 --- a/holoviews/plotting/mpl/plot.py +++ b/holoviews/plotting/mpl/plot.py @@ -505,8 +505,8 @@ def _layout_axis(self, layout, axis): def _process_ticklabels(self, labels, dim): formatted_labels = [] for k in labels: - if dim and dim.formatter: - k = dim.formatter(k) + if dim and dim.value_format: + k = dim.value_format(k) elif not isinstance(k, (str, type(None))): k = self.tick_format % k elif k is None: From a4a4470c8012beb4fc57b06e3f8033ee5aefc2bf Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 1 Dec 2015 19:28:26 +0000 Subject: [PATCH 03/15] Added aliases support to util.sanitize_identifier_fn --- holoviews/core/util.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/holoviews/core/util.py b/holoviews/core/util.py index a8e604758a..6ad775adf9 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -114,10 +114,32 @@ class sanitize_identifier_fn(param.ParameterizedFunction): Whether leading underscores should be allowed to be sanitized with the leading prefix.""") + aliases = param.Dict(default={}, doc=""" + A dictionary of aliases mapping long strings to their short, + sanitized equivalents""") + prefix = 'A_' _lookup_table = {} + + @param.parameterized.bothmethod + def add_aliases(self_or_cls, **kwargs): + """ + Conveniently add new aliases as keyword arguments. For instance + you can add one new alias with add_aliases(short='Longer string') + """ + self_or_cls.aliases.update({v:k for k,v in kwargs.items()}) + + @param.parameterized.bothmethod + def remove_aliases(self_or_cls, aliases): + """ + Remove a list of aliases. + """ + for k,v in self_or_cls.aliases.items(): + if v in aliases: + self_or_cls.aliases.pop(k) + @param.parameterized.bothmethod def allowable(self_or_cls, name, disable_leading_underscore=None): disabled_reprs = ['javascript', 'jpeg', 'json', 'latex', @@ -177,6 +199,8 @@ def shortened_character_name(self_or_cls, c, eliminations=[], substitutions={}, def __call__(self, name, escape=True, version=None): if name in [None, '']: return name + elif name in self.aliases: + return self.aliases[name] elif name in self._lookup_table: return self._lookup_table[name] name = safe_unicode(name) @@ -244,6 +268,10 @@ def sanitize(self, name, valid_fn): return self._process_underscores(sanitized + ([chars] if chars else [])) sanitize_identifier = sanitize_identifier_fn.instance() + + +group_sanitizer = sanitize_identifier_fn.instance() +label_sanitizer = sanitize_identifier_fn.instance() dimension_sanitizer = sanitize_identifier_fn.instance(capitalize=False) def find_minmax(lims, olims): From f7804663edeb03f182dfc70a3ef251b45a49b229 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 1 Dec 2015 19:32:33 +0000 Subject: [PATCH 04/15] Added support for defining alias by passing tuple as Dimension name --- holoviews/core/dimension.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index df600562f5..fe9acfd2ab 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -103,7 +103,14 @@ def __init__(self, name, **params): existing_params = dict(name.get_param_values()) else: existing_params = {'name': name} - super(Dimension, self).__init__(**dict(existing_params, **params)) + + all_params = dict(existing_params, **params) + if isinstance(all_params['name'], tuple): + alias, long_name = all_params['name'] + dimension_sanitizer.add_aliases(**{alias:long_name}) + all_params['name'] = long_name + + super(Dimension, self).__init__(**all_params) def __call__(self, name=None, **overrides): From f1cdf7b61d4faafcb834398464f3194b84776fa0 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 1 Dec 2015 19:35:15 +0000 Subject: [PATCH 05/15] The tests in testutils now use new sanitize_identifier_fn instance --- tests/testutils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/testutils.py b/tests/testutils.py index 679f677cc6..79fcc13e00 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -8,11 +8,12 @@ import numpy as np -from holoviews.core.util import sanitize_identifier, find_range, max_range +from holoviews.core.util import sanitize_identifier_fn, find_range, max_range from holoviews.element.comparison import ComparisonTestCase py_version = sys.version_info.major +sanitize_identifier = sanitize_identifier_fn.instance() class TestAllowablePrefix(ComparisonTestCase): """ From 88663ff36dae71a797ac744f2409bb7655a70180 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 1 Dec 2015 19:48:30 +0000 Subject: [PATCH 06/15] LabelledData now uses separate sanitizers for group and label --- holoviews/core/dimension.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index fe9acfd2ab..aee1d7c071 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -14,7 +14,8 @@ import numpy as np import param -from ..core.util import (basestring, sanitize_identifier, max_range, +from ..core.util import (basestring, sanitize_identifier, + group_sanitizer, label_sanitizer, max_range, find_range, dimension_sanitizer) from .options import Store, StoreOptions from .pprint import PrettyPrinter @@ -244,11 +245,21 @@ def __init__(self, data, id=None, **params): """ self.data = data self.id = id + if isinstance(params.get('label',None), tuple): + (alias, long_name) = params['label'] + label_sanitizer.add_aliases(**{alias:long_name}) + params['label'] = long_name + + if isinstance(params.get('group',None), tuple): + (alias, long_name) = params['group'] + label_sanitizer.add_aliases(**{alias:long_name}) + params['group'] = long_name + super(LabelledData, self).__init__(**params) - if not sanitize_identifier.allowable(self.group): + if not group_sanitizer.allowable(self.group): raise ValueError("Supplied group %r contains invalid characters." % self.group) - elif not sanitize_identifier.allowable(self.label): + elif not label_sanitizer.allowable(self.label): raise ValueError("Supplied label %r contains invalid characters." % self.label) From b870948dba14cfbb90242641bfa6902f8d802e60 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 1 Dec 2015 20:04:38 +0000 Subject: [PATCH 07/15] Updated Exploring_Data tutorial to use value_format parameter --- doc/Tutorials/Exploring_Data.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Tutorials/Exploring_Data.ipynb b/doc/Tutorials/Exploring_Data.ipynb index 3425764979..96cbd8915c 100644 --- a/doc/Tutorials/Exploring_Data.ipynb +++ b/doc/Tutorials/Exploring_Data.ipynb @@ -132,7 +132,7 @@ }, "outputs": [], "source": [ - "date_dim = hv.Dimension(\"Date\", formatter=md.DateFormatter('%h %d %Y %H:%M UTC'), type=float)\n", + "date_dim = hv.Dimension(\"Date\", value_format=md.DateFormatter('%h %d %Y %H:%M UTC'), type=float)\n", "kdims = ['Frame', date_dim]" ] }, From dbcc80dbb05c08bfec1618a820da28cdfcc27146 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 1 Dec 2015 21:00:01 +0000 Subject: [PATCH 08/15] Replaced use of sanitize_identifier with calls to label/group sanitizer --- holoviews/core/dimension.py | 4 ++-- holoviews/core/io.py | 6 +++--- holoviews/core/layout.py | 6 +++--- holoviews/core/options.py | 11 ++++++----- holoviews/core/pprint.py | 6 +++--- holoviews/core/util.py | 4 ++-- holoviews/operation/element.py | 5 +++-- holoviews/plotting/plot.py | 4 ++-- holoviews/plotting/widgets/__init__.py | 4 ++-- 9 files changed, 26 insertions(+), 24 deletions(-) diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index aee1d7c071..3c049eed10 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -801,11 +801,11 @@ def __call__(self, options=None, **kwargs): raise Exception("Cannot mix target specification keys such as 'Image' with non-target keywords.") elif not any(targets): # Not targets specified - add current object as target - sanitized_group = sanitize_identifier(self.group) + sanitized_group = group_sanitizer(self.group) if self.label: identifier = ('%s.%s.%s' % (self.__class__.__name__, sanitized_group, - sanitize_identifier(self.label))) + label_sanitizer(self.label))) elif sanitized_group != self.__class__.__name__: identifier = '%s.%s' % (self.__class__.__name__, sanitized_group) else: diff --git a/holoviews/core/io.py b/holoviews/core/io.py index 7ed6be810d..bd931cbece 100644 --- a/holoviews/core/io.py +++ b/holoviews/core/io.py @@ -28,7 +28,7 @@ from .layout import Layout from .ndmapping import OrderedDict, NdMapping, UniformNdMapping from .options import Store -from .util import unique_iterator, sanitize_identifier +from .util import unique_iterator, sanitize_identifier, group_sanitizer, label_sanitizer class Reference(param.Parameterized): @@ -336,8 +336,8 @@ def save(self_or_cls, obj, filename, key={}, info={}, **kwargs): components = list(obj.data.values()) entries = entries if len(entries) > 1 else [entries[0]+'(L)'] else: - entries = ['%s.%s' % (sanitize_identifier(obj.group, False), - sanitize_identifier(obj.label, False))] + entries = ['%s.%s' % (group_sanitizer(obj.group, False), + label_sanitizer(obj.label, False))] components = [obj] for component, entry in zip(components, entries): diff --git a/holoviews/core/layout.py b/holoviews/core/layout.py index 6de3eef87a..9b8d7de7f2 100644 --- a/holoviews/core/layout.py +++ b/holoviews/core/layout.py @@ -15,7 +15,7 @@ from .dimension import Dimension, Dimensioned, ViewableElement from .ndmapping import OrderedDict, NdMapping, UniformNdMapping from .tree import AttrTree -from .util import int_to_roman, sanitize_identifier +from .util import int_to_roman, sanitize_identifier, group_sanitizer, label_sanitizer from . import traversal @@ -384,9 +384,9 @@ def from_values(cls, val): return cls._from_values(val) elif collection: val = val[0] - group = sanitize_identifier(val.group) + group = group_sanitizer(val.group) group = ''.join([group[0].upper(), group[1:]]) - label = sanitize_identifier(val.label if val.label else 'I') + label = label_sanitizer(val.label if val.label else 'I') label = ''.join([label[0].upper(), label[1:]]) return cls(items=[((group, label), val)]) diff --git a/holoviews/core/options.py b/holoviews/core/options.py index bbb85d6a99..b577705975 100644 --- a/holoviews/core/options.py +++ b/holoviews/core/options.py @@ -39,7 +39,7 @@ import param from .tree import AttrTree -from .util import sanitize_identifier +from .util import sanitize_identifier, group_sanitizer,label_sanitizer from .pprint import InfoPrinter class OptionError(Exception): @@ -479,8 +479,9 @@ def closest(self, obj, group): In addition, closest supports custom options by checking the object """ - components = (obj.__class__.__name__, sanitize_identifier(obj.group), - sanitize_identifier(obj.label)) + components = (obj.__class__.__name__, + group_sanitizer(obj.group), + label_sanitizer(obj.label)) return self.find(components).options(group) @@ -700,12 +701,12 @@ def _slice_match_level(self, overlay_items): level += 1 # Types match if len(spec) == 1: continue - group = [el.group, sanitize_identifier(el.group, escape=False)] + group = [el.group, group_sanitizer(el.group, escape=False)] if spec[1] in group: level += 1 # Values match else: return None if len(spec) == 3: - group = [el.label, sanitize_identifier(el.label, escape=False)] + group = [el.label, label_sanitizer(el.label, escape=False)] if (spec[2] in group): level += 1 # Labels match else: diff --git a/holoviews/core/pprint.py b/holoviews/core/pprint.py index 4a6c001eaf..ee5cd2fe74 100644 --- a/holoviews/core/pprint.py +++ b/holoviews/core/pprint.py @@ -16,7 +16,7 @@ import re # IPython not required to import ParamPager from param.ipython import ParamPager -from holoviews.core.util import sanitize_identifier +from holoviews.core.util import sanitize_identifier, group_sanitizer, label_sanitizer @@ -88,8 +88,8 @@ def info(cls, obj, ansi=False, backend='matplotlib'): @classmethod def get_target(cls, obj): objtype=obj.__class__.__name__ - group = sanitize_identifier(obj.group) - label = ('.'+sanitize_identifier(obj.label) if obj.label else '') + group = group_sanitizer(obj.group) + label = ('.' + label_sanitizer(obj.label) if obj.label else '') target = '{objtype}.{group}{label}'.format(objtype=objtype, group=group, label=label) diff --git a/holoviews/core/util.py b/holoviews/core/util.py index 6ad775adf9..9d41a6bb56 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -418,8 +418,8 @@ def match_spec(element, specification): match_tuple = () match = specification.get((), {}) for spec in [type(element).__name__, - sanitize_identifier(element.group, escape=False), - sanitize_identifier(element.label, escape=False)]: + group_sanitizer(element.group, escape=False), + label_sanitizer(element.label, escape=False)]: match_tuple += (spec,) if match_tuple in specification: match = specification[match_tuple] diff --git a/holoviews/operation/element.py b/holoviews/operation/element.py index 74f81a0859..1f2c0c7a1e 100644 --- a/holoviews/operation/element.py +++ b/holoviews/operation/element.py @@ -10,7 +10,7 @@ from ..core import (ElementOperation, NdOverlay, Overlay, GridMatrix, HoloMap, Columns, Element) -from ..core.util import find_minmax, sanitize_identifier +from ..core.util import find_minmax, sanitize_identifier, group_sanitizer, label_sanitizer from ..element.chart import Histogram, Curve, Scatter from ..element.raster import Raster, Image, RGB, QuadMesh from ..element.path import Contours, Polygons @@ -204,9 +204,10 @@ def _match(cls, el, spec): if not isinstance(el, Image) or spec_dict['type'] != 'Image': raise NotImplementedError("Only Image currently supported") + sanitizers = {'group':group_sanitizer, 'label':label_sanitizer} strength = 1 for key in ['group', 'label']: - attr_value = sanitize_identifier(getattr(el, key)) + attr_value = sanitizers[key](getattr(el, key)) if key in spec_dict: if spec_dict[key] != attr_value: return None strength += 1 diff --git a/holoviews/plotting/plot.py b/holoviews/plotting/plot.py index 2ae12d6357..f1eb2aba28 100644 --- a/holoviews/plotting/plot.py +++ b/holoviews/plotting/plot.py @@ -314,8 +314,8 @@ def _get_norm_opts(self, obj): norm_opts = {} # Get all elements' type.group.label specs and ids - type_val_fn = lambda x: (x.id, (type(x).__name__, util.sanitize_identifier(x.group, escape=False), - util.sanitize_identifier(x.label, escape=False))) \ + type_val_fn = lambda x: (x.id, (type(x).__name__, util.group_sanitizer(x.group, escape=False), + util.label_sanitizer(x.label, escape=False))) \ if isinstance(x, Element) else None element_specs = {(idspec[0], idspec[1]) for idspec in obj.traverse(type_val_fn) if idspec is not None} diff --git a/holoviews/plotting/widgets/__init__.py b/holoviews/plotting/widgets/__init__.py index 9c30643ade..3f24daaaf5 100644 --- a/holoviews/plotting/widgets/__init__.py +++ b/holoviews/plotting/widgets/__init__.py @@ -5,7 +5,7 @@ import numpy as np from ...core import OrderedDict, NdMapping from ...core.options import Store -from ...core.util import (sanitize_identifier, safe_unicode, basestring, +from ...core.util import (dimension_sanitizer, safe_unicode, basestring, unique_iterator) from ...core.traversal import hierarchical @@ -259,7 +259,7 @@ def get_widgets(self): dim_vals = repr([v for v in dim_vals if v is not None]) dim_str = safe_unicode(dim.name) visibility = 'visibility: visible' if len(dim_vals) > 1 else 'visibility: hidden; height: 0;' - widget_data = dict(dim=sanitize_identifier(dim_str), dim_label=dim_str, + widget_data = dict(dim=dimension_sanitizer(dim_str), dim_label=dim_str, dim_idx=idx, vals=dim_vals, type=widget_type, visibility=visibility, step=step, next_dim=next_dim, next_vals=next_vals) From c35d936f9838ab27754e8779d4d5c7da81e941c1 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 1 Dec 2015 21:54:19 +0000 Subject: [PATCH 09/15] Removed outdated unit test from testcomparisondimension.py --- tests/testcomparisondimension.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/tests/testcomparisondimension.py b/tests/testcomparisondimension.py index bc8d0b1887..1056ae3204 100644 --- a/tests/testcomparisondimension.py +++ b/tests/testcomparisondimension.py @@ -18,9 +18,8 @@ def setUp(self): self.dimension6 = Dimension('dim1', cyclic=True, range=(0,1)) self.dimension7 = Dimension('dim1', cyclic=True, range=(0,1), unit='ms') self.dimension8 = Dimension('dim1', values=['a', 'b']) - self.dimension9 = Dimension('dim1', format_string='{name}') - self.dimension10 = Dimension('dim1', type=int) - self.dimension11 = Dimension('dim1', type=float) + self.dimension9 = Dimension('dim1', type=int) + self.dimension10 = Dimension('dim1', type=float) def test_dimension_comparison_equal1(self): self.assertEqual(self.dimension1, self.dimension1) @@ -69,15 +68,9 @@ def test_dimension_comparison_values_unequal(self): except AssertionError as e: self.assertEqual(str(e), "Dimension value declarations mismatched: [] != ['a', 'b']") - def test_dimension_comparison_format_unequal(self): - try: - self.assertEqual(self.dimension4, self.dimension9) - except AssertionError as e: - self.assertEqual(str(e), 'Dimension format string declarations mismatched: {name}: {val}{unit} != {name}') - def test_dimension_comparison_types_unequal(self): try: - self.assertEqual(self.dimension10, self.dimension11) + self.assertEqual(self.dimension9, self.dimension10) except AssertionError as e: self.assertEqual(str(e)[:39], "Dimension type declarations mismatched:") From 0d72f831208772e4f1aff3aae16024a8250c7fd7 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 1 Dec 2015 22:45:39 +0000 Subject: [PATCH 10/15] Added Aliases helper object to core.util --- holoviews/core/util.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/holoviews/core/util.py b/holoviews/core/util.py index 9d41a6bb56..adb4e3912e 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -46,6 +46,27 @@ def capitalize_unicode_name(s): return s[:index] + tail +class Aliases(object): + """ + Helper class useful for defining a set of alias tuples on a single object. + + For instance, when defining a group or label with an alias, instead + of setting tuples in the constructor, you could use + ``aliases.water`` if you first define: + + >>> aliases = Aliases(water='H_2O', sugar='C_6H_{12}O_6') + >>> aliases.water + ('water', 'H_2O') + + This may be used to conveniently define aliases for groups, labels + or dimension names. + """ + def __init__(self, **kwargs): + for k,v in kwargs.items(): + setattr(self, k, (k,v)) + + + class sanitize_identifier_fn(param.ParameterizedFunction): """ Sanitizes group/label values for use in AttrTree attribute From b264cb86f4d0d1fca5d2bc86cfb013a639e159de Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 1 Dec 2015 23:07:33 +0000 Subject: [PATCH 11/15] Fixed bug setting group aliases in LabelledData --- holoviews/core/dimension.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index 3c049eed10..c260c4b698 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -252,7 +252,7 @@ def __init__(self, data, id=None, **params): if isinstance(params.get('group',None), tuple): (alias, long_name) = params['group'] - label_sanitizer.add_aliases(**{alias:long_name}) + group_sanitizer.add_aliases(**{alias:long_name}) params['group'] = long_name super(LabelledData, self).__init__(**params) From 61e065687ec4186d2bf16cb160df031666f4b267 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 1 Dec 2015 23:10:56 +0000 Subject: [PATCH 12/15] Selecting appropriate sanitizers in LabelledData.matches --- holoviews/core/dimension.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/holoviews/core/dimension.py b/holoviews/core/dimension.py index c260c4b698..7c7bac096c 100644 --- a/holoviews/core/dimension.py +++ b/holoviews/core/dimension.py @@ -324,8 +324,9 @@ def matches(self, spec): self_spec = match_fn(split_spec) unescaped_match = match_fn(specification[:len(split_spec)]) == self_spec if unescaped_match: return True - identifier_specification = tuple(sanitize_identifier(ident, escape=False) - for ident in specification) + sanitizers = [sanitize_identifier, group_sanitizer, label_sanitizer] + identifier_specification = tuple(fn(ident, escape=False) + for ident, fn in zip(specification, sanitizers)) identifier_match = match_fn(identifier_specification[:len(split_spec)]) == self_spec return identifier_match From 81009392b25d0ac02f7b2faa381e77c7ecdd2b66 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 1 Dec 2015 23:13:49 +0000 Subject: [PATCH 13/15] Selecting appropriate sanitizers in Layout.new_path --- holoviews/core/layout.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/holoviews/core/layout.py b/holoviews/core/layout.py index 9b8d7de7f2..1be6618628 100644 --- a/holoviews/core/layout.py +++ b/holoviews/core/layout.py @@ -332,7 +332,8 @@ def collate(cls, data, kdims=None, key_dimensions=None): @classmethod def new_path(cls, path, item, paths, count): - path = tuple(sanitize_identifier(p) for p in path) + sanitizers = [sanitize_identifier, group_sanitizer, label_sanitizer] + path = tuple(fn(p) for (p, fn) in zip(path, sanitizers)) while any(path[:i] in paths or path in [p[:i] for p in paths] for i in range(1,len(path)+1)): path = path[:2] From 2ce6bde18073125192d5b6feef17ee56f4f188f8 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 1 Dec 2015 23:36:16 +0000 Subject: [PATCH 14/15] Minor fix to docstring of Aliases helper class --- holoviews/core/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/holoviews/core/util.py b/holoviews/core/util.py index adb4e3912e..26c54fb93f 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -54,7 +54,7 @@ class Aliases(object): of setting tuples in the constructor, you could use ``aliases.water`` if you first define: - >>> aliases = Aliases(water='H_2O', sugar='C_6H_{12}O_6') + >>> aliases = Aliases(water='H_2O', glucose='C_6H_{12}O_6') >>> aliases.water ('water', 'H_2O') From 5816f185020b226a8657c8382b28602ba1fb49e1 Mon Sep 17 00:00:00 2001 From: jlstevens Date: Tue, 1 Dec 2015 23:57:10 +0000 Subject: [PATCH 15/15] Added unit tests of the aliasing system --- tests/testaliases.py | 56 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 tests/testaliases.py diff --git a/tests/testaliases.py b/tests/testaliases.py new file mode 100644 index 0000000000..aad4af2f2b --- /dev/null +++ b/tests/testaliases.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +""" +Unit tests of the Aliases helper function and aliases in general +""" +import holoviews as hv +import numpy as np + +from holoviews.element.comparison import ComparisonTestCase + + +class TestAliases(ComparisonTestCase): + """ + Tests of allowable and hasprefix method. + """ + + def setUp(self): + self.data1 = np.random.rand(10,10) + self.data2 = np.random.rand(10,10) + super(TestAliases, self).setUp() + + + def test_aliased_layout(self): + im1 = hv.Image(self.data1, group=('Spectrum', 'Frequency spectrum'), + label=('Glucose', '$C_6H_{12}O_6$')) + self.assertEqual(im1.label, '$C_6H_{12}O_6$') + im2 = hv.Image(self.data2, + group=('Spectrum', 'Frequency spectrum'), + label=('Water', '$H_2O$')) + self.assertEqual(im2.label, '$H_2O$') + layout = im1 + im2 + self.assertEqual(layout.Spectrum.Glucose, im1) + self.assertEqual(layout.Spectrum.Water, im2) + + + def test_aliased_layout_helper(self): + al = hv.util.Aliases(Spectrum='Frequency spectrum', + Water='$H_2O$', + Glucose='$C_6H_{12}O_6$') + + im1 = hv.Image(self.data1, group=al.Spectrum, label=al.Glucose) + self.assertEqual(im1.label, '$C_6H_{12}O_6$') + im2 = hv.Image(self.data2, group=al.Spectrum, label=al.Water) + self.assertEqual(im2.label, '$H_2O$') + layout = im1 + im2 + self.assertEqual(layout.Spectrum.Glucose, im1) + self.assertEqual(layout.Spectrum.Water, im2) + + + def test_dimension_aliases(self): + im = hv.Image(self.data1, + kdims=[('Lambda', '$\Lambda$'), + ('Joules', 'Energy ($J$)')]) + self.assertEqual(im.kdims[0].name, '$\Lambda$') + self.assertEqual(im.kdims[1].name, 'Energy ($J$)') + sliced = im.select(Lambda=(-0.2, 0.2), Joules=(-0.3, 0.3)) + self.assertEqual(sliced.shape, (6,4))