Linking Tabulator with a HV plot

I would like to have a plot parameterized by a DataFrame (shown with panel.widgets.Tabulator), which will react to parameters being changed by recomputing the data to plot and refreshing the plot. I still cannot find a way to properly cause the refresh. Could someone point the obvious to me?

I considered using interact though it does not seem to support DataFrame as parameter type (I would be glad to be wrong here), param.Parametrized subclass (where I was not sure how to set widget type for the DataFrame parameter) and then finally the lower-level links. Any of those solutions would be good for me.

The following MWE computes sine wave for various period/amplitudes given by the table and plots them, but it won’t update when I change a value in the table.

pars=pd.DataFrame({'name':['A','B','C'],'period':[1,.5,.3],'amplitude':[.3,.4,.1]})
def compute(pp):
    xx=np.linspace(0,1,100)
    arr=pp['amplitude'].to_numpy()*np.sin(np.outer(2*np.pi*xx,1./pp['period'].to_numpy()))
    return pd.DataFrame(dict([(pp['name'][i],arr[:,i]) for i in range(arr.shape[1])]),index=xx)
tabedit=pn.widgets.Tabulator(value=pars,show_index=False)
plot=compute(tabedit.value).hvplot(grid=True)
tabedit.jslink(plot,value='cds')
col=pn.Column(tabedit,plot)
col

Thanks!

Hi @eudoxos

Welcome to the community. The trick is to bind to the selection parameter. Unfortunately the selected_dataframe is just a normal property and not a parameter that can be bound to.

import panel as pn
import hvplot.pandas
import pandas as pd
import numpy as np

pn.extension(sizing_mode="stretch_width")

lookup = {0: "A", 1: "B", 2: "C"}

pars = pd.DataFrame(
    {"name": ["A", "B", "C"], "period": [1, 0.5, 0.3], "amplitude": [0.3, 0.4, 0.1]}
)
xx = np.linspace(0, 1, 100)
arr = pars["amplitude"].to_numpy() * np.sin(
    np.outer(2 * np.pi * xx, 1.0 / pars["period"].to_numpy())
)
curves = pd.DataFrame(dict([(pars["name"][i], arr[:, i]) for i in range(0, 3)]), index=xx)


def compute_plot(selection):
    if not selection:
        selection_df = curves
    else:
        selection = [lookup[i] for i in selection]
        selection_df = curves[selection]
    return selection_df.hvplot(grid=True)


tabedit = pn.widgets.Tabulator(
    value=pars, show_index=False, selectable=True, disabled=True, theme="site", height=140
)
plot = pn.bind(compute_plot, selection=tabedit.param.selection)

pn.template.FastListTemplate(
    site="Awesome Panel", title="Table Selection", main=[tabedit, plot]
).servable()

Thanks for your attention to my problem, I appreciate that; and the welcome :slight_smile: . I actually don’t need to show/hide curves but rather recompute the curves as I edit parameters in the table (changing the period or amplitude). What would be the modification necessary for that?

(As a side note, if I run your example with pn.serve(...), it runs nicely (the selection hides/shows the curves) but not when I run it in jupyter lab, then the curves are not affected.)

Hi @eudoxos

To react to changes in the table/ dataframe you would just change the compute_plot function accordingly and bind to tabedit or tabedit.param.value instead.

import panel as pn
import hvplot.pandas
import pandas as pd
import numpy as np

pn.extension(sizing_mode="stretch_width")

lookup = {0: "A", 1: "B", 2: "C"}

pars = pd.DataFrame(
    {"name": ["A", "B", "C"], "period": [1, 0.5, 0.3], "amplitude": [0.3, 0.4, 0.1]}
)
xx = np.linspace(0, 1, 100)

def compute_plot(value):
    arr = value["amplitude"].to_numpy() * np.sin(
        np.outer(2 * np.pi * xx, 1.0 / value["period"].to_numpy())
    )
    curves = pd.DataFrame(dict([(value["name"][i], arr[:, i]) for i in range(0, 3)]), index=xx)
    return curves.hvplot(grid=True)


tabedit = pn.widgets.Tabulator(
    value=pars, show_index=False, selectable=True, theme="site", height=140
)
plot = pn.bind(compute_plot, value=tabedit)

pn.template.FastListTemplate(
    site="Awesome Panel", title="Table Edits", main=[tabedit, plot]
).servable()

Regarding the jupyterlab issue. If you use the FastListTemplate in the notebook it won’t work. It works only on the server. If that is not the problem then please paste the code that does not work and a screenshot or gif of the issue.

Oh great, binding the tabedit works wonders (I only blindly tried tabedit.value and others, which did nothing).

Now for Jupyter Lab, it is indeed an issue. This is a MWE (use one of the last lines to select whether the panel is run inline or served):

import pandas as pd
import numpy as np
import panel as pn
import hvplot.pandas

pars=pd.DataFrame({'name':['A','B','C'],'period':[1,.5,.3],'amplitude':[.3,.4,.1]})
def compute(pp):
    xx=np.linspace(0,1,100)
    arr=pp['amplitude'].to_numpy()*np.sin(np.outer(2*np.pi*xx,1./pp['period'].to_numpy()))
    return pd.DataFrame(dict([(pp['name'][i],arr[:,i]) for i in range(arr.shape[1])]),index=xx).hvplot(grid=True)
tabedit=pn.widgets.Tabulator(value=pars,show_index=False)
plot=pn.bind(compute,tabedit)
col=pn.Column(tabedit,plot)

col # this does not work (no plot update)
# pn.serve(col) # this DOES work (plot updates)

I am glad you made me learn how to use Peek for screencasts :slight_smile: this one is in the notebook inline (note: curves don’t update):

1 Like

And this one goes via pn.serve(col):

This was all recorded with JupyterLab 3.0.16, panel.__version__ is 0.12.0a11, param.__version__ is 1.10.0.

1 Like

Could you try adding pn.extension() right after the imports. I believe that is the problem @eudoxos .

Let me know if it works.


The reason why binding to tabedit.value does not work is that this only contains the value (i.e. the dataframe).

If you bind to tabedit.param.value then it will work as this is not the value but an instance of a Parameter. This is something that holds the value and can signal when the value is changed.

If you bind to tabedit then under the hood you will bind to tabedit.param.value.


This is such a beautiful example and reacting to either of selection or value changes is something that is so useful all the time. Thanks for bringing it up.

Thanks for the advice. I removed most pip modules and installed all dependencies from scratch (to ensure I am up-to-date) and it works now in the notebook inline — with and without pn.extension(). Feel free to add this (or similar) example to the gallery; binding tabulator as input param is not covered, as far as I was looking before.

As a suggestion, perhaps pn.bind could check that the object being bound to is an instance of parameter and raise an exception otherwise? That is easier to understand than just not getting the callback called.

Have a good day :slight_smile:

1 Like