Combining periodic and widget stream to update plot fails for widget changes

Hello everyone,

I am working on a script that periodically updates and plots a dataset using Holoviews and Panel. The script is designed to allow dynamic selection of the y-axis for the plot. However, I am encountering an issue where the axis selection does not respond to changes in the col_selector widget.

Here is the code I am using:

import pandas as pd
import numpy as np
import panel as pn
import hvplot.pandas
import param
import holoviews as hv
import datetime as dt
import time
pn.extension()

columns = list('XYZ')

def create_df():
    return pd.DataFrame(columns=columns,data=np.random.randn(20,3))

class Selector(hv.streams.Stream):
    y = param.Selector(objects='YZ',default='Y')
col_selector = Selector()

dfstream = hv.streams.Buffer(data=pd.DataFrame(columns=columns),length=90,index=False)
dfstream.send(create_df())

colormap = dict(zip(['Y','Z'],hv.Cycle('Category10').values))
def plot(data,y):
    scatter =  data.hvplot.scatter(x='X',y=y,c=colormap[y],xlim=(-5,5),ylim=(-5,5),grid=True)
    return scatter+hv.operation.histogram(scatter).opts(color=colormap[y])

dmap = hv.DynamicMap(plot, streams=[dfstream,col_selector])
latest_update = pn.widgets.StaticText()

def update_plot():
    latest_update.value = f'updating: {dt.datetime.now().strftime("%c")}'
    time.sleep(0.5)
    dfstream.send(create_df())
    latest_update.value = f'latest: {dt.datetime.now().strftime("%c")}'

cb = pn.state.add_periodic_callback(update_plot, period=1000, start=False)    

button = pn.widgets.Button(name = 'Click to Start', button_type = 'success')
def button_click(event):
    if button.name == 'Click to Start':
        print('started')
        cb.start()
        button.name = 'Click to Stop'
        button.button_type = 'danger'
    else:
        print('stopped')
        cb.stop()
        button.name ='Click to Start'
        button.button_type = 'success'
button.on_click(button_click)

pn.Column(
    button,
    pn.Param(col_selector),
    pn.Column(
        dmap
    ),
    latest_update
)

This script uses two streams (dfstream and col_selector) for the DynamicMap update. The dfstream is updated periodically by the update_plot function, and the col_selector allows the user to select the y-axis for the plot. However, the plot does not update to reflect changes in the col_selector widget.

In general, I’ve struggled to combine buffer updates with other updates. Seems it should be possible, but all my attempts so far failed at some point.
Any help would be appreciated.

The col selector widget seems a bit strange; I’d use pn.widgets.Select instead.

Also, for reference:
https://medium.com/@pYdeas/transform-a-python-script-into-an-interactive-web-app-73fa3b304cdf

Thanks for you quick reply.

The reason I’m using this unusual streams object is because I can’t seem to figure out how to combine actual streams objects like Buffer and normal widgets as arguments in the DynamicMap call.

It seems to be either using a list of streams objects: Working with Streaming Data — HoloViews v1.18.3

dfstream = Buffer(example, length=100, index=False)
curve_dmap = hv.DynamicMap(hv.Curve, streams=[dfstream])

or using dict of widgets (and alike): Responding to Events — HoloViews v1.18.3

bank_dmap = hv.DynamicMap(table, streams=dict(total=account.param.balance, overdraft=account.param.overdraft, owner=account.param.name))

Combining them e.g. trying to use a buffer in the dict format:

col_selector = pn.widgets.Select(options=list('YZ'))
dmap = hv.DynamicMap(plot, streams=dict(data=dfstream,y=col_selector))

gives me errors like

TypeError: Cannot handle value Buffer(data= X Y Z
0 -0.009899 0.895101 -1.009318
1 -1.274712 0.199677 -0.495488
2 1.331487 0.342875 -1.393973
3 -1.433471 -0.084139 -0.745656
4 -0.446742 1.058923 -0.298400
5 -0.610556 -0.299927 -0.346697
6 -0.360474 0.816891 -0.140833
7 -2.654756 -0.171564 -0.584830
8 0.873773 0.119052 0.385048
9 -0.921049 -0.662527 0.190355
10 1.645390 1.499426 -0.759081
11 -1.758438 1.474617 -0.018315
12 -0.526570 1.233248 0.484089
13 0.291056 1.544265 -0.460464
14 1.915952 1.523159 0.816997
15 -0.617101 -0.156388 -1.139641
16 1.012438 0.828870 -0.739160
17 2.112561 1.764959 -0.400179
18 0.822183 -1.369521 1.273394
19 -1.911799 -0.787761 -1.147696) in streams dictionary

What am I missing?

col_selector = pn.widgets.Select(options=list('YZ'))
dmap = hv.DynamicMap(plot, streams=dict(data=dfstream,y=col_selector.param.value))

I think

The error occurs for a Buffer in a dict-type argument for hv.DynamicMap and I’m hoping to use a Buffer for periodic updating.
Panel widgets and dict-type arguments work just fine, but not what I need. :man_shrugging:

col_selector = pn.widgets.Select(options=list('YZ'))
data = pn.widgets.DataFrame(value=create_df()) # replace buffer with panel widget
# update this
dmap = hv.DynamicMap(plot, streams=dict(data=data,y=col_selector)) 

Since Buffer arguments error as the dict-type arguments, I was trying to create streams-list like argument for widgets. Hence the class Selector(hv.streams.Stream) instance. But, as mentioned, these widgets doesn’t seem to trigger updates in the panel.

Any idea how to handle the combination of Streams and widgets?

When using the dict approach you (currently) need to provide dfstream with dfstream.param.data.

import pandas as pd
import numpy as np
import panel as pn
import hvplot.pandas
import param
import holoviews as hv
import datetime as dt
import time
pn.extension()

columns = list('XYZ')

def create_df():
    return pd.DataFrame(columns=columns,data=np.random.randn(20,3))

col_selector = pn.widgets.Select(options=['Y', 'Z'],value='Y')

dfstream = hv.streams.Buffer(data=pd.DataFrame(columns=columns),length=90,index=False)
dfstream.send(create_df())

colormap = dict(zip(['Y','Z'],hv.Cycle('Category10').values))

def plot(data,y):
    scatter =  data.hvplot.scatter(x='X',y=y,c=colormap[y],xlim=(-5,5),ylim=(-5,5),grid=True)
    return scatter+hv.operation.histogram(scatter).opts(color=colormap[y])

dmap = hv.DynamicMap(plot, streams=dict(data=dfstream.param.data,y=col_selector.param.value))
latest_update = pn.widgets.StaticText()

def update_plot():
    latest_update.value = f'updating: {dt.datetime.now().strftime("%c")}'
    time.sleep(0.5)
    dfstream.send(create_df())
    latest_update.value = f'latest: {dt.datetime.now().strftime("%c")}'

cb = pn.state.add_periodic_callback(update_plot, period=1000, start=False)    

button = pn.widgets.Button(name = 'Click to Start', button_type = 'success')
def button_click(event):
    if button.name == 'Click to Start':
        print('started')
        cb.start()
        button.name = 'Click to Stop'
        button.button_type = 'danger'
    else:
        print('stopped')
        cb.stop()
        button.name ='Click to Start'
        button.button_type = 'success'
button.on_click(button_click)

pn.Column(
    button,
    col_selector,
    pn.Column(
        dmap
    ),
    latest_update
).servable()

I found out by inspecting the HoloViews code. I’ve reported this as a bug in Buffer does not work as dict argument to Dynamic Map · Issue #6229 · holoviz/holoviews (github.com). Don’t know if it is a bug.

1 Like