Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Easy definition of sanitized substitutions #312

Merged
merged 15 commits into from
Dec 2, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion doc/Tutorials/Exploring_Data.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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]"
]
},
Expand Down
54 changes: 35 additions & 19 deletions holoviews/core/dimension.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -25,6 +26,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
Expand Down Expand Up @@ -63,7 +66,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="""
Expand All @@ -89,12 +92,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})'
Expand All @@ -107,7 +104,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):
Expand All @@ -132,7 +136,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)
Expand All @@ -150,13 +155,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):
Expand Down Expand Up @@ -240,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']
group_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)

Expand Down Expand Up @@ -309,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

Expand Down Expand Up @@ -786,11 +802,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:
Expand Down
6 changes: 3 additions & 3 deletions holoviews/core/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
9 changes: 5 additions & 4 deletions holoviews/core/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -384,9 +385,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)])

Expand Down
4 changes: 2 additions & 2 deletions holoviews/core/ndmapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
11 changes: 6 additions & 5 deletions holoviews/core/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)


Expand Down Expand Up @@ -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:
Expand Down
6 changes: 3 additions & 3 deletions holoviews/core/pprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -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



Expand Down Expand Up @@ -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)
Expand Down
53 changes: 51 additions & 2 deletions holoviews/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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', glucose='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
Expand Down Expand Up @@ -114,10 +135,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',
Expand Down Expand Up @@ -177,6 +220,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)
Expand Down Expand Up @@ -244,6 +289,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):
Expand Down Expand Up @@ -390,8 +439,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]
Expand Down
3 changes: 0 additions & 3 deletions holoviews/element/comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
5 changes: 3 additions & 2 deletions holoviews/operation/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading