Search code examples
pythonbokehdashboardinteractiveholoviews

How to create a dashboard with widgets (selector) and interactivity (tap stream) between plots in HoloViews/Bokeh?


I'm trying to create a dashboard that consists of two plots (heatmap and line graph) and one widget (selector):

  • When you select an option from widget both plots get updated;
  • When you tap on the first plot the second plot is updated based on tap info.

enter image description here

Currently I'm trying to do it in HoloViews. It seems that this should be very easy to do but I somehow can't wrap my head around it.

The code below shows how it should look like. However, the selector is not connected in any way to the dashboard since I don't know how to do it.

import pandas as pd
import numpy as np

import panel as pn
import holoviews as hv


hv.extension('bokeh')


def create_test_df(k_features, n_tickers=5, m_windows=5):
    start_date = pd.Timestamp('01-01-2020')
    window_len = pd.Timedelta(days=1)
    cols = ['window_dt', 'ticker'] + [f'feature_{i}' for i in range(k_features)]
    data = {c: [] for c in cols}
    
    for w in range(m_windows):
        window_dt = start_date + w*window_len
        for t in range(n_tickers):
            ticker = f'ticker_{t}'
            data['window_dt'].append(window_dt)
            data['ticker'].append(ticker)
            for f in range(k_features):
                data[f'feature_{f}'].append(np.random.rand())
    return pd.DataFrame(data)

k_features = 3
features = [f'feature_{i}' for i in range(k_features)]
df = create_test_df(k_features)

selector = pn.widgets.Select(options=features)
heatmap = hv.HeatMap(df[['window_dt', 'ticker', f'{selector.value}']])
posxy = hv.streams.Tap(source=heatmap, x='01-01-2020', y='ticker_4')

def tap_heatmap(x, y):
    scalar = np.random.randn()
    x = np.linspace(-2*np.pi, 2*np.pi, 100)
    data = list(zip(x, np.sin(x*scalar)))
    return hv.Curve(data)

pn.Row(heatmap, hv.DynamicMap(tap_heatmap, streams=[posxy]), selector)

Solution

  • Ok I finally got it. It turned out to be simple (just as expected) but not quite intuitive. Basically, different approach for implementing selector (dropdown menu) should be used. Working code for such example is below:

    import pandas as pd
    import numpy as np
    
    import panel as pn
    import holoviews as hv
    
    
    hv.extension('bokeh')
    
    
    def create_test_df(k_features, n_tickers=5, m_windows=5):
        start_date = pd.Timestamp('01-01-2020')
        window_len = pd.Timedelta(days=1)
        cols = ['window_dt', 'ticker'] + [f'feature_{i}' for i in range(k_features)]
        data = {c: [] for c in cols}
        
        for w in range(m_windows):
            window_dt = start_date + w*window_len
            for t in range(n_tickers):
                ticker = f'ticker_{t}'
                data['window_dt'].append(window_dt)
                data['ticker'].append(ticker)
                for f in range(k_features):
                    data[f'feature_{f}'].append(np.random.rand())
        return pd.DataFrame(data)
    
    
    def load_heatmap(feature):
        return hv.HeatMap(df[['window_dt', 'ticker', f'{feature}']])
    
    
    def tap_heatmap(x, y):
        scalar = np.random.randn()
        x = np.linspace(-2*np.pi, 2*np.pi, 100)
        data = list(zip(x, np.sin(x*scalar)))
        return hv.Curve(data)
    
    
    k_features = 3
    features = [f'feature_{i}' for i in range(k_features)]
    df = create_test_df(k_features)
    
    heatmap_dmap = hv.DynamicMap(load_heatmap, kdims='Feature').redim.values(Feature=features)
    posxy = hv.streams.Tap(source=heatmap_dmap, x='01-01-2020', y='ticker_0')
    sidegraph_dmap = hv.DynamicMap(tap_heatmap, streams=[posxy])
    
    pn.Row(heatmap_dmap, sidegraph_dmap)