Tap and DoubleTap not working in Bokeh server app

Hi,

I am trying to build an interactive Bokeh app with python callback to trigger a backend query when the user taps on the plot. I was trying to follow the events_app.py example from Bokeh repo but neither the the js_on_event nor the python on_event callbacks are registering the Tap event. Other events like Mouse enter/leave, pan, zoom etc. are working fine.

I have a print debug statement in the print_event function but p.on_event(events.Tap, print_event(attributes=point_attributes)) is not even triggering that function.

Ultimately I plan to embed this app in a jupyter notebook.

Thanks in advance for the help !!

Cheers,
Rusty

The JS example in the docs is working for me, as is the server example when I run it locally. There is not enough information in your post to speculate about anything. It is always advised to provide full details such as:

  • exact versions for all relevant packages
  • system and browser information

and most critically a complete Minimal Reproducible Example of your code that can actually be copy and pasted to run locally for investigation.

Sure @Bryan , attaching the python code I am locally running on my laptop

bokeh-events-server-app.py :

""" Demonstration Bokeh app of how to register event callbacks in both
Javascript and Python using an adaptation of the color_scatter example
from the bokeh gallery. This example extends the js_events.py example
with corresponding Python event callbacks.
"""

import numpy as np

from bokeh import events
from bokeh.io import curdoc
from bokeh.layouts import column, row
from bokeh.models import Button, CustomJS, Div
from bokeh.plotting import figure


def display_event(div, attributes=[]):
    """
    Function to build a suitable CustomJS to display the current event
    in the div model.
    """
    style = 'float: left; clear: left; font-size: 13px'
    return CustomJS(args=dict(div=div), code=f"""
        const {{to_string}} = Bokeh.require("core/util/pretty")
        const attrs = {attributes};
        const args = [];
        for (let i = 0; i<attrs.length; i++ ) {{
            const val = to_string(cb_obj[attrs[i]], {{precision: 2}})
            args.push(attrs[i] + '=' + val)
        }}
        const line = "<span style={style!r}><b>" + cb_obj.event_name + "</b>(" + args.join(", ") + ")</span>\\n";
        const text = div.text.concat(line);
        const lines = text.split("\\n")
        if (lines.length > 35)
            lines.shift();
        div.text = lines.join("\\n");
    """)

def print_event(attributes=[]):
    """
    Function that returns a Python callback to pretty print the events.
    """
    print("inside call back wrapper...")
    def python_callback(event):
        cls_name = event.__class__.__name__
        attrs = ', '.join([f"{attr}={event.__dict__[attr]}" for attr in attributes])
        print(f"{cls_name}({attrs})")

    return python_callback

# Follows the color_scatter gallery example

N = 4000
x = np.random.random(size=N) * 100
y = np.random.random(size=N) * 100
radii = np.random.random(size=N) * 1.5
colors = np.array([(r, g, 150) for r, g in zip(50+2*x, 30+2*y)], dtype="uint8")

p = figure(tools="pan,wheel_zoom,zoom_in,zoom_out,reset,tap,lasso_select,box_select,box_zoom,undo,redo")

p.circle(x, y, radius=radii, fill_color=colors, fill_alpha=0.6, line_color=None)

# Add a div to display events and a button to trigger button click events

div = Div(width=1000)
button = Button(label="Button", button_type="success", width=300)
layout = column(button, row(p, div))


point_attributes = ['x','y','sx','sy']
pan_attributes = [*point_attributes, 'delta_x', 'delta_y']
pinch_attributes = [*point_attributes, 'scale']
wheel_attributes = [*point_attributes, 'delta']

## Register Javascript event callbacks

# Point events

p.js_on_event(events.Tap,       display_event(div, attributes=point_attributes))
p.js_on_event(events.DoubleTap, display_event(div, attributes=point_attributes))
p.js_on_event(events.Press,     display_event(div, attributes=point_attributes))


# Point events

