Player widget watch multiple widgets

Hello,

I have a notebook designed to plot data of different subjects and different runs. Each data set, depending on the run, has a different number of data points (in my case each data point is a point in time). I have select widgets that allow me to select the subject and run I wish to plot. Linked to the plot, I have a player so I can look at the data progress over time. However, I am having trouble updating the end value of the player when changing the subject or run.

Bellow is an example of how my data is set up.

  1. Declare a directory of the data where the keys are the subject names and the values are a list of tuples as such, (run number, number of time points for that run). A list of the subjects is created from the dictionary.
  2. I then create the widgets that allow me to select the subject and run I want to plot. I was able to create the run selector dependent on the subject selector using a function “update_run()” and param.watch.
  3. I then created a function “get_num_tp()” that finds the number of time points for a selected set of data, which is dependent on the subject and run. This is done by using the dictionary created in step 1. This function is not necessary but I find it helpful.
  4. I then create the player were the end value is generated from the “get_num_tp()” function. I use a similar method from step 2 where I create a function “update_player()” that updates the end value of the player and param.watch to watch both the subject and run value. This is where things go wrong. I always get an error “TypeError: update_player() missing 1 required positional argument: ‘event2’” when executing the code.
  5. This “print_player_value()” function is only there for example purposes. I prints the player value so I can see if the player changes when I change the run or subject.
  6. Lastly I display the panel with the subject selector, run selector, and player.

CODE:

data_dict={'sub-01':[('run1',100),('run2',100),('run3',60)],'sub-02':[('run1',200),('run2',100),('run3',50)],'sub-03':[('run1',100),('run2',400)]}
subject_list = list(data_dict.keys())

subject_select = pn.widgets.Select(name='Select Subject',options=subject_list,value=subject_list[0])
run_select     = pn.widgets.Select(name='Select Run', options=[data_dict[subject_select.value][i][0] for i in range(0,len(data_dict[subject_select.value]))])
def update_run(event):
    run_select.options = [data_dict[event.new][i][0] for i in range(0,len(data_dict[event.new]))]
subject_select.param.watch(update_run,'value')

@pn.depends(subject_select.param.value,run_select.param.value)
def get_num_tp(SBJ,RUN):
    num_tp = [data_dict[subject_select.value][i][1] for i in range(0,len(data_dict[subject_select.value])) if data_dict[subject_select.value][i][0] == run_select.value][0]
    return num_tp

player = pn.widgets.Player(name='Player', start=0, end=get_num_tp(subject_select.value,run_select.value), value=1,loop_policy='loop', width=800, step=1)
def update_player(event1,event2):
    player.end = get_num_tp(event1.new,event2.new)
subject_select.param.watch(update_player,'value')
run_select.param.watch(update_player,'value')

@pn.depends(player.param.value)
def print_player_value(value):
    value = str(value)
    markdown = pn.pane.Markdown(value)
    return markdown

pn.Column(pn.Row(subject_select,run_select),player,print_player_value)

Note that I actually have 3 widgets to watch in my data but I only use 2 in this example for simplicity. I figure it isn’t that much different having 2 or 3 dependent widgets.

If you have any advice or can give any help I would greatly appreciate it.

Hi @IsabelF98

I can also get the TypeError: update_player() missing 1 required positional argument: 'event2' error.

The cause is this part

def update_player(event1,event2):
    player.end = get_num_tp(event1.new,event2.new)
subject_select.param.watch(update_player,'value')
run_select.param.watch(update_player,'value')

The update_player function takes two arguments (event1 and event2). But you then use that as a callback in subject_select.param.watch(update_player,'value').

When the value of subject_select changes it will trigger the update_player function but only provide subject_select.value as a positional argument. Thus no second positional argument for event2 is provided.

You will have to restructure your code to achieve what you want. I will try to find a solution and post below.

If you change the problematic code to

@pn.depends(subject_select, run_select, watch=True)
def update_player(subject, run):
    player.end = get_num_tp(subject,run)

Then it removes the error and as a bonus simplifies the code.


The working code is below for reference

import panel as pn
from panel.util import value_as_date
import param

data_dict={'sub-01':[('run1',100),('run2',100),('run3',60)],'sub-02':[('run1',200),('run2',100),('run3',50)],'sub-03':[('run1',100),('run2',400)]}
subject_list = list(data_dict.keys())

subject_select = pn.widgets.Select(name='Select Subject',options=subject_list,value=subject_list[0])
run_select     = pn.widgets.Select(name='Select Run', options=[data_dict[subject_select.value][i][0] for i in range(0,len(data_dict[subject_select.value]))])
def update_run(event):
    run_select.options = [data_dict[event.new][i][0] for i in range(0,len(data_dict[event.new]))]
subject_select.param.watch(update_run,'value')

@pn.depends(subject_select.param.value,run_select.param.value)
def get_num_tp(SBJ,RUN):
    num_tp = [data_dict[subject_select.value][i][1] for i in range(0,len(data_dict[subject_select.value])) if data_dict[subject_select.value][i][0] == run_select.value][0]
    return num_tp

player = pn.widgets.Player(name='Player', start=0, end=get_num_tp(subject_select.value,run_select.value), value=1,loop_policy='loop', width=800, step=1)

@pn.depends(subject_select, run_select, watch=True)
def update_player(subject, run):
    player.end = get_num_tp(subject,run)

@pn.depends(player.param.value)
def print_player_value(value):
    value = str(value)
    markdown = pn.pane.Markdown(value)
    return markdown

pn.Column(pn.Row(subject_select,run_select),player,print_player_value).servable()

If you look carefully at the video above I believe the end value is not being set correctly.

I took the liberty to simplify the fix the problem, simplify the code and blacken the code

import panel as pn
import param

data_dict = {
    "sub-01": [("run1", 100), ("run2", 100), ("run3", 60)],
    "sub-02": [("run1", 200), ("run2", 100), ("run3", 50)],
    "sub-03": [("run1", 100), ("run2", 400)],
}
subject_list = list(data_dict.keys())

subject_select = pn.widgets.Select(
    name="Select Subject", options=subject_list, value=subject_list[0]
)
run_select = pn.widgets.Select(
    name="Select Run",
    options=[
        data_dict[subject_select.value][i][0]
        for i in range(0, len(data_dict[subject_select.value]))
    ],
)


def get_num_tp(sbj, run):
    for item in data_dict[sbj]:
        if item[0] == run:
            return item[1]
    return 0


player = pn.widgets.Player(
    name="Player",
    start=0,
    end=get_num_tp(subject_select.value, run_select.value),
    value=1,
    loop_policy="loop",
    width=800,
    step=1,
)


@pn.depends(subject_select, watch=True)
def update_run(subject):
    run_select.options = [item[0] for item in data_dict[subject]]


@pn.depends(subject_select, run_select, watch=True)
def update_player(subject, run):
    end_value = get_num_tp(subject, run)
    player.value = min(player.value, end_value)
    player.end = end_value


@pn.depends(player)
def print_player_value(value):
    value = str(value)
    markdown = pn.pane.Markdown(value)
    return markdown


pn.Column(pn.Row(subject_select, run_select), player, print_player_value).servable()

One further suggestion to improve the code/ app would be to change the data_dict structure from a dictionary of lists to a dictionary of dictionaries. That would make look ups much easier. I.e something like

data_dict2 = {
    "sub-01": {"run1": 100, "run2": 100, "run3": 60},
    "sub-02": {"run1": 200, "run2": 100, "run3": 50},
    "sub-03": {"run1": 100, "run2": 400},
}

Thank you very much Marc. This is really helpful.

1 Like