HoloViews is designed to be both highly customizable, allowing you to control how your visualizations appear, but also to enforce a strong separation between your data (with any semantically associated metadata, like type and label information) and all options related purely to visualization. This separation allows HoloViews objects to be generated easily by external programs, without giving them a dependency on any plotting or windowing libraries. It also helps make it completely clear which parts of your code deal with the actual data, and which are just about displaying it nicely, which becomes very important for complex visualizations that become more complicated than your data itself.
To achieve this separation, HoloViews stores visualization options independently from your data, and applies the options only when rendering the data to a file on disk or when displaying it in an IPython notebook cell.
This tutorial gives an overview of the different types of options available, how to find out more about them, and how to set them in both regular Python and using the IPython magic interface that is shown elsewhere in the tutorials.
First, we'll create some HoloViews data objects ready to visualize:
In [ ]:
import numpy as np
import holoviews as hv
%reload_ext holoviews.ipython
x,y = np.mgrid[-50:51, -50:51] * 0.1
image = hv.Image(np.sin(x**2+y**2), group="Function", label="Sine")
coords = [(0.1*i, np.sin(0.1*i)) for i in range(100)]
curve = hv.Curve(coords)
curves = {phase: hv.Curve([(0.1*i, np.sin(phase+0.1*i)) for i in range(100)])
for phase in [0, np.pi/2, np.pi, np.pi*3/2]}
waves = hv.HoloMap(curves)
layout = image + curve
To illustrate how to do plotting independently of IPython, we'll generate and save a plot directly to disk. First, let's create a renderer
object that will render our files to SVG (for static figures) or GIF (for animations):
In [ ]:
renderer = hv.Store.renderers['matplotlib'].instance(fig='svg', holomap='gif')
We could instead have used the default Store.renderer
, but that would have been PNG format. Using this renderer, we can save any HoloViews object as SVG or GIF:
In [ ]:
renderer.save(layout, 'example_I')
That's it! The renderer builds the figure in matplotlib, renders it to SVG, and saves that to "example_I.svg" on disk. Everything up to this point would have worked the same in IPython or in regular Python, even with no display available. But since we're in IPython Notebook at the moment, we can check whether the exporting worked:
In [ ]:
from IPython.display import SVG
SVG(filename='example_I.svg')
You can use this workflow for generating HoloViews visualizations directly from Python, perhaps as a part of a set of scripts that you run automatically, e.g. to put your results up on a web server as soon as data is generated. But so far, this plot just uses all the default options, with no customization. How can we change how the plot will appear when we render it?
HoloViews provides three categories of visualization options that can be set by the user. In this section we will first describe the different kinds of options, then later sections show you how to list the supported options of each type for a given HoloViews object or class, and how to change them in Python or IPython.
style
options are passed directly to the underlying rendering backend that actually draws the plots, allowing you to control the details of how it behaves. The default backend is matplotlib, and the only other backend currently available is mpld3, both of which use matplotlib options. HoloViews can tell you which of these options are supported, but you will need to see the matplotlib documentation for the details of their use.
HoloViews has been designed to be easily extensible to additional backends in the future, such as Cairo, VTK, Bokeh, or D3.js, and if one of those backends were selected then the supported style options would differ.
Each of the various HoloViews plotting classes declares various Parameters that control how HoloViews builds the visualization for that type of object, such as plot sizes and labels. HoloViews uses these options internally; they are not simply passed to the matplotlib backend. HoloViews documents these options fully in its online help and in the Reference Manual. These options may vary for different backends in some cases, but we try to keep any options that are meaningful for a variety of backends the same for all of them.
norm
options are a special type of plot option that are applied orthogonally to the above two types, to control normalization. Normalization refers to adjusting the properties of one plot relative to those of another. For instance, two images normalized together would appear with relative brightness levels, with the brightest image using the full range black to white, while the other image is scaled proportionally. Two images normalized independently would both cover the full range from black to white. Similarly, two axis ranges normalized together will expand to fit the largest range of either axis, while those normalized separately would cover different ranges.
There are currently only two norm
options supported, axiswise
and framewise
, but they can be applied to any of the various object types in HoloViews to specify a huge range of different normalization options.
For a given category or group of HoloViews objects, if axiswise
is True, normalization will be computed independently for all items in that category that have their own axes, such as different Image
plots or Curve
plots. If axiswise
is False, all such objects are normalized together.
For a given category or group of HoloViews objects, if framewise
is True, normalization of any HoloMap
objects included is done independently per frame rendered -- each frame will appear as it would if it were extracted from the HoloMap
and plotted separately. If framewise
is False (the default), all frames in a given HoloMap
are normalized together, so that you can see strength differences over the course of the animation.
As described below, these options can be controlled precisely and in any combination to make sure that HoloViews displays the data of most interest, ignoring irrelevant differences and highlighting important ones.
For the norm
options, no further online documentation is provided, because all of the various visualization classes support only the two options described above. But there are a variety of ways to get the list of supported style
options and detailed documentation for the plot
options for a given component.
First, for any Python class or object in HoloViews, you can use holoviews.help(
object-or-class, visualization=False)
to find out about its parameters. For instance, these parameters are available for our Image
object, shown with their current value (or default value, for a class), data type, whether it can be changed by the user (if it is constant, read-only, etc.), and bounds if any:
In [ ]:
hv.help(image, visualization=False)
This information can be useful, but we have explicitly suppressed information regarding the visualization parameters -- these all report metadata about your data, not about anything to do with plotting directly. That's because the normal HoloViews components have nothing to do with plotting; they are just simple containers for your data and a small amount of metadata.
Instead, the plotting implementation and its associated parameters are kept in completely separate Python classes and objects. To find out about visualizing a HoloViews component like an Image
, you can simply use the help command holoviews.help(
object-or-class)
that looks up the code that plots that particular type of component, and then reports the style
and plot
options available for it.
For our image
example, holoviews.help
first finds that image
is of type Image
, then looks in its database to find that Image
visualization is handled by the RasterPlot
class (which users otherwise rarely need to access directly). holoviews.help
then shows information about what objects are available to customize (either the object itself, or the items inside a container), followed by a brief list of style
options supported by a RasterPlot
, and a very long list of plot
options (which are all the parameters of a RasterPlot
):
In [ ]:
hv.help(image)
As you can see, HoloViews lists the currently allowed style
options, but provides no further documentation because these settings are implemented by matplotlib and described at the matplotlib site. Note that matplotlib actually accepts a huge range of additional options, but they are not listed as being allowed because those options are not normally meaningful for this plot type. But if you know of a specific matplotlib option not on the list and really want to use it, you can add it manually to the list of supported options using Store.add_style_opts(
holoviews-component-class, ['
matplotlib-option ...'])
. For instance, if you want to use the filternorm
parameter with this image object, you would run Store.add_style_opts(Image, ['filternorm'])
. This will add the new option to the corresponding plotting class RasterPlot
:
In [ ]:
hv.Store.add_style_opts(hv.Image, ['filternorm'])
# To check that it worked:
RasterPlot = renderer.plotting_class(hv.Image)
print(RasterPlot.style_opts)
Any parameter in HoloViews can be set on an object or on the class of the object, so any of the above plot options can be set like:
In [ ]:
RasterPlot.colorbar=True
RasterPlot.set_param(show_title=False,show_frame=True)
Here .set_param()
allows you to set multiple parameters conveniently, but it works the same as the single-parameter .colorbar
example above it. Setting these values at the class level affects all previously created and to-be-created plotting objects of this type, unless specifically overridden via Store
as described below.
Note that if you look at the source code for a particular plotting class, you will only see some of the parameters it supports. The rest, such as show_frame
above, are defined in a superclass of the given object. The Reference Manual shows the complete list of parameters available for any given class (those labeled param
in the manual), but it can be an overwhelming list since it includes all superclasses, all the metadata about each parameter, etc. The holoviews.help
command with visualization=True
provides a much more concise listing, and also shows the style
options that are not listed in the Reference Manual.
Because setting these parameters at the class level does not provide much control over individual plots, HoloViews provides a much more flexible system using the OptionTree
mechanisms described below, which can override these class defaults according to the HoloViews object type, group
, and label
.
The rest of the sections show how to change any of the above options, once you have found the right one using the suitable call to holoviews.help
.
Once you know the name of the option you want to change, and the value you want to change it to, there are a number of ways to customize your plot.
For the Python output to SVG example above, you can specify the options for a given type using keywords supplying a dictionary for any of the above option categories. You can see that the colormap changes when we supply that style
option and render a new SVG:
In [ ]:
renderer.save(layout, 'example_II', style=dict(Image={'cmap':'Blues'}),
plot= dict(Image={'yaxis':None}))
SVG(filename='example_II.svg')
As before, the SVG call is simply to display it here in the notebook; the actual image is saved on disk and then loaded back in here for display.
You can see that the image now has a colorbar, because we set colorbar=True
on the RasterPlot
class, that it has become blue, because we set the matplotlib cmap
style option in the renderer.save
call, and that the y axis has been disabled, because we set the plot
option yaxis
to None
(which is normally 'left'
by default, as you can see in the default value for RasterPlot
's parameter yaxis
above). Hopefully you can see that once you know the option value you want to use, it can be provided easily.
You can also create a whole set of options separately, perhaps holding a large collection of preferred values, and apply it whenever you wish to save:
In [ ]:
options={'Image.Function.Sine': {'plot':dict(fig_size=50), 'style':dict(cmap='jet')}}
renderer.save(layout, 'example_III',options=options)
SVG(filename='example_III.svg')
Here you can see that the y axis has returned, because our previous setting to turn it off was just for the call to renderer.save
. But we still have a colorbar, because that parameter was set at the class level, for all future plots of this type. Note that this form of option setting, while more verbose, accepts the full {type}[.{group}[.{label}]]
syntax, like 'Image.Function.Sine'
or 'Image.Function'
, while the shorter keyword approach above only supports the class, like 'Image'.
Note that for the options
dictionary, the option nesting is inverted compared to the keyword approach: the outermost dictionary is by key (Image
, or Image.Function.Sines
), with the option categories underneath. You can see that with this mechanism, we can specify the options even for subobjects of a container, as long as we can specify them with an appropriate key.
There's also another way to customize options in Python that lets you build up customizations incrementally. To do this, you can associate a particular set of options persistently with a particular HoloViews object, even if that object is later combined with other objects into a container. Here a new copy of the object is created, with the given set of options (using either the keyword or options=
format above) bound to it:
In [ ]:
green_sine = image(style={'cmap':'Greens'})
Here we could save the object to SVG just as before, but in this case we can skip a step and simply view it directly in the notebook:
In [ ]:
green_sine
Both IPython notebook and renderer.save()
use the same mechanisms for keeping track of the options, so they will give the same results. Specifically, what happens when you "bind" a set of options to an object is that there is an integer ID stored in the object (green_sine
in this case), and a corresponding entry with that ID is stored in a database of options called an OptionTree
(kept in holoviews.core.options.Store
). The object itself is otherwise unchanged, but then if that object is later used in another container, etc. it will retain its ID and therefore its customization. Any customization stored in an OptionTree
will override any class attribute defaults set like RasterGridPlot.border=5
above. This approach lets HoloViews keep track of any customizations you want to make, without ever affecting your actual data objects.
If the same object is later customized again to create a new customized object, the old customizations will be copied, and then the new customizations applied. The new customizations will thus override the old, while retaining any previous customizations not specified in the new step.
In this way, it is possible to build complex objects with arbitrary customization, step by step. As mentioned above, it is also possible to customize objects already combined into a complex container, just by specifying an option for a suitable key (e.g. 'Image.Function.Sine'
above). This flexible system should allow for any level of customization that is needed.
Finally, there is one more way to apply options that is a mix of the above approaches -- temporarily assign a new ID to the object and apply a set of customizations during a specific portion of the code:
In [ ]:
with hv.StoreOptions.options(green_sine, options={'Image':{'style':{'cmap':'Reds'}}}):
data, info = renderer(green_sine)
print(info)
SVG(data)
Here the result is red, because it was rendered within the options context above, but were we to render the green_sine
again it would still be green; the options are applied only within the scope of the with
statement.
The above sections describe how to set all of the options using regular Python. Similar functionality is provided in IPython, but with a more convenient syntax based on an IPython magic command:
In [ ]:
%%opts Curve style(linewidth=8) Image style(interpolation='bilinear') plot[yaxis=None] norm{+framewise}
layout
The %%opts
magic works like the pure-Python option for associating options with an object, except that it works on the item in the IPython cell, and it affects the item directly rather than making a copy or applying only in scope. Specifically, it assigns a new ID number to the object returned from this cell, and makes a new OptionTree
containing the options for that ID number.
If the same layout
object is used later in the notebook, even within a complicated container object, it will retain the options set on it.
The options accepted are just the same as for the Python version, but specified more succinctly:
%%opts
target-specification style(
styleoption=
val ...) plot[
plotoption=
val ...] norm{+
normoption -
normoption...}
Here key lets you specify the object type (e.g. Image
), and optionally its group
(e.g. Image.Function
) or even both group
and label
(e.g. Image.Function.Sine
), if you want to control options very precisely. There is also an even further abbreviated syntax, because the special bracket types alone are enough to indicate which category of option is specified:
%%opts
target-specification (
styleoption=
val ...) [
plotoption=
val ...] {+
normoption -
normoption ...}
Here parentheses indicate style options, square brackets indicate plot options, and curly brackets indicate norm options (with +axiswise
and +framewise
indicating True for those values, and -axiswise
and -framewise
indicating False). Additional target-specifications and associated options of each type for that target-specification can be supplied at the end of this line. This ultra-concise syntax is used throughout the other tutorials, because it helps minimize the code needed to specify the plotting options, and helps make it very clear that these options are handled separately from the actual data.
The %opts
"line" magic (with one %
) works just the same as the %%opts
"cell" magic, but it changes the global default options for all future cells, allowing you to choose a new default colormap, line width, etc.
Apart from its brevity, a big benefit of using the IPython magic syntax %%opts
or %opts
is that it is fully tab-completable. Each of the options that is currently available will be listed if you press <TAB>
when you are ready to write it, which makes it much easier to find the right parameter. Of course, you will still need to consult the full holoviews.help
documentation (described above) to see the type, allowable values, and documentation for each option, but the tab completion should at least get you started and is great for helping you remember the list of options and see which options are available.
You can even use the succinct IPython-style specification directly in your Python code if you wish, but it requires the external pyparsing library (which is already available if you are using matplotlib):
In [ ]:
from holoviews.ipython.parser import OptsSpec
renderer.save(image + waves, 'example_V',
options=OptsSpec.parse("Image (cmap='gray')"))
There is also a special IPython syntax for listing the visualization options for a plotting object in a pop-up window that is equivalent to calling holoviews.help(
object)
:
In [ ]:
%%output info=True
curve
The line-magic version of this syntax %output info=True
is particularly useful for learning about components using the notebook, because it will keep a window open with the available options for each object updated as you do <shift-Enter>
in each cell. E.g. you can go through each of the components in the Elements
or Containers
tutorials this way, to see what options are offered by each without having to type anything for each one.
Hopefully you see from this tutorial how HoloViews enforces a strict separation between your data, stored in HoloViews Element
and container objects, and your plotting options, stored in dictionaries or OptionTree
s. Finding the right options is easiest in IPython, because of <TAB>
completion, but the same options are available in pure Python as well, with or without a display, allowing you to automate any part of the process of visualization and analysis.