p.on_event('Tap',       print_event(attributes=point_attributes))
p.on_event(events.DoubleTap, print_event(attributes=point_attributes))
p.on_event(events.Press,     print_event(attributes=point_attributes))


curdoc().add_root(layout)

bokeh serve command :

❯ bokeh serve --show bokeh-events-server-app.py
2024-04-12 14:41:29,650 Starting Bokeh server version 3.4.0 (running on Tornado 6.4)
2024-04-12 14:41:29,653 User authentication hooks NOT provided (default user enabled)
2024-04-12 14:41:29,655 Bokeh app running at: http://localhost:5006/bokeh-events-server-app
2024-04-12 14:41:29,655 Starting Bokeh server with process id: 37384
inside call back wrapper...
inside call back wrapper...
inside call back wrapper...
2024-04-12 14:41:30,073 WebSocket connection opened
2024-04-12 14:41:30,073 ServerConnection created

pip freeze :

❯ pip freeze
asttokens==2.4.1
black==24.3.0
bokeh==3.4.0
cachetools==5.3.3
certifi==2024.2.2
charset-normalizer==3.3.2
click==8.1.7
comm==0.2.2
contourpy==1.2.1
db-dtypes==1.2.0
decorator==5.1.1
executing==2.0.1
google-api-core==2.18.0
google-auth==2.29.0
google-cloud-bigquery==3.20.1
google-cloud-core==2.4.1
google-crc32c==1.5.0
google-resumable-media==2.7.0
googleapis-common-protos==1.63.0
grpcio==1.62.1
grpcio-status==1.62.1
idna==3.6
ipython==8.23.0
ipywidgets==8.1.2
jedi==0.19.1
Jinja2==3.1.3
jupyter_bokeh==4.0.1
jupyterlab_widgets==3.0.10
MarkupSafe==2.1.5
matplotlib-inline==0.1.6
mypy-extensions==1.0.0
networkx==3.3
numpy==1.26.4
packaging==24.0
pandas==2.2.1
parso==0.8.4
pathspec==0.12.1
pexpect==4.9.0
pillow==10.3.0
platformdirs==4.2.0
prompt-toolkit==3.0.43
proto-plus==1.23.0
protobuf==4.25.3
ptyprocess==0.7.0
pure-eval==0.2.2
pyarrow==15.0.2
pyasn1==0.6.0
pyasn1_modules==0.4.0
Pygments==2.17.2
python-dateutil==2.9.0.post0
pytz==2024.1
PyYAML==6.0.1
requests==2.31.0
rsa==4.9
setuptools==69.2.0
six==1.16.0
stack-data==0.6.3
tornado==6.4
traitlets==5.14.2
tzdata==2024.1
urllib3==2.2.1
wcwidth==0.2.13
widgetsnbextension==4.0.10
xyzservices==2024.4.0

Browser : Google Chrome : Version 123.0.6312.122 (Official Build) (arm64)

Double tap works fine for me with 3.4.1 for both the JS and Python callbacks. For the tap event, your problem is that the event names are case sensitive. This:

p.on_event('Tap', print_event(attributes=point_attributes))

Should be

p.on_event('tap', print_event(attributes=point_attributes))

or, even more preferably, not a string at all:

p.on_event(events.Tap, print_event(attributes=point_attributes))

With that change:

There is an error at the source for bad string event names, but it does seem like that is being swallowed or suppressed somewhere since I don’t see it in the console log.

In [2]: Event.cls_for("Tap")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[2], line 1
----> 1 Event.cls_for("Tap")

File ~/work/bokeh/src/bokeh/events.py:161, in Event.cls_for(event_name)
    159     return event_cls
    160 else:
--> 161     raise ValueError(f"unknown event name '{event_name}'")

ValueError: unknown event name 'Tap'

Interesting, so I deployed the code with events.Tap event name on a x86 Linux with chrome browser and it works just fine. But not on M3 Mac !!

Next I will try to run it in Jupyter embedded mode.


It might be the browser, Jupytyer notebook working fine on Chrome in Linux. Its not responding on Chrome in M3 Mac.