import numpy as np
import pandas as pd
import holoviews as hv
from bokeh.sampledata import stocks
from holoviews.operation.timeseries import rolling, rolling_outlier_std
from holoviews.streams import Stream
hv.notebook_extension('bokeh')
In the Data Processing Pipelines section we discovered how to declare a DynamicMap
and control multiple processing steps with the use of custom streams as described in the Responding to Events guide. Here we will use the same example exploring a dataset of stock timeseries and build a small dashboard using the paramNB
library, which allows us to declare easily declare custom widgets and link them to our streams. We will begin by once again declaring our function that loads the stock data:
%%opts Curve {+framewise}
def load_symbol(symbol, variable='adj_close', **kwargs):
df = pd.DataFrame(getattr(stocks, symbol))
df['date'] = df.date.astype('datetime64[ns]')
return hv.Curve(df, ('date', 'Date'), variable)
stock_symbols = ['AAPL', 'IBM', 'FB', 'GOOG', 'MSFT']
dmap = hv.DynamicMap(load_symbol, kdims='Symbol').redim.values(Symbol=stock_symbols)
dmap
Controlling stream events manually from the Python prompt can be a bit cumbersome. However since you can now trigger events from Python we can easily bind any Python based widget framework to the stream. HoloViews itself is based on param and param has various UI toolkits that accompany it and allow you to quickly generate a set of widgets. Here we will use paramnb
, which is based on ipywidgets
to control our stream values.
To do so we will declare a StockExplorer
class which inherits from Stream
and defines two parameters, the rolling_window
as an integer and the symbol
as an ObjectSelector. Additionally we define a view method, which defines the DynamicMap and applies the two operations we have already played with, returning an overlay of the smoothed Curve
and outlier Scatter
.
import param
import paramnb
class StockExplorer(hv.streams.Stream):
rolling_window = param.Integer(default=10, bounds=(1, 365))
symbol = param.ObjectSelector(default='AAPL', objects=stock_symbols)
def view(self):
stocks = hv.DynamicMap(load_symbol, kdims=[], streams=[self])
# Apply rolling mean
smoothed = rolling(stocks, streams=[self])
# Find outliers
outliers = rolling_outlier_std(stocks, streams=[self])
return smoothed * outliers
Now that we have defined this Parameterized
class we can instantiate it and pass it to the paramnb.Widgets function, which will display the widgets. Additionally we call the StockExplorer.view
method to display the DynamicMap.
%opts Curve [width=600] {+framewise} Scatter (color='red' marker='triangle')
explorer = StockExplorer()
paramnb.Widgets(explorer, continuous_update=True, callback=explorer.event, on_init=True)
explorer.view()
In HoloViews you have to declare the type of data that you want to display using Elements. Changing the types returned by a DynamicMap is generally not supported. Therefore ParamNB
provides the ability to completely redraw the output by replacing the object that is being displayed. Here we will extend the class we created above to draw directly to a view
parameter, which we can assign to. view
parameters like HTML
support a renderer
argument, which converts whatever you assign to the object to the correct representation. Therefore we will define a quick function that returns the HTML representation of our HoloViews object and also computes the size:
def render(obj):
renderer = hv.renderer('bokeh')
plot = renderer.get_plot(obj)
size = renderer.get_size(plot)
return renderer._figure_data(plot), size
Now we can extend the StockExplorer
class from above with a custom event
method. By default event
is called on Stream
instances to notify any subscribers. We can intercept this call when we want to redraw, here we will instead assign to our output
parameter whenever the variable
changes, which will trigger a full redraw:
class AdvancedStockExplorer(StockExplorer):
output = paramnb.view.HTML(renderer=render)
variable = param.ObjectSelector(default='adj_close', objects=[c for c in stocks.AAPL.keys() if c!= 'date'])
def event(self, **kwargs):
if self.output is None or 'variable' in kwargs:
self.output = self.view()
else:
super(AdvancedStockExplorer, self).event(**kwargs)
explorer = AdvancedStockExplorer()
paramnb.Widgets(explorer, continuous_update=True, callback=explorer.event, on_init=True)
As you can see using streams we have bound the widgets to the streams letting us easily control the stream values and making it trivial to define complex dashboards. paramNB
is only one widget framework we could use, we could also use paramBokeh
to use bokeh widgets and deploy the dashboard on bokeh server or manually linked ipywidgets
to our streams. For more information on how to deploy bokeh apps from HoloViews and build dashboards see the Deploying Bokeh Apps.