holoviz / panel

Panel: The powerful data exploration & web app framework for Python

Home Page:https://panel.holoviz.org

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

holoviews Selection1D Tap: regression on panel 1.4

pmav99 opened this issue · comments

I am not sure if this belongs here or on the holoviews bug-tracker.

I suspect that the issue is actually related to bokeh 3.3 vs 3.4 but holoviews doesn't restrict the bokeh version, while bokeh and panel releases seem to be coupled. This is why I am opening the ticket here. Feel free to move it if this is not the case.

Description of expected behavior and the observed behavior

After upgrading to panel 1.4 the Holoviews Selection1D Tap example (and possibly others) no longer works.

Up to panel 1.3, when you click on a new point, the previous point gets deselected and the regression plot on the right gets updated. On panel 1.4 the previous point remains selected and the regression plot keeps on showing the first point (i.e there is no update). If you deselect the first point, then only the second point remains selected and the regression plot gets updated.

ALL software version info

This has been tested on 2 different linux machines and a Mac. It's the same behavior everywhere.

anyio==4.4.0
argon2-cffi==23.1.0
argon2-cffi-bindings==21.2.0
arrow==1.3.0
asttokens==2.4.1
async-lru==2.0.4
attrs==23.2.0
Babel==2.15.0
beautifulsoup4==4.12.3
bleach==6.1.0
bokeh==3.4.1
certifi==2024.2.2
cffi==1.16.0
charset-normalizer==3.3.2
colorcet==3.1.0
comm==0.2.2
contourpy==1.2.1
debugpy==1.8.1
decorator==5.1.1
defusedxml==0.7.1
executing==2.0.1
fastjsonschema==2.19.1
fqdn==1.5.1
h11==0.14.0
holoviews==1.18.3
httpcore==1.0.5
httpx==0.27.0
idna==3.7
ipykernel==6.29.4
ipython==8.24.0
isoduration==20.11.0
jedi==0.19.1
Jinja2==3.1.4
json5==0.9.25
jsonpointer==2.4
jsonschema==4.22.0
jsonschema-specifications==2023.12.1
jupyter-events==0.10.0
jupyter-lsp==2.2.5
jupyter_client==8.6.2
jupyter_core==5.7.2
jupyter_server==2.14.0
jupyter_server_terminals==0.5.3
jupyterlab==4.2.1
jupyterlab_pygments==0.3.0
jupyterlab_server==2.27.2
linkify-it-py==2.0.3
Markdown==3.6
markdown-it-py==3.0.0
MarkupSafe==2.1.5
matplotlib-inline==0.1.7
mdit-py-plugins==0.4.1
mdurl==0.1.2
mistune==3.0.2
nbclient==0.10.0
nbconvert==7.16.4
nbformat==5.10.4
nest-asyncio==1.6.0
notebook_shim==0.2.4
numpy==1.26.4
overrides==7.7.0
packaging==24.0
pandas==2.2.2
pandocfilters==1.5.1
panel==1.4.2
param==2.1.0
parso==0.8.4
pexpect==4.9.0
pillow==10.3.0
platformdirs==4.2.2
prometheus_client==0.20.0
prompt_toolkit==3.0.45
psutil==5.9.8
ptyprocess==0.7.0
pure-eval==0.2.2
pycparser==2.22
Pygments==2.18.0
python-dateutil==2.9.0.post0
python-json-logger==2.0.7
pytz==2024.1
pyviz_comms==3.0.2
PyYAML==6.0.1
pyzmq==26.0.3
referencing==0.35.1
requests==2.32.2
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
rpds-py==0.18.1
scipy==1.13.1
Send2Trash==1.8.3
six==1.16.0
sniffio==1.3.1
soupsieve==2.5
stack-data==0.6.3
terminado==0.18.1
tinycss2==1.3.0
tornado==6.4
tqdm==4.66.4
traitlets==5.14.3
types-python-dateutil==2.9.0.20240316
typing_extensions==4.12.0
tzdata==2024.1
uc-micro-py==1.0.3
uri-template==1.3.0
urllib3==2.2.1
wcwidth==0.2.13
webcolors==1.13
webencodings==0.5.1
websocket-client==1.8.0
xyzservices==2024.4.0

