One of the major design principles of HoloViews is that the declaration of data is completely independent from the plotting implementation. This means that the visualization of HoloViews data structures can be performed by different plotting backends. As part of the 1.4 release of HoloViews, a Bokeh backend was added in addition to the default matplotlib backend. Bokeh provides a powerful platform to generate interactive plots using HTML5 canvas and WebGL, and is ideally suited towards interactive exploration of data.

By combining the ease of generating interactive, high-dimensional visualizations with the interactive widgets and fast rendering provided by Bokeh, HoloViews becomes even more powerful.

This tutorial will cover some basic options on how to style and change various plot attributes and explore some of the more advanced features like interactive tools, linked axes, and brushing.

As usual, the first thing we do is initialize the HoloViews notebook extension, but we now specify the backend specifically.


In [ ]:
import numpy as np
import pandas as pd
import holoviews as hv

In [ ]:
hv.notebook_extension('bokeh')

We could instead leave the default backend as 'matplotlib', and then switch only some specific cells to use bokeh using a cell magic:

%%output backend='bokeh'
obj

Element Style options

Most Bokeh Elements support a mixture of the following fill, line, and text style customization options:


In [ ]:
from holoviews.plotting.bokeh.element import (line_properties, fill_properties,
                                              text_properties)
print("""
      Line properties: %s\n
      Fill properties: %s\n
      Text properties: %s
      """ % (line_properties, fill_properties, text_properties))

Here's an example of HoloViews Elements using a Bokeh backend, with bokeh's style options:


In [ ]:
curve_opts = dict(line_width=10, line_color='indianred', line_dash='dotted', line_alpha=0.5)
point_opts = dict(fill_color='#00AA00', fill_alpha=0.5, line_width=1, line_color='black', size=5)
text_opts  = dict(text_align='center', text_baseline='middle', text_color='gray', text_font='Arial')
xs = np.linspace(0, np.pi*4, 100)
data = (xs, np.sin(xs))

(hv.Curve(data)(style=curve_opts) +
 hv.Points(data)(style=point_opts) +
 hv.Text(6, 0, 'Here is some text')(style=text_opts))

Notice that because the first two plots use the same underlying data, they become linked, such that zooming or panning one of the plots makes the corresponding change on the other.

Sizing Elements

Sizing and aspect of Elements in bokeh are always computed in absolute pixels. To change the size or aspect of an Element set the width and height plot options.


In [ ]:
%%opts Points.A [width=300 height=300] Points.B [width=600 height=300]
hv.Points(data, group='A') + hv.Points(data, group='B')

Controlling axes

Bokeh provides a variety of options to control the axes. Here we provide a quick overview of linked plots for the same data displayed differently by applying log axes, disabling axes, rotating ticks, specifying the number of ticks, and supplying an explicit list of ticks.


In [ ]:
points = hv.Points(np.exp(xs)) 
axes_opts = [('Plain', {}),
             ('Log', {'logy': True}),
             ('None', {'yaxis': None}),
             ('Rotate', {'xrotation': 90}),
             ('N Ticks', {'xticks': 3}),
             ('List Ticks', {'xticks': [0, 100, 300, 500]})]

hv.Layout([points.relabel(group=group)(plot=opts)
           for group, opts in axes_opts]).cols(3).display('all')

Datetime axes

Both the matplotlib and the bokeh backends allow plotting datetime data, if you ensure the dates array is of a datetime dtype.


In [ ]:
%%opts Overlay [width=600 legend_position='top_left']
try:
    import bokeh.sampledata.stocks
except:
    import bokeh.sampledata
    bokeh.sampledata.download()

from bokeh.sampledata.stocks import GOOG, AAPL
goog_dates = np.array(GOOG['date'], dtype=np.datetime64)
aapl_dates = np.array(AAPL['date'], dtype=np.datetime64)
hv.Curve((goog_dates, GOOG['adj_close']), kdims=['Date'], vdims=['Stock Index'], label='Google') *\
hv.Curve((aapl_dates, AAPL['adj_close']), kdims=['Date'], vdims=['Stock Index'], label='Apple')

Matplotlib/Seaborn conversions

Bokeh also allows converting a subset of existing matplotlib plot types to Bokeh. This allows us to work with some of the Seaborn plot types, including Distribution, Bivariate, and TimeSeries:


In [ ]:
%%opts Distribution (kde_kws=dict(shade=True))
d1 = np.random.randn(500) + 450
d2 = np.random.randn(500) + 540
sines = np.array([np.sin(np.linspace(0, np.pi*2, 100)) + np.random.normal(0, 1, 100) for _ in range(20)])
hv.Distribution(d1) + hv.Bivariate((d1, d2)) + hv.TimeSeries(sines)

