I’m working on a UI which displays telemetry being regularly received from an instrument. The data arrives on disk; at regular intervals I poll for new data, and if present then I update the graphs.
The data is effectively 4-dimensional: a time series of multiple attributes of multiple sensors. I am forming the received data points (which arrive as Pandas DataFrames, one per time interval) into an xarray.DataArray and plotting it with hvplot.line.
Ideally, I would like the plots to update, but I can’t see any way to modify the DataArray.
Instead, I am clearing and recreating the view every update.
The users are complaining that the graphs update while they are interacting with them. I have partially fixed this by creating a Select widget for the chosen attribute, rather than relying on hvplot default behaviour, so that the selected option is “sticky”.
However the users are still complaining that the graph resets while they are zoomed into a point of interest.
Is there any way to read and re-apply the current zoom extents for example? I have tried using a hook function but could not get it to work.
I have looked at this existing post. The main difference is that I’m using xarray rather than a simple pandas DataFrame, and I can’t see how to update the array. I’ve also looked at this existing post, but my data updates are caused by a timer not an interactive user change.
The following code is very close to reproducing the issue. Note however that in my real application, data may arrive out of order; I can’t simply append new data on updates, as new dataframes may have appeared out of order.
import panel as pn
import pandas as pd
import xarray as xr
import hvplot.xarray
from datetime import datetime
import random
class Issue:
UPDATE_INTERVAL_MS = 5000
NUM_SENSORS = 5
def __init__(self):
self.state = {f'Sensor {i+1}': {'voltage': 0.0, 'current': 0.0} for i in range(self.NUM_SENSORS)}
self.data = []
self.data_timestamps = []
self.page = pn.Column()
self.choice_ctrl = pn.widgets.Select(name='Attribute', options=['voltage', 'current'])
self.do_update()
def make_dataframe(self):
for sensor_state in self.state.values():
sensor_state['voltage'] += random.uniform(-0.5, 0.5)
sensor_state['current'] += random.uniform(-0.5, 0.5)
return pd.DataFrame.from_dict(self.state, orient='index')
def do_update(self):
self.data.append(self.make_dataframe())
self.data_timestamps.append(datetime.now())
alldata = xr.concat([xr.DataArray(dataframe) for dataframe in self.data], dim='timestamp')
alldata = alldata.rename({'dim_0': 'sensor', 'dim_1': 'attribute'})
alldata['timestamp'] = self.data_timestamps
alldata.name = 'value'
self.page.clear()
alldata_interactive = alldata.interactive(loc='right_top')
chosen_attribute_i = alldata_interactive.sel(attribute=self.choice_ctrl)
self.page.append(chosen_attribute_i.hvplot.line(by='sensor'))
def run(self):
pn.state.add_periodic_callback(lambda : self.do_update(), self.UPDATE_INTERVAL_MS)
self.page.servable()
iss = Issue()
iss.run()
I ran this in a virtual environment with the following packages installed:
panel==1.4.5
hvplot==0.10.0
xarray==2024.7.0
jupyter==1.1.1
jupyter_bokeh==4.0.5