Cookbook example: Select points and update associated plot

This is a common cookbook example.
I have two data frames that are related. One is represented as points ( geo data but for simple example that has been left out) and the other data frame is say associated time series for the “selected” point.

I have a working example, but it feels inefficient, because I am linking the streams via a param called “selected”. Please help me improve

Attaching the jupyter notebook as well as displaying it here

import numpy as np
import pandas as pd

import holoviews as hv
hv.extension('bokeh')
from holoviews import opts
import hvplot.pandas

import param as param
import panel as pn
pn.extension()
np.random.seed(12) # to allow repitition with same random values
npts = 10
dfpts = pd.DataFrame(np.random.rand(npts,2))
dfpts.head()
nts=20 # number of time series points
dfmatching=pd.DataFrame(np.random.rand(nts, npts), pd.date_range('01-01-2000',periods=nts))
dfmatching.head()
class MapSelect(param.Parameterized):
    selected = param.List(default=[0], doc='selection')
    line_color = param.Color(doc='Line Color', default='#FF0000')
    
    def __init__(self, dfpts, dfmatching, **kwargs):
        super().__init__(**kwargs) # on't foget to call super init!
        self.dfpts=dfpts
        self.dfmatching=dfmatching
        self.points = dfpts.hvplot.points().opts(opts.Points(tools=['tap'], size=5)) # add tap to allow user interactive select
        self.select_stream = hv.streams.Selection1D(source=self.points, index=[0]) # stream to attach to points plot
        self.select_stream.add_subscriber(self.set_selected) # callback to a method here to set selection
        
    # Feels inefficient. Is there a better way of linking the view method to both stream and param changes
    def set_selected(self, index):
        self.selected = index

    def viewpts(self):
        return self.points
    
    @param.depends('selected', 'line_color')
    def view(self):
        if self.selected is None or self.selected==None:
            self.selected=[0] # set to first index if empty ? How to handle empty selections
        fi=self.selected[0] # making it easy by just doing the first selection. One could do an overlay of all
        pts_selected=self.dfpts.iloc[fi,:] # select by iloc (thats how Selection1D works)
        common_on=pts_selected.name # this common_on could be an property on which to select dfmatching values
        return dfmatching.iloc[:,common_on].hvplot(color=self.line_color)
ms=MapSelect(dfpts, dfmatching)
pn.Column(ms.viewpts, ms.param.line_color, ms.view)

example_map_select_display_matching_data.ipynb (3.1 MB)

I think dynamic map is what you need. Try taking a look at the example below. I could not get the color of the line to work instantaneous, so it first update after the next point is selected. Maybe another can help you with that.

Update: I got the color picker to work after changing some small things. :slight_smile:

import numpy as np
import pandas as pd

import holoviews as hv

import hvplot.pandas  # noqa
import panel as pn
import param as param
from holoviews import opts

hv.extension("bokeh")
pn.extension()

np.random.seed(12)  # to allow repitition with same random values
npts = 10
dfpts = pd.DataFrame(np.random.rand(npts, 2))
dfpts.head()

nts = 20  # number of time series points
dfmatching = pd.DataFrame(
    np.random.rand(nts, npts), pd.date_range("01-01-2000", periods=nts)
)
dfmatching.head()
class MapSelect(param.Parameterized):
    # line_color = param.Color(doc='Line Color', default='#FF0000')
    line_color = param.Selector(["r", "g", "b"])

    def __init__(self, dfpts, dfmatching, **kwargs):
        super().__init__(**kwargs)
        self.dfpts = dfpts
        self.dfmatching = dfmatching
        self.points = dfpts.hvplot.points().opts(opts.Points(tools=["tap"], size=5))
        self.select_stream = hv.streams.Selection1D(source=self.points, index=[0])
        self.dmap = hv.DynamicMap(self.mathing_points, streams=[self.select_stream])

    @param.depends("line_color", watch=True)
    def _set_line_color(self):
        self.dmap.opts(color=self.line_color)

    def mathing_points(self, index):
        fi = index[0] if index else 0
        pts_selected = self.dfpts.iloc[fi, :]
        common_on = pts_selected.name
        return dfmatching.iloc[:, common_on].hvplot(color=self.line_color)

    def view(self):
        return pn.Column(self.points, self.param.line_color, self.dmap)


dm = MapSelect(dfpts, dfmatching)
pn.panel(dm.view).servable()

@Hoxbro I did try the DynamicMap approach. Then my other params have no effect as the view changes but the dynamic map is not called as the stream has not had an event on it.

Thanks for the example, now it makes the problem complete!

Reviving this thread again. The DynamicMap is limited in that the view being returned needs to be compatible with DynamicMap restrictions. For example, if you return a Layout on certain calls and a Overlay or other kind of element on other calls it will fail. In that case, a container is needed that responds to stream events and I could not find one in Holoviews.

However my original example is flexible enough to work for any view that needs to be linked to the stream. (Ofcourse, DynamicMap option is more performant and I use that if possible always)