Here is the output of my interactive component:
It’s still a little rough around the edges, but before I dive into the details, I’d like to know if this GENERAL approach is the correct (most performant) way to use HoloViews:
"""EventWindow can logs visualized"""
import pandas as pd
import numpy as np
import param
import holoviews as hv
from holoviews import opts
import panel as pn
import hvplot.pandas #noqa
class EventWindowViewer(pn.viewable.Viewer):
"""Display Interactive EventWindows"""
df = param.DataFrame(pd.DataFrame())
selected_alerts = param.ListSelector(default=[])
selected_signals = param.ListSelector(default=[])
selected_states = param.ListSelector(default=[])
def __init__(self, **params):
super().__init__(**params)
self._layout = pn.Row(
pn.WidgetBox(
'Controls',
self.param.selected_alerts,
self.param.selected_signals,
self.param.selected_states,
width=200,
sizing_mode='fixed',
),
self.get_plot,
)
@param.depends('df', watch=True, on_init=True)
def _prepare_df(self):
"""Add _time column, add sig_type column, sort by time"""
self.df['_time'] = pd.to_datetime(self.df['epoch_ms'], unit='ms')
self.df = self.df.sort_values(by=['_time'])
def classify_sig_type(row):
"""Classify sig_name into `state`, `signal`, `alert` """
if row['alert_name'] == None:
if 'state' in row['sig_name']:
return 'state'
else:
return 'signal'
else:
return 'alert'
self.df['sig_type'] = self.df.apply(classify_sig_type, axis=1)
@param.depends('df', watch=True, on_init=True)
def _prepare_selectors(self):
"""Set the options for the signal, state, alert selectors"""
self.param.selected_alerts.objects = list(self.df[self.df['sig_type'] == 'alert']['alert_name'].unique())
self.param.selected_signals.objects = list(self.df[self.df['sig_type'] == 'signal']['sig_name'].unique())
self.param.selected_states.objects = list(self.df[self.df['sig_type'] == 'state']['sig_name'].unique())
@param.depends('df', watch=True, on_init=True)
def _prepare_ds(self):
"""Annotate the data, set view options"""
kdims = [
hv.Dimension('_time', label='Datetime', unit='ms'),
hv.Dimension('sig_type', label='Signal Type', values=['state', 'signal', 'alert'])
]
ds = hv.Dataset(self.df, kdims=kdims+['sig_name'], vdims=['sig_value', 'alert_name', 'sig_text'])
self.alerts = ds.select(sig_type='alert')\
.to(hv.Scatter, '_time', 'alert_name').overlay('sig_name').collate(drop=['sig_type'])
self.signals = ds.select(sig_type='signal')\
.to(hv.Curve, '_time', 'sig_value').overlay('sig_name').collate(drop=['sig_type'])
self.states = ds.select(sig_type='state')\
.to(hv.Curve, '_time', 'sig_text').overlay('sig_name').collate(drop=['sig_type'])
# style
curve_opts = opts.Curve(interpolation='steps-post', width=1000)
self.alerts.opts(
opts.Scatter(height=100, width=1000, size=5, ), opts.NdOverlay(title='Alerts')
)
self.states.opts(
curve_opts, opts.Curve(height=200), opts.NdOverlay(title='States'),
)
self.signals.opts(
curve_opts, opts.NdOverlay(title='Signals', legend_position='top'),
)
@param.depends('selected_signals', 'selected_states')
@pn.io.profile('get_plot', engine='snakeviz')
def get_plot(self):
return hv.Layout(
self.alerts +
self.signals.select(sig_name=self.selected_signals) +
self.states.select(sig_name=self.selected_states)
).cols(1)
def __panel__(self):
return self._layout
d = {
'epoch_ms': [],
'sig_name': [],
'alert_name': [],
'sig_text': [],
'sig_value': [],
}
for epoch in range(0, 40000, 40):
for sig in ['sig1', 'sig2', 'sig3']:
d['epoch_ms'].append(epoch)
d['sig_name'].append(sig)
d['alert_name'].append(None)
d['sig_text'].append(None)
d['sig_value'].append(np.random.randint(0, 10))
d['epoch_ms'].extend([2, 5, 15, 17, 19, 20])
d['sig_name'].extend(['alert_1', 'state_a', 'state_b', 'alert_2', 'state_a', 'state_b'])
d['alert_name'].extend(['a1', None, None, 'a2', None, None])
d['sig_text'].extend([None, 'Hello', 'World', None, 'Good', 'Bye'])
d['sig_value'].extend([1, None, None, 2, None, None])
EventWindowViewer(df=pd.DataFrame(d)).servable()
I’m asking because I’m noticing an unfortunate amount of delay between the widget selections and the plots re-rendering. My company has an older, similar app using raw Bokeh which seems to be faster. I’m hoping that we can “upgrade” to holoviews, but if the app is significantly slower that’s a tough sell.
Am I doing anything here that is obviously slowing things down? What kind of restructuring might speed things up? Are there any best practices that I’m violating?
Note that I care less about initialization time than I do about interactive re-rendering time
Thank you in advance for any suggestions
P.S., is there any way to share a vertical CrossHairTool between all three plots??