Skip to content

Commit

Permalink
Merge 65ce994 into 50ae8f0
Browse files Browse the repository at this point in the history
  • Loading branch information
jlstevens committed Dec 20, 2018
2 parents 50ae8f0 + 65ce994 commit cd91e5e
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 23 deletions.
31 changes: 23 additions & 8 deletions examples/getting_started/2-Customization.ipynb
Expand Up @@ -240,7 +240,7 @@
"source": [
"# Switching to matplotlib\n",
"\n",
"Now let's switch our backend to [matplotlib](http://matplotlib.org/) to show the same elements in `layout` as rendered with different customizations, in a different output format (SVG), with a completely different plotting library:"
"Now let's customize our `layout` for rendering by matplotlib [matplotlib](http://matplotlib.org/) by supplying the `backend='matplotlib'` argument to the `.opts` method:"
]
},
{
Expand All @@ -249,26 +249,41 @@
"metadata": {},
"outputs": [],
"source": [
"hv.output(backend='matplotlib', fig='svg')\n",
"\n",
"layout.opts(\n",
"layout = layout.opts(\n",
" opts.Curve( aspect=6, xaxis=None, color='blue', linewidth=2, show_grid=False, linestyle='dashed'),\n",
" opts.Spikes(aspect=6, yaxis='bare', color='red', linewidth=0.25),\n",
" opts.Layout(sublabel_format='', vspace=0.1, fig_size=200))"
" opts.Layout(sublabel_format='', vspace=0.1, fig_size=200), backend='matplotlib')\n",
"layout\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Here we use the same tools with a different plotting extension. Naturally, a few changes needed to be made:\n",
"The plot is still rendered with bokeh as we haven't switched to the matplotlib backend just yet (although matplotlib support was was loaded by `hv.extension` at the start of this notebook). The above code sets the options appropriate to matplotlib without immediately making use of them and naturally, a few changes needed to be made:\n",
"\n",
"* A few of the options are different because of differences in how the plotting backends work. For instance, matplotlib uses ``aspect`` instead of setting ``width`` and ``height``. In some cases, but not all, HoloViews can smooth over such differences in the *plotting* options to make it simpler to switch backends.\n",
"* Some of the options are different because of differences in how the plotting backends work. For instance, matplotlib uses ``aspect`` instead of setting ``width`` and ``height``. In some cases, but not all, HoloViews can smooth over such differences in the *plotting* options to make it simpler to switch backends.\n",
"* The Bokeh hover tool is not supported by the matplotlib backend, as you might expect, nor are there any other interactive controls, because the Matplotlib backend generates static PNG or SVG images.\n",
"* Some options have different names; for instance, the Bokeh ``line_width`` option is called ``linewidth`` in matplotlib. These \"style\" options are directly inherited from the API of the plotting library backend, not defined by HoloViews.\n",
"* Containers like `Layout`s also have some options to control the arrangement of its components. Here we adjust the gap betwen the plots using ``vspace``.\n",
"\n",
"Note that you can even write options that work across multiple backends, as HoloViews will ignore keywords that are not applicable to the current backend (as long as they are valid for *some* loaded backend). See the [User Guide](../user_guide/03-Customizing_Plots.ipynb) for more details."
"Now we can use the `hv.output` utility to to show the same elements in `layout` as rendered with these different customizations, in a different output format (SVG), with a completely different plotting library:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"hv.output(layout, backend='matplotlib', fig='svg')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This approach allows you to associate options for multiple different backends with the same object. See the [User Guide](../user_guide/03-Customizing_Plots.ipynb) for more details."
]
},
{
Expand Down
48 changes: 33 additions & 15 deletions holoviews/util/__init__.py
Expand Up @@ -337,9 +337,9 @@ def _completer_reprs(cls, options, namespace=None, ns=None):
"""
Given a list of Option objects (such as those returned from
OptsSpec.parse_options) or an %opts or %%opts magic string,
return a list of corresponding completer reprs. The namespace is
typically given as 'hv' if fully qualified namespaces are
desired.
return a list of corresponding option builder reprs. The
namespace is typically given as 'hv' if fully qualified
namespaces are desired.
"""
if isinstance(options, basestring):
from .parser import OptsSpec
Expand All @@ -366,32 +366,50 @@ def _completer_reprs(cls, options, namespace=None, ns=None):
return reprs

@classmethod
def _build_completer(cls, element, allowed):
def _create_builder(cls, element, completions):
def fn(cls, spec=None, **kws):
spec = element if spec is None else '%s.%s' % (element, spec)
prefix = 'In opts.{element}(...), '.format(element=element)
backend = kws.pop('backend', None)
keys = set(kws.keys())
if backend:
allowed_kws = cls._element_keywords(backend,
elements=[element])[element]
invalid = set(kws.keys()) - set(allowed_kws)
invalid = keys - set(allowed_kws)
else:
allowed_kws = allowed
invalid = set(kws.keys()) - set(allowed)

spec = element if spec is None else '%s.%s' % (element, spec)

prefix = None
mismatched = {}
all_valid_kws = set()
for loaded_backend in Store.loaded_backends():
valid = set(cls._element_keywords(loaded_backend)[element])
all_valid_kws |= set(valid)
if keys <= valid: # Found a backend for which all keys are valid
return Options(spec, **kws)
mismatched[loaded_backend] = list(keys - valid)

invalid = keys - all_valid_kws # Keys not found for any backend
if mismatched and not invalid: # Keys found across multiple backends
msg = ('{prefix} keywords supplied are mixed across backends. '
'Keyword(s) {info}')
info = ', '.join('%s are invalid for %s'
% (', '.join(repr(el) for el in v), k)
for k,v in mismatched.items())
raise ValueError(msg.format(info=info, prefix=prefix))
allowed_kws = completions

reraise = False
if invalid:
try:
cls._options_error(list(invalid)[0], element, backend, allowed_kws)
except ValueError as e:
prefix = 'In opts.{element}(...), '.format(element=element)
msg = str(e)[0].lower() + str(e)[1:]
if prefix:
reraise = True

if reraise:
raise ValueError(prefix + msg)

return Options(spec, **kws)

filtered_keywords = [k for k in allowed if k not in cls._no_completion]
filtered_keywords = [k for k in completions if k not in cls._no_completion]
kws = ', '.join('{opt}=None'.format(opt=opt) for opt in sorted(filtered_keywords))
fn.__doc__ = '{element}({kws})'.format(element=element, kws=kws)
return classmethod(fn)
Expand Down Expand Up @@ -430,7 +448,7 @@ def _update_backend(cls, backend):
with param.logging_level('CRITICAL'):
all_keywords |= set(keywords)
setattr(cls, element,
cls._build_completer(element, keywords))
cls._create_builder(element, keywords))

filtered_keywords = [k for k in all_keywords if k not in cls._no_completion]
kws = ', '.join('{opt}=None'.format(opt=opt) for opt in sorted(filtered_keywords))
Expand Down

0 comments on commit cd91e5e

Please sign in to comment.