I’d like to have a bi-directional link from a scatter plot to a tabulator table. Everything works in this example, except when a Tabulator row is chosen, it is not reflected in the scatter plot.
_table_selected captures the event but how can I update the plot to select the point in the plot? Is there a better way to link these objects?
import param
import panel as pn
import pandas as pd
import holoviews as hv
from holoviews.streams import PointDraw, Selection1D
hv.extension("bokeh")
pn.extension("tabulator")
class PlotLinker(param.Parameterized):
df = param.DataFrame(pd.DataFrame(data={"x": [0, 5, 10], "y": [0, 3, 10]}))
stream = param.ClassSelector(default=PointDraw(drag=True, add=False),
class_=(PointDraw),
precedence=-1)
selection = param.ClassSelector(default=Selection1D(),
class_=(Selection1D),
precedence=-1)
def table(self):
self.table_ = pn.widgets.Tabulator(self.df,
width=500,
layout="fit_columns")
self.table_.link(self.df,
callbacks={"value": self._callback_tabulator})
self.table_.param.watch(self._table_selected, 'selection')
return self.table_
def scatter(self):
points = hv.Scatter(self.df)
points.opts(tools=["hover"], active_tools=["point_draw"], size=10)
self.stream.source = points
self.selection.source = points
return points
@pn.depends("stream.data", watch=True)
def _update_dataframe(self):
"""Function to reflect the change in the PointDraw data to the original data"""
print('_update_dataframe')
stream_df = pd.DataFrame(self.stream.data)
if not stream_df.equals(self.df):
df = stream_df.reset_index()
if "index" in df.columns:
df = df.drop(["index"], axis=1)
self.df = df
def _callback_tabulator(self, target, event):
"""Simple callback to update the values when the table is updated"""
print('_callback_tabulator')
df = event.new
if "index" in df.columns:
df = df.drop(["index"], axis=1)
self.df = df
@param.depends('selection.index', watch=True)
def _point_selected(self):
''' link scatter plot selection to table row '''
print('_point_selected')
self.table_.selection = self.selection.index
# ??? doesn't update scatter & re-triggers seloection.index
def _table_selected(self, event):
''' link table row selection to scatter plot '''
print('_table_selected')
print(event)
# self.selection.update(index=event.new)
def view(self):
return pn.Row(self.scatter, self.table)
pl = PlotLinker()
pn.Row(pl.scatter, pl.table).servable()
We did it! Linked a Tabulator to a Scatter plot … sort of … but it feels pretty hacky … and it’s triggering a bit of recursion. Maybe someone can help clean it up a bit. Here it is:
import panel as pn
import pandas as pd
import holoviews as hv
from holoviews import streams
pn.extension("tabulator")
df = pd.DataFrame(data={"x": [0, 5, 2], "y": [0, 3, 7]})
scatter = hv.Scatter(df, "x", "y").opts(xlim=(-10, 10),
size=10,
selected=[1],
tools=['tap'])
tabulator = pn.widgets.Tabulator(df, selection=[])
def update_scatter(selection):
''' link table row selection to scatter plot '''
print('row_selected', selection)
return hv.Scatter(df, "x", "y").opts(xlim=(-10, 10),
ylim=(-10, 10),
size=10,
selected=selection,
tools=['tap', 'box_select'])
def point_selected(index):
''' link scatter plot selection to table row '''
print('point_selected', index)
tabulator.selection = index
plot = hv.DynamicMap(pn.bind(update_scatter, tabulator.param.selection))
sel = streams.Selection1D(source=plot)
sel.param.watch_values(point_selected, 'index')
# tabulator.param.watch_values(row_selected, 'selection')
pn.Row(tabulator, plot).servable()
Thanks for sharing how to make bi direction link between tabulator and plot. Its awesome, however did you have idea if we have a lot data in the table and we can select from graph then table will be selected to those point of data in the table and put them in upper row instead we look one by one in every page in tabulator table or simply make filter to showing only selected data table later we can reset the table to showing original data. Same thing when we select multiple data point in the table then graph will correspond as well like in usually we use powerbi to cross filter between graph, table and map.
Thank you
Thanks @skytaker . It works to display only selected point however I get difficulty to put back the original data when reset the filter. do you have any idea.
def point_selected(index):
‘’’ link scatter plot selection to table row ‘’’ #print(‘point_selected’, index)
tabulator.selection = index
tabulator.remove_filter
however there is some other problem that I realize. When we click from plot its show as expected unfortunately when we click from table expecially move to other row they will have error