Open jerry-kobold opened 1 month ago
I think I've traced the source of the problem: when you pass a plot to a link_selections.instance
object, that plot will get cloned several times. After it gets cloned, the connection between that plot and previous streams which have been defined to use it as the source, as reflected in the Stream.registry
dictionary, is severed. Consequently, when the Bokeh callbacks are constructed, that stream is no longer transformed into a callback and so the debug
function in the above example is never invoked.
I have come up with a hacky way of patching this by essentially pulling out the old plot by its _plot_id
from the registry and then assigning it to be the source of the stream. The following snippet (applied to the example above) illustrates the basic concept:
if link:
plot_id = plot._plot_id
plot = ls(plot)
cloned_plot = [plot_key for plot_key in list(Stream.registry.keys()) if plot_key._plot_id == plot_id]
cloned_plot = cloned_plot[0]
sel_stream.source = cloned_plot
This works, but feels gross. It seems like it should be easy enough to handle in the internals of the link_selections.instance
object, but I'm not sure if this is a good solution or not. Happy to make a PR for this if it seems worthwhile to do.
I would also expect this to work, so I will mark this as a bug.
I've been playing around with it myself. Without a fix, I think the "cleanest" way is to create two streams and bind them to the debug function.
import holoviews as hv
import numpy as np
import pandas as pd
import panel as pn
hv.extension("bokeh")
ls = hv.link_selections.instance(cross_filter_mode="overwrite")
sel_stream1 = hv.streams.Selection1D()
sel_stream2 = hv.streams.Selection1D()
def make_plot(index):
data = np.random.default_rng(seed=42).normal(size=(100, 2))
df = pd.DataFrame(data, columns=["x", "y"])
return hv.Points(df, kdims=["x", "y"], vdims=[]).opts(tools=["box_select"])
img1 = hv.DynamicMap(make_plot, streams=[sel_stream1])
img2 = hv.DynamicMap(make_plot, streams=[sel_stream2])
img = ls(img1 + img2)
selected = pn.widgets.StaticText(name="selected", value="")
def debug(index):
selected.value = f"{', '.join([str(idx) for idx in index])}"
pn.bind(debug, sel_stream1.param.index, watch=True)
pn.bind(debug, sel_stream2.param.index, watch=True)
pn.Column(img, selected).servable()
ALL software version info
holoviews==1.19.2
bokeh==3.4.3
panel==1.4.4
Description of expected behavior and the observed behavior
I am trying to accomplish the following: I want two plots (generated from the same data) that are linked to each other by some column. Additionally, I want to be able to hook a stream into the plots that fires on selection so I can do something with the selected points. However, when I apply the linkage, I lose the selection stream callback. I would expect both the linked selection and the manually installed callbacks to fire.
Complete, minimal, self-contained example code that reproduces the issue
If the checkbox is unchecked, the
debug
callbacks will fire as expected, but the selections will not be linked. On the other hand, if the checkbox is checked, then the selections will be linked, but the callbacks attached tosel_stream
will not fire.Stack traceback and/or browser JavaScript console output
There isn't any error associated with this, but it seems like not the behavior one would expect. I drilled down into the HoloViews code and it seems like when the link instance object attaches the callabacks to the plot, the callbacks that are attached from
sel_stream
get erased, but I don't know why.Screenshots or screencasts of the bug in action
https://github.com/user-attachments/assets/9931301f-14cc-4d8d-99d7-f920ce912390