Hi,
I’m new to Holoviz and I’m creating a proof of concept for real-time data analysis.
I’ve been able to stream my data to a real-time chart, and I would like to add some parameters (subtract two curves, and add an offset to a curve) for the user to interact with the chart.
To do so, I used a param.Parameterized
class. The subtraction works, but when I change the curve to subtract with my param.ObjectSelector
widget, then the chart breaks: at first the refreshing is “throttled” (the total cpu usage is barely 60-70% with 15% from the web navigator though) while the auto-range breaks and the curves disapear slowly leaving an empty chart.
I created my Panel app as follow:
import asyncio
import panel as pn, holoviews as hv
from utils.Frequencemetre import Frequencemetre
pn.config.sizing_mode = 'stretch_width'
hv.extension('bokeh')
frequencemetre = Frequencemetre()
pn.template.FastListTemplate(
site="Panel",
title="PoC",
sidebar=[*controls],
main=[
pn.Row(frequencemetre.view,frequencemetre.param)
]
).servable();
And my Frequencemetre class is as follow:
import param
import redis
import json
import holoviews as hv
import pandas as pd, numpy as np
from holoviews.streams import Buffer
from tornado.ioloop import PeriodicCallback
from tornado import gen
class Frequencemetre(param.Parameterized):
# left side of the subtraction
c1 = param.ObjectSelector(
default="Channel 1",
objects=["N/A", "Channel 1", "Channel 2", "Channel 3", "Channel 4"]
)
# right side of the subtraction
c2 = param.ObjectSelector(
default="Channel 2",
objects=["N/A", "Channel 1", "Channel 2", "Channel 3", "Channel 4"]
)
# my offset
offset = param.Number(0.0, precedence=0)
# I get my real time data from a Redis cache
r = redis.Redis()
df = pd.DataFrame({
'timestamp': np.array([]),
'var_0': np.array([]),
'var_1': np.array([]),
'var_2': np.array([]),
'var_3': np.array([]),
'var_4': np.array([]),
'var_5': np.array([]),
'var_6': np.array([]),
'var_7': np.array([])})
df.set_index('timestamp', inplace=True)
# this is the buffer containing my data
buffer = Buffer(data=df, length=1000)
# This function is called from my hv.DynamicMap and plots 8 curves
@param.depends('c1', 'c2', 'offset')
def sin_curves(self,data):
# I apply my transformation here, on the 8th curve, not elegant but I don't know if there's a better way
data["var_7"] = data["var_" + str(self.c1[-1])] - data["var_" + str(self.c2[-1])] + self.offset
return (
hv.Curve(data[["timestamp","var_0"]], label='Variable 0') *
hv.Curve(data[["timestamp","var_1"]], label='Variable 1') *
hv.Curve(data[["timestamp","var_2"]], label='Variable 2') *
hv.Curve(data[["timestamp","var_3"]], label='Variable 3') *
hv.Curve(data[["timestamp","var_4"]], label='Variable 4') *
hv.Curve(data[["timestamp","var_5"]], label='Variable 5') *
hv.Curve(data[["timestamp","var_6"]], label='Variable 6') *
hv.Curve(data[["timestamp","var_7"]], label='Variable 7')
)
# This is the function fetching the data
@gen.coroutine
def sin_data(self):
# Read the last element from the Redis list
data = self.r.lindex('random_sin', -1)
if data:
data = json.loads(data)
index = pd.to_datetime(data['timestamp'], unit='ms')
self.buffer.send(pd.DataFrame({
'timestamp': [index],
'var_0': [data['var_0']],
'var_1': [data['var_1']],
'var_2': [data['var_2']],
'var_3': [data['var_3']],
'var_4': [data['var_4']],
'var_5': [data['var_5']],
'var_6': [data['var_6']],
'var_7': [data['var_7']]}))
# Here is my function used to display the DynamicMap, as showed in the example
def view(self):
PeriodicCallback(self.sin_data, 100).start()
return hv.DynamicMap(self.sin_curves ,streams=[self.buffer]).opts(
width=1200,
height=600,
title='Sinusoides',
tools=['hover']
)
Is this the correct way to parameterize a real-time chart?
Custom Operations seemed promising but I had some trouble trying to add kdims
to my DynamicMap to create the widgets, even with functools.partial
.
Thanks in advance!