☰Read-only archive⌕
Looking for help with Bokeh?

This is a read-only archive. Ask questions and join the conversation on GitHub.

Visit Community Support on GitHub →
Archived discussions · Captured October 28, 2026

How to see if user shift clicked or control clicked or command clicked on Tap Tool javascript callback

Community Support
monadandan
monadandan

Hi,

I am trying to make a tap tool with two different behaviors depending on whether the user is clicking on a point or control + clicking a point.

I would like this so that the user can tap for one type of behavior, but also control + click to open a URL associated with the point.

My example to start with that does not have this feature, it just opens the link when you press the points:

from bokeh.models import CustomJS, TapTool, ColumnDataSource
from bokeh.plotting import figure, show

cds = ColumnDataSource({
    'x': [1, 2, 3],
    'y': ['a', 'b', 'c'],
    'link': ['www.google.com', 'www.bing.com', 'www.cnn.com']
})
plot = figure(width=300, height=300, y_range=['a', 'b', 'c'])
plot.scatter(x='x', y='y', color="#fa9fb5", source=cds,
             marker='triangle_pin', size=15)

code = """
window.open('http://'.concat(cb_data.source.data.link[cb_data.source.inspected.indices]), '_blank')
"""
callback = CustomJS(args=dict(), code=code)
tap_tool = TapTool(behavior='inspect', callback=callback)
plot.add_tools(tap_tool)

show(plot)

I want to adjust the callback code to be something like this:

code = """
console.log(window)
console.log(MouseEvent.ctrlKey)
if (MouseEvent.ctrlKey) {
    window.open('http://'.concat(cb_data.source.data.link[cb_data.source.inspected.indices]), '_blank')
} else {
   // do something different
}
"""

But I am not sure how exactly to know if control key was pressed, since MouseEvent.ctrlKey is undefined.

I’m wondering if I can somehow get to this information via window? Any help is appreciated! Thanks in advance.

Edit: I am using bokeh 2.4.2

⌁   Link to post
Bryan
Bryan

This information is not currently available to the CustomJS callback. Perhaps surprisingly in retrospect, no one has ever asked about this before. Please feel free to open a GitHub Issue to request it as a new feature in a future 3.x release.

It would be possible to implement a custom extension that can detect shift- or ctrl-click (but only those two modifiers). I’ll wait for you to express interest in that route before elaborating, however.

⌁   Link to post
monadandan
monadandan

Hi Bryan,

I will go ahead and make a feature request! (Edit: Here is the feature request)

For the time being, I came up with a workaround:

from bokeh.models import CustomJS, TapTool, ColumnDataSource, Tabs, Panel, Div
from bokeh.plotting import figure, show

cds = ColumnDataSource({
    'x': [1, 2, 3],
    'y': ['a', 'b', 'c'],
    'link': ['www.google.com', 'www.bing.com', 'www.cnn.com']
})
plot = figure(width=300, height=300, y_range=['a', 'b', 'c'])
plot.scatter(x='x', y='y', color="#fa9fb5", source=cds, marker='triangle_pin', size=15)

code_1 = """
window.onclick = function () {
    window.ctrl_press = event.ctrlKey || event.metaKey
}
"""
callback_1 = CustomJS(code=code_1)

code_2 = """
if (window.ctrl_press) {
    window.open('http://'.concat(cb_data.source.data.link[cb_data.source.inspected.indices]), '_blank')
}
"""
callback_2 = CustomJS(code=code_2)

tap_tool = TapTool(behavior='inspect', callback=callback_2)
plot.add_tools(tap_tool)

panels = [Panel(child=Div(text='Welcome!'), title='Intro'), Panel(child=plot, title='Example')]
tabs = Tabs(tabs=panels, sizing_mode='stretch_width')
tabs.js_on_change('active', callback_1)

show(tabs)

This may be janky… When the user changes tabs, a callback is set to make a property stored in window equal to whether the control or command key was pressed in the window when a click happens. Now, the TapTool callback has access to this property in window.

As for a custom extension, you mentioned it would only be able to detect shift- or ctrl-click. Just to make sure, it could not detect meta-click (command-click)?

Thank you!

⌁   Link to post

End of archived discussion · Replies are disabled