from collections import defaultdict
from ..core import OrderedDict
from ..core import Store
from ..core.util import basestring
[docs]class KeywordSettings(object):
"""
Base class for options settings used to specified collections of
keyword options.
"""
# Dictionary from keywords to allowed bounds/values
allowed = {'charwidth' : (0, float('inf'))}
defaults = OrderedDict([('charwidth' , 80)]) # Default keyword values.
options = OrderedDict(defaults.items()) # Current options
# Callables accepting (value, keyword, allowed) for custom exceptions
custom_exceptions = {}
# Hidden. Options that won't tab complete (for backward compatibility)
hidden = {}
[docs] @classmethod
def update_options(cls, options, items):
"""
Allows updating options depending on class attributes
and unvalidated options.
"""
pass
[docs] @classmethod
def get_options(cls, items, options, warnfn):
"Given a keyword specification, validate and compute options"
options = cls.update_options(options, items)
for keyword in cls.defaults:
if keyword in items:
value = items[keyword]
allowed = cls.allowed[keyword]
if isinstance(allowed, set): pass
elif isinstance(allowed, dict):
if not isinstance(value, dict):
raise ValueError("Value %r not a dict type" % value)
disallowed = set(value.keys()) - set(allowed.keys())
if disallowed:
raise ValueError("Keywords %r for %r option not one of %s"
% (disallowed, keyword, allowed))
wrong_type = {k: v for k, v in value.items()
if not isinstance(v, allowed[k])}
if wrong_type:
errors = []
for k,v in wrong_type.items():
errors.append("Value %r for %r option's %r attribute not of type %r" %
(v, keyword, k, allowed[k]))
raise ValueError('\n'.join(errors))
elif isinstance(allowed, list) and value not in allowed:
if keyword in cls.custom_exceptions:
cls.custom_exceptions[keyword](value, keyword, allowed)
else:
raise ValueError("Value %r for key %r not one of %s"
% (value, keyword, allowed))
elif isinstance(allowed, tuple):
if not (allowed[0] <= value <= allowed[1]):
info = (keyword,value)+allowed
raise ValueError("Value %r for key %r not between %s and %s" % info)
options[keyword] = value
return cls._validate(options, items, warnfn)
@classmethod
def _validate(cls, options, items, warnfn):
"Allows subclasses to check options are valid."
raise NotImplementedError("KeywordSettings is an abstract base class.")
def list_backends():
backends = []
for backend in Store.renderers:
backends.append(backend)
renderer = Store.renderers[backend]
modes = [mode for mode in renderer.params('mode').objects if mode != 'default']
backends += ['%s:%s' % (backend, mode) for mode in modes]
return backends
[docs]class OutputSettings(KeywordSettings):
"""
Class for controlling display and output settings.
"""
# Lists: strict options, Set: suggested options, Tuple: numeric bounds.
allowed = {'backend' : list_backends(),
'fig' : list_formats('fig'),
'holomap' : list_formats('holomap'),
'widgets' : ['embed', 'live'],
'fps' : (0, float('inf')),
'max_frames' : (0, float('inf')),
'max_branches': {None}, # Deprecated
'size' : (0, float('inf')),
'dpi' : (1, float('inf')),
'charwidth' : (0, float('inf')),
'filename' : {None},
'info' : [True, False],
'css' : {k: basestring
for k in ['width', 'height', 'padding', 'margin',
'max-width', 'min-width', 'max-height',
'min-height', 'outline', 'float']}}
defaults = OrderedDict([('backend' , None),
('fig' , None),
('holomap' , None),
('widgets' , None),
('fps' , None),
('max_frames' , 500),
('size' , None),
('dpi' , None),
('charwidth' , 80),
('filename' , None),
('info' , False),
('css' , None)])
# Defines the options the OutputSettings remembers. All other options
# are held by the backend specific Renderer.
remembered = ['max_frames', 'charwidth', 'info', 'filename']
# Remaining backend specific options renderer options
render_params = ['fig', 'holomap', 'size', 'fps', 'dpi', 'css', 'widget_mode', 'mode']
options = OrderedDict()
_backend_options = defaultdict(dict)
# Used to disable info output in testing
_disable_info_output = False
#==========================#
# Backend state management #
#==========================#
last_backend = None
backend_list = [] # List of possible backends
def missing_dependency_exception(value, keyword, allowed):
raise Exception("Format %r does not appear to be supported." % value)
def missing_backend_exception(value, keyword, allowed):
if value in OutputSettings.backend_list:
raise ValueError("Backend %r not available. Has it been loaded with the notebook_extension?" % value)
else:
raise ValueError("Backend %r does not exist" % value)
custom_exceptions = {'holomap':missing_dependency_exception,
'backend': missing_backend_exception}
# Counter for nbagg figures
nbagg_counter = 0
@classmethod
def _generate_docstring(cls):
intro = ["Helper used to set HoloViews display options.",
"Arguments are supplied as a series of keywords in any order:", '']
backend = "backend : The backend used by HoloViews"
fig = "fig : The static figure format"
holomap = "holomap : The display type for holomaps"
widgets = "widgets : The widget mode for widgets"
fps = "fps : The frames per second used for animations"
frames= ("max_frames : The max number of frames rendered (default %r)"
% cls.defaults['max_frames'])
size = "size : The percentage size of displayed output"
dpi = "dpi : The rendered dpi of the figure"
chars = ("charwidth : The max character width for displaying helper (default %r)"
% cls.defaults['charwidth'])
fname = ("filename : The filename of the saved output, if any (default %r)"
% cls.defaults['filename'])
page = ("info : The information to page about the displayed objects (default %r)"
% cls.defaults['info'])
css = ("css : Optional css style attributes to apply to the figure image tag")
descriptions = [backend, fig, holomap, widgets, fps, frames, size, dpi, chars, fname, page, css]
return '\n'.join(intro + descriptions)
@classmethod
def _validate(cls, options, items, warnfn):
"Validation of edge cases and incompatible options"
if 'html' in Store.display_formats:
pass
elif 'fig' in items and items['fig'] not in Store.display_formats:
msg = ("Requesting output figure format %r " % items['fig']
+ "not in display formats %r" % Store.display_formats)
if warnfn is None:
print('Warning: {msg}'.format(msg=msg))
else:
warnfn(msg)
backend = Store.current_backend
return Store.renderers[backend].validate(options)
@classmethod
def output(cls, line=None, cell=None, cell_runner=None,
help_prompt=None, warnfn=None, **kwargs):
if line and kwargs:
raise ValueError('Please either specify a string to '
'parse or keyword arguments')
# Make backup of previous options
prev_backend = Store.current_backend
prev_renderer = Store.renderers[prev_backend]
prev_backend_spec = prev_backend+':'+prev_renderer.mode
prev_params = {k: v for k, v in prev_renderer.get_param_values()
if k in cls.render_params}
prev_restore = dict(OutputSettings.options)
try:
if line is not None:
# Parse line
line = line.split('#')[0].strip()
kwargs = cls.extract_keywords(line, OrderedDict())
options = cls.get_options(kwargs, {}, warnfn)
# Make backup of options on selected renderer
if 'backend' in options:
backend_spec = options['backend']
if ':' not in backend_spec:
backend_spec += ':default'
else:
backend_spec = prev_backend_spec
renderer = Store.renderers[backend_spec.split(':')[0]]
render_params = {k: v for k, v in renderer.get_param_values()
if k in cls.render_params}
# Set options on selected renderer and set display hook options
OutputSettings.options = options
cls._set_render_options(options, backend_spec)
except Exception as e:
# If setting options failed ensure they are reset
OutputSettings.options = prev_restore
cls.set_backend(prev_backend)
print('Error: %s' % str(e))
if help_prompt:
print(help_prompt)
return
if cell is not None:
if cell_runner: cell_runner(cell,renderer)
# After cell restore previous options and restore
# temporarily selected renderer
OutputSettings.options = prev_restore
cls._set_render_options(render_params, backend_spec)
if backend_spec.split(':')[0] != prev_backend:
cls.set_backend(prev_backend)
cls._set_render_options(prev_params, prev_backend_spec)
[docs] @classmethod
def update_options(cls, options, items):
"""
Switch default options and backend if new backend is supplied in
items.
"""
# Get new backend
backend_spec = items.get('backend', Store.current_backend)
split = backend_spec.split(':')
backend, mode = split if len(split)==2 else (split[0], 'default')
if ':' not in backend_spec:
backend_spec += ':default'
if 'max_branches' in items:
print('Warning: The max_branches option is now deprecated. Ignoring.')
del items['max_branches']
# Get previous backend
prev_backend = Store.current_backend
renderer = Store.renderers[prev_backend]
prev_backend_spec = prev_backend+':'+renderer.mode
# Update allowed formats
for p in ['fig', 'holomap']:
cls.allowed[p] = list_formats(p, backend_spec)
# Return if backend invalid and let validation error
if backend not in Store.renderers:
options['backend'] = backend_spec
return options
# Get backend specific options
backend_options = dict(cls._backend_options[backend_spec])
cls._backend_options[prev_backend_spec] = {k: v for k, v in cls.options.items()
if k in cls.remembered}
# Fill in remembered options with defaults
for opt in cls.remembered:
if opt not in backend_options:
backend_options[opt] = cls.defaults[opt]
# Switch format if mode does not allow it
for p in ['fig', 'holomap']:
if backend_options.get(p) not in cls.allowed[p]:
backend_options[p] = cls.allowed[p][0]
# Ensure backend and mode are set
backend_options['backend'] = backend_spec
backend_options['mode'] = mode
return backend_options
@classmethod
def initialize(cls, backend_list):
cls.backend_list = backend_list
backend = Store.current_backend
if backend in Store.renderers:
cls.options = dict({k: cls.defaults[k] for k in cls.remembered})
cls.set_backend(backend)
else:
cls.options['backend'] = None
cls.set_backend(None)
@classmethod
def set_backend(cls, backend):
cls.last_backend = Store.current_backend
Store.current_backend = backend
@classmethod
def _set_render_options(cls, options, backend=None):
"""
Set options on current Renderer.
"""
if backend:
backend = backend.split(':')[0]
else:
backend = Store.current_backend
cls.set_backend(backend)
if 'widgets' in options:
options['widget_mode'] = options['widgets']
renderer = Store.renderers[backend]
render_options = {k: options[k] for k in cls.render_params if k in options}
renderer.set_param(**render_options)