Complete, minimal, self-contained example code that reproduces the issue

https://holoviews.org/reference/streams/bokeh/Selection1D_tap.html

import numpy as np
import holoviews as hv
from holoviews import opts
from holoviews.streams import Selection1D
from scipy import stats
hv.extension('bokeh')

def gen_samples(N, corr=0.8):
    xx = np.array([-0.51, 51.2])
    yy = np.array([0.33, 51.6])
    means = [xx.mean(), yy.mean()]  
    stds = [xx.std() / 3, yy.std() / 3]
    covs = [[stds[0]**2          , stds[0]*stds[1]*corr], 
            [stds[0]*stds[1]*corr,           stds[1]**2]] 

    return np.random.multivariate_normal(means, covs, N)

data = [('Week %d' % (i%10), np.random.rand(), chr(65+np.random.randint(5)), i) for i in range(100)]
sample_data = hv.NdOverlay({i: hv.Points(gen_samples(np.random.randint(1000, 5000), r2))
                            for _, r2, _, i in data})
points = hv.Scatter(data, 'Date', ['r2', 'block', 'id']).redim.range(r2=(0., 1))
stream = Selection1D(source=points)
empty = (hv.Points(np.random.rand(0, 2)) * hv.Slope(0, 0)).relabel('No selection')

def regression(index):
    if not index:
        return empty
    scatter = sample_data[index[0]]
    xs, ys = scatter['x'], scatter['y']
    slope, intercep, rval, pval, std = stats.linregress(xs, ys)
    return (scatter * hv.Slope(slope, intercep)).relabel('r2: %.3f' % slope)

reg = hv.DynamicMap(regression, kdims=[], streams=[stream])

average = hv.Curve(points, 'Date', 'r2').aggregate(function=np.mean)
layout = points * average + reg
layout.opts(
    opts.Curve(color='black'),
    opts.Slope(color='black', framewise=True),
    opts.Scatter(color='block', tools=['tap', 'hover'], width=600, 
                 marker='triangle', cmap='Set1', size=10, framewise=True),
    opts.Points(frame_width=250),
    opts.Overlay(toolbar='above', legend_position='right')
)

Screencasts of the bug in action

This is the screencast with panel 1.3, where everything works as expected

panel_issue-2024-05-29_10.34.14.mp4

This is the screencast with panel 1.4.2, where the regression occurs (I didn't use 1.4.3 due to #6865 )

panel_issue-2024-05-29_10.38.49.mp4

This is a behavior change in Bokeh 3.4, see bokeh/bokeh#13831

And will likely be fixed/reverted in bokeh 3.5.0. Will close.

Hi! I just wanted to share my workaround, which passes the Bokeh TapTool explicitly.

specifying mode="replace" is essential for now, until bokeh reverted the behavior. then, I expect it won't break anything to stay "explicit"

import numpy as np
import panel as pn
import bokeh
import holoviews as hv
from holoviews import opts
from holoviews import streams
hv.extension('bokeh')


ndoverlay = hv.NdOverlay({i: hv.Curve(np.arange(10)*i) for i in range(5)})

selection = streams.Selection1D(source=ndoverlay)
dmap = hv.DynamicMap(lambda index: ndoverlay[index] if index else ndoverlay.clone(),
                     kdims=[], streams=[selection])
layout = ndoverlay + dmap
tap_tool = bokeh.models.TapTool(mode="replace")
layout.opts(opts.Curve(tools=[tap_tool], line_width=10), opts.NdOverlay(legend_limit=0))

pn.panel(layout).servable()

based on: https://holoviews.org/reference/streams/bokeh/Selection1D.html

works with

  • bokeh==3.4.1
  • panel==1.4.4
  • holoviews==1.19.0