Why is this Scatter Plot Not Updating With Widget Selection?

I want a Panel app with dynamic plots, and different plots depending on which menu/button is selected from a sidepanel, so I started from this great answer to a previous q: Multi page app documentation - #2 by maximlt?

Each page had a sine & cosine plot which updated based on widget values, using @pn.depends.

Then I updated the code so that the sine plot would be a scatter plot, and the widget would be a selection drop-down menu instead of a slider. But now that scatter plot is not updating when I update the selection drop-down widget. What am I doing wrong? Clearly I’m misunderstanding something about what @pn.depends() is doing or not doing. Any help would be super appreciated!

Full code below:

import pandas as pd
import panel as pn
import holoviews as hv
import hvplot.pandas
import numpy as np
import time
from datetime import datetime

pn.extension()

template = pn.template.FastListTemplate(title='My Dashboard')

# load detailed data
durations = np.random.randint(0, 10, size=10)
activity_codes = ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3', 'C3']
activity_categories = ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C', 'C']

df = pd.DataFrame({'Duration': durations,
                   'ActivityCode': activity_codes,
                   'ActivityCategory': activity_categories})

# Page 1 Widget Controls
ac_categories = ['A', 'B', 'C']
ac_cat_radio_button = pn.widgets.Select(name='Activity Category', options=ac_categories)


# Page 1 Plotting Code
@pn.depends(ac_cat=ac_cat_radio_button)
def scatter_detail_by_ac(df, ac_cat):
    print(f"ac_cat is {ac_cat}")
    print(f"ac_cat.value is {ac_cat.value}")
    df_subset = df.loc[df.ActivityCategory==ac_cat.value]

    print(f"number of records in df_subset to scatter plot: {len(df_subset):,}")
    return df_subset.hvplot.scatter(x='ActivityCode', y='Duration')


freq2 = pn.widgets.FloatSlider(name="Frequency", start=0, end=10, value=2)
phase2 = pn.widgets.FloatSlider(name="Phase", start=0, end=np.pi)
xs = np.linspace(0,np.pi)
@pn.depends(freq=freq2, phase=phase2)
def cosine(freq, phase):
    return hv.Curve((xs, np.cos(xs*freq+phase))).opts(
        responsive=True, min_height=400)

page = pn.Column(sizing_mode='stretch_width')

content1 = [
    pn.Row(ac_cat_radio_button), #grouping_vars_radio_button),
    scatter_detail_by_ac(df, ac_cat_radio_button),
]
content2 = [
    pn.Row(freq2, phase2),
    hv.DynamicMap(cosine),
]

link1 = pn.widgets.Button(name='Scatter')
link2 = pn.widgets.Button(name='Cosine')

template.sidebar.append(link1)
template.sidebar.append(link2)

template.main.append(page)

def load_content1(event):
    template.main[0].objects = content1


def load_content2(event):
    template.main[0].objects = content2

link1.on_click(load_content1)
link2.on_click(load_content2)

template.show()

Also, here’s the versions I’m using, from my requirements.in (which I pip-compile into a requirements.txt / lockfile):

panel==v1.0.0rc6
pandas==1.5.3
holoviews==1.16.0a2
hvplot
pandas-gbq>=0.19.1

Change the function to:

@pn.depends(ac_cat=ac_cat_radio_button)
def scatter_detail_by_ac(ac_cat):
    print(f"ac_cat is {ac_cat}")
    df_subset = df.loc[df.ActivityCategory==ac_cat]

    print(f"number of records in df_subset to scatter plot: {len(df_subset):,}")
    return df_subset.hvplot.scatter(x='ActivityCode', y='Duration')

And the following lines:

content1 = [
    pn.Row(ac_cat_radio_button), #grouping_vars_radio_button),
    scatter_detail_by_ac,
]

Awesome, thanks! Confirmed it’s working on my end as well.

Just to make sure I understand what’s going on here, my interpretation of this solution is that:

reactive functions which depend on some widget parameter must not accept any non-reactive/non-widget function parameters or it breaks their reactivity. Any other objects these functions may need (e.g. my df dataframe) must be referenced as global vars, not passed to the function.

…is that correct? And if so, any documentation or source code you can point me to on why this is?

No hard feelings if this follow-up is too much of an ask, really appreciate your quick solution already. Thanks again!

The more important thing is that you should not call a bound function. Pass it into a pn.Column or pn.Row as a function.

A guide could be this: Make your component interactive — Panel v1.0.0rc9