Containers

Tabs

Using bokeh, both (Nd)Overlay and (Nd)Layout types may be displayed inside a tabs widget. This may be enabled via a plot option tabs, and may even be nested inside a Layout.


In [ ]:
%%opts Overlay [tabs=True width=600 height=600] RGB [width=600 height=600]
x,y = np.mgrid[-50:51, -50:51] * 0.1

img = hv.Image(np.sin(x**2+y**2), bounds=(-1,-1,1,1))
img.relabel('Low') * img.clone(img.data*2).relabel('High') + img

Another reason to use tabs is that some Layout combinations may not be able to be displayed directly using HoloViews. For example, it is not currently possible to display a GridSpace as part of a Layout in any backend, and this combination will automatically switch to a tab representation for the bokeh backend.

Marginals

The Bokeh backend also supports marginal plots to generate adjoined plots. The most convenient way to build an AdjointLayout is with the .hist() method.


In [ ]:
points = hv.Points(np.random.randn(500,2))
points.hist(num_bins=51, dimension=['x','y'])

When the histogram represents a quantity that is mapped to a value dimension with a corresponding colormap, it will automatically share the colormap, making it useful as a colorbar for that dimension as well as a histogram.


In [ ]:
img.hist(num_bins=100, dimension=['x', 'y'], weight_dimension='z', mean_weighted=True) +\
img.hist(dimension='z')

HoloMaps

HoloMaps work in bokeh just as in other backends.


In [ ]:
hmap = hv.HoloMap({phase: img.clone(np.sin(x**2+y**2+phase))
                   for phase in np.linspace(0, np.pi*2, 6)}, kdims=['Phase'])
hmap.hist(num_bins=100, dimension=['x', 'y'], weight_dimension='z', mean_weighted=True)

Tools: Interactive widgets

Hover tools

Some Elements allow revealing additional data by hovering over the data. To enable the hover tool, simply supply 'hover' as a list to the tools plot option.


In [ ]:
%%opts Points [tools=['hover']] (size=5) HeatMap [tools=['hover']] Histogram [tools=['hover']] Layout [shared_axes=False]
error = np.random.rand(100, 3)
heatmap_data = {(chr(65+i),chr(97+j)):i*j for i in range(5) for j in range(5) if i!=j}
data = [np.random.normal() for i in range(10000)]
hist = np.histogram(data, 20)
hv.Points(error) + hv.HeatMap(heatmap_data).sort() + hv.Histogram(hist)

Selection tools

Bokeh provides a number of tools for selecting data points including box_select, lasso_select and poly_select. To distinguish between selected and unselected data points we can also set the unselected_color. You can try out any of these selection tools and see how the plot is affected:


In [ ]:
%%opts Points [tools=['box_select', 'lasso_select', 'poly_select']] (size=10 unselected_color='red' color='blue')
hv.Points(error)

Selection widget with shared axes and linked brushing

When dealing with complex multi-variate data it is often useful to explore interactions between variables across plots. HoloViews will automatically link the data sources of plots in a Layout if they draw from the same data, allowing for both linked axes and brushing.

We'll see what this looks like in practice using a small dataset of macro-economic data:


In [ ]:
macro_df = pd.read_csv('http://assets.holoviews.org/macro.csv', '\t')

By creating two Points Elements, which both draw their data from the same pandas DataFrame, the two plots become automatically linked. This linking behavior can be toggled with the shared_datasource plot option on a Layout or GridSpace. You can try selecting data in one plot, and see how the corresponding data (those on the same rows of the DataFrame, even if the plots show different data, will be highlighted in each.


In [ ]:
%%opts Scatter [tools=['box_select', 'lasso_select']] Layout [shared_axes=True shared_datasource=True]
hv.Scatter(macro_df, kdims=['year'], vdims=['gdp']) +\
hv.Scatter(macro_df, kdims=['gdp'], vdims=['unem'])

A gridmatrix is a clear use case for linked plotting. This operation plots any combination of numeric variables against each other, in a grid, and selecting datapoints in any plot will highlight them in all of them. Such linking can thus reveal how values in a particular range (e.g. very large outliers along one dimension) relate to each of the other dimensions.


In [ ]:
%%opts Scatter [tools=['box_select', 'lasso_select', 'hover'] border=0] Histogram {+axiswise}
table = hv.Table(macro_df, kdims=['year', 'country'])
matrix = hv.operation.gridmatrix(table.groupby('country'))
matrix.select(country=['West Germany', 'United Kingdom', 'United States'])

The Bokeh Elements tutorial shows examples of all the Elements supported for Bokeh, in a format that can be compared with the default matplotlib Elements tutorial.