Hi @mehdi
You almost gave me a shock. I read your title as Using Param with Panel breaks my heart
I think you have many good things going in your example. The main thing to consider is make sure that you periodically read the data from redis only once for your entire application instead of once for each open user session. If you read data per user it could get slow.
You probably also want to set the length
of your buffer to not end up moving too many points around and displaying them. That would also be slow and could lead to a risk of crashing your browser.
So in my example below I have created a seperate SharedBuffer
class that is shared among all user sessions.
app.py
import panel as pn, holoviews as hv
from frequencemetre import SharedBuffer, Frequencemetre
pn.extension(sizing_mode = 'stretch_width')
hv.extension('bokeh')
ACCENT="#A01346"
# We will share the buffer instance among all users/ sessions
# to avoid having lots of instances all reading data from the redis cache
shared_buffer = pn.state.as_cached("shared_buffer", SharedBuffer)
frequencemetre = Frequencemetre(buffer=shared_buffer)
pn.template.FastListTemplate(
site="Panel",
title="Streaming Frequency Metre PoC",
accent_base_color=ACCENT, header_background=ACCENT,
main=[
pn.Row(frequencemetre.view,pn.panel(frequencemetre.param, width=150))
]
).servable()
frequencemetre.py
import holoviews as hv
import numpy as np
import pandas as pd
import param
from holoviews.streams import Buffer
from tornado import gen
from tornado.ioloop import PeriodicCallback
INITIAL_DATA = 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([]),
}
)
INITIAL_DATA.set_index("timestamp", inplace=True)
class SharedBuffer(param.Parameterized):
value = param.ClassSelector(class_=Buffer)
last_update = param.Date()
def __init__(self, **params):
params.update(
value = Buffer(data=INITIAL_DATA, length=1000),
last_update = pd.Timestamp.now(),
)
super().__init__(**params)
PeriodicCallback(self.sin_data, 500).start()
@gen.coroutine
def sin_data(self):
# Read the last element from the Redis list
# Just do something random in this example
N = 3
start = self.last_update
self.last_update = end = pd.Timestamp.now()
index = [start + (start-end)*float(i)/float(N) for i in range(N)]
data = pd.DataFrame(
{
"timestamp": index,
"var_0": np.random.rand(N),
"var_1": np.random.rand(N),
"var_2": np.random.rand(N),
"var_3": np.random.rand(N),
"var_4": np.random.rand(N),
"var_5": np.random.rand(N),
"var_6": np.random.rand(N),
"var_7": np.random.rand(N),
}
)
self.value.send(data)
class Frequencemetre(param.Parameterized):
c1 = param.ObjectSelector(
default="Channel 1",
objects=["Channel 1", "Channel 2", "Channel 3", "Channel 4"],
doc="left side of the subtraction"
)
c2 = param.ObjectSelector(
default="Channel 2",
objects=["Channel 1", "Channel 2", "Channel 3", "Channel 4"],
doc="right side of the subtraction",
)
offset = param.Number(0.0, precedence=0)
buffer = param.ClassSelector(class_=SharedBuffer, precedence=-1)
@param.depends("c1", "c2", "offset")
def sin_curves(self, data: pd.DataFrame):
"""This function is called from my hv.DynamicMap and plots 8 curves
I apply my transformation here, on the 8th curve, not elegant but I don't know if there's a better way"""
data = data.sort_values(by="timestamp").tail(100)
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")
)
def view(self):
return hv.DynamicMap(self.sin_curves, streams=[self.buffer.value]).opts(
responsive=True, height=600, title="Sinusoides", tools=["hover"], legend_position='top_left'
)
panel serve app.py
I am not an expert in using Tornado, PeriodicCallback
and @gen.coroutine
so whether that could be modernized using pn.state.schedule_task
and async
as in How to make the pn.state.schedule_task non-blocking? - Panel - HoloViz Discourse might be worth checking out. But your code works, so maybe its not worth it.
As your code example was not a minimum, reproducible example I can run I cannot say anything about the throttling and cpu usage. If you have problems with my example too, then please try to adjust my example (or similar) such that you can share it and I or others in the community can run it. We need to be able to reproduce what you experience in order to find the cause. Thanks.