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. Note also that the legends are interactive and can be toggled by clicking on its entries. Additionally the style of unselected curve can be controlled by setting the muted_alpha and muted_color style options.


In [ ]:
%%opts Overlay [width=600 legend_position='top_left'] Curve (muted_alpha=0.5 muted_color='black')
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')

Categorical axes

A number of Elements will also support categorical (i.e. string types) as dimension values, these include HeatMap, Points, Scatter, Curve, ErrorBar and Text types.

Here we create a set of points indexed by ascending alphabetical x- and y-coordinates and values multiplying the integer index of each coordinate. We then overlay a HeatMap of the points with the points themselves enabling the hover tool for both and scaling the point size by the 'z' coordines.


In [ ]:
%%opts Points [size_index='z' tools=['hover']] HeatMap [toolbar='above' tools=['hover']]
points = hv.Points([(chr(i+65), chr(j+65), i*j) for i in range(10) for j in range(10)], vdims=['z'])
hv.HeatMap(points) * points

In the example above both axes are categorical because a HeatMap by definition represents 2D categorical coordinates (unlike Image and Raster types). Other Element types will automatically infer a categorical dimension if the coordinates along that dimension include string types.

Here we will generate random samples indexed by categories from 'A' to 'E' using the Scatter Element and overlay them. Secondly we compute the mean and standard deviation for each category and finally we overlay these two elements with a curve representing the mean value and a text element specifying the global mean. All these Elements respect the categorical index, providing us a view of the distribution of values in each category:


In [ ]:
%%opts Overlay [show_legend=False height=400 width=600] ErrorBars (line_width=5) Scatter(alpha=0.2 size=6)

overlay = hv.NdOverlay({group: hv.Scatter(([group]*100, np.random.randn(100)*(i+1)+i))
                        for i, group in enumerate(['A', 'B', 'C', 'D', 'E'])})

errorbars = hv.ErrorBars([(k, el.reduce(function=np.mean), el.reduce(function=np.std))
                          for k, el in overlay.items()])

global_mean = hv.Text('A', 12, 'Global mean: %.3f' % overlay.dimension_values('y').mean())

errorbars * overlay * hv.Curve(errorbars) * global_mean

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] Image [width=400 height=400]
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')

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 tap, box_select, lasso_select and poly_select. To distinguish between selected and unselected data points we can also control the color and alpha of the selection and nonselection points. You can try out any of these selection tools and see how the plot is affected:


In [ ]:
%%opts Points [tools=['box_select', 'lasso_select', 'tap']] (size=10 nonselection_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.