Hi, I want to filter a tabulator based on zoom or box select in a plot. I adjusted the code in this topic Using box_select to Return a Filtered Dataframe to do what I want, but I wonder if there is a better way because this seems quite hacky. Here is the code:
import numpy as np
import pandas as pd
import holoviews as hv
import panel as pn
import hvplot.pandas
from holoviews import streams
pn.extension(sizing_mode="stretch_width")
n=200
xs = np.linspace(0, 1, n)
ys = np.cumsum(np.random.randn(n))
df = pd.DataFrame({'x': xs, 'y': ys})
def get_plot():
return df.set_index('x').hvplot.step(tools=["box_select"])
source = hv.Curve({})
boundsx = streams.BoundsX(source=source, boundsx=(0, 0))
t = pn.widgets.Tabulator(df.head())
@pn.depends(boundsx.param.boundsx)
def data_view(boundsx):
x_start, x_end = boundsx
filtered_df = df[(df["x"].between(x_start, x_end))]
if boundsx != (0,0):
t.value = filtered_df.head()
component = pn.Column(
hv.DynamicMap(get_plot) * source,
t,
pn.pane.DataFrame(data_view, visible=False)
)
An alternative for this particular example, which is less hacky but has other downsides, is doing it like this:
boundsx = streams.BoundsX(source=source, boundsx=(0, 0))
@pn.depends(boundsx.param.boundsx)
def data_view(boundsx):
x_start, x_end = boundsx
filtered_df = df[(df["x"].between(x_start, x_end))]
return filtered_df
t = pn.widgets.Tabulator(data_view)
component = pn.Column(
hv.DynamicMap(get_plot) * source,
t
)
Whereas this does not work:
boundsx = streams.BoundsX(source=source, boundsx=(0, 0))
t = pn.widgets.Tabulator(df.head())
@pn.depends(boundsx.param.boundsx)
def data_view(boundsx):
x_start, x_end = boundsx
filtered_df = df[(df["x"].between(x_start, x_end))]
t.value = filtered_df.head()
component = pn.Column(
hv.DynamicMap(get_plot) * source,
t
)
Questions:
- My biggest question is why I cannot define a callback on the plot event like in the “this does not work” example without having to add a widget based on the callback somehow? Or maybe I can, but how then?
- The downside of the first solution is that I need to artificially add an invisble component to the layout just in order to have
data_view
part of some component. The downside of the second solution is that I also want the tabulator to be filtered on other events, not just from this plot. - Why do I need the
DynamicMap
overlaid on an empty plot? Again coming back to the first bullet, is there no way to just define a callback on a plot zoom / select? Behind the scenes, theDynamicMap
must be registering some listener to the bokeh plot event – is that not exposed somewhere?
Thanks in advance for any help