I need to have sync between a hv.Points
plot and a DataFrame, while enabling a PointDraw
tool. Yes, this calls for an Annotator, but I don’t know how to extend Annotator with further callbacks.
So, I tried the following approach, where I am using PointDraw
for do plot->df
sync, and a separate text box to do the renaming of annotations. And… it almost works. I just need a pointer how to let the points_stream
know the vdim
has changed.
import param
import pandas as pd
import panel as pn
pn.extension('tabulator')
import holoviews as hv
hv.extension('bokeh')
import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
_coords_column_order = ['site','x', 'y']
class SG(param.Parameterized):
coords = param.DataFrame(columns=_coords_column_order,doc="Dataframe containing coordinates",)
plot = param.ClassSelector(class_=hv.Points)
points_stream = param.ClassSelector(class_=hv.streams.PointDraw,)
sel_stream = param.ClassSelector(class_=hv.streams.Selection1D,default=hv.streams.Selection1D())
def plot_points(self, coords=None):
if coords is None:
coords = self.coords
plot = hv.Points(coords, kdims=['x','y'], vdims=['site'])
plot.opts(size=10,tools=['hover'],active_tools = ['point_draw',"wheel_zoom"])
return plot
def __init__(self,coords):
plot = self.plot_points(coords)
ps = hv.streams.PointDraw(data=plot.columns(), source=plot, empty_value='')
ss = hv.streams.Selection1D(source=plot)
super().__init__(
coords = coords,
points_stream = ps,
plot = plot,
sel_stream = ss
)
@pn.depends("points_stream.data", watch=True)
def _update_coordinates_from_plot(self):
self.coords = pd.DataFrame(self.points_stream.data)[_coords_column_order]
def rename_site(self, new_site_name):
logger.debug("=======\ntrying to rename site with index")
i = self.sel_stream.index
logger.debug(f"i: {i}, new name: {new_site_name}")
df = self.coords.copy()
df.loc[i,'site'] = new_site_name
self.coords = df
coords = pd.DataFrame(
{ "site":list("ABC"), "x":[0,.2,.4], "y":[0,.1,0]},
)
A = SG(coords)
rename_text = pn.widgets.TextInput(width=100)
def rename(event):
A.rename_site(event.new)
logger.debug("\n"+str(A.coords))
rename_text.param.watch(rename, "value")
tbl_view = pn.widgets.Tabulator.from_param(
A.param.coords,
show_index=False,
)
text = "To rename a site, select it on a plot by clicking, then enter the new name in the text box below, and confirm by pressing enter."
main = pn.Row(
pn.Column(tbl_view,text,rename_text),
A.plot
)
main.servable()
Right now, the renaming is registered at the level of the main dataframe, but the points_stream
does not know about it. So, the next time, the renamed point is being dragged, the dataframe’s 'site'
reverts back to what it was before renaming.