Callback on TapTool Unselect

I have a simple vbar plot and I am using the TapTool to trigger a callback when a specific bar is selected. When selecting a bar, it is highlighted, the others are faded, and the callback is triggered.

But I cant figure out how to get a callback to trigger when I un-select the bar. If i click in the white space of the graph, the bars all return to normal coloring, but i cant trigger a callback.

I tried using the js_on_event(tap, callback) but, as expected, this triggers anytime there is a tap anywhere on the plot.
+569+
How can I get the a callback to trigger only on the un-select (when i tap in the whitespace)?

import bokeh.plotting
import bokeh.io
import bokeh.layouts
from bokeh.models import CustomJS,TapTool
from bokeh.models import ColumnDataSource
import pandas as pd

bokeh.io.output_file("bar.html")

fruit = ['apple', 'orange', 'pear']
data = pd.DataFrame({'x': fruit, 'y': [3, 4, 6]})
s1 = ColumnDataSource(data)
p3 = bokeh.plotting.figure(x_range=fruit)
p3.vbar(x='x', top='y', width=.9, source=s1)

tap = TapTool(callback=CustomJS(args=dict(s1=s1), code=""" console.log("bar selected");"""))
p3.add_tools(tap)
layout = bokeh.layouts.layout(p3)

# This is triggered on clicks in both the whitespace and on a bar, which me
# p3.js_on_event('tap', CustomJS(args=dict(s1=s1), code=""" console.log("no bars selected");"""))
bokeh.io.show(layout)

Thanks!

1 Like

Hi, @EMiller

If you use bokeh in server mode and register an on_change callback for the ColumnDataSource, s1 in your example, you can know when the whitespace is clicked because the indices of the selected item(s) will be empty. I will let others comment on the analogue for JavaScript callbacks if you need to go that route.

So something like the following, with the caveat that this is not tested.

s1.selected.on_change('indices', sel_cb)

And a callback with the signature as follows, where new will contain the index(ices) of the selected entries in your data source.

def sel_cb(attr, old, new):
    if new:
         do something
    else:
        do other

Thanks @_jm

For this application I need to stick to the JavaScript Callbacks, if possible.

It seems like there is a trigger somewhere, because when the white space is selected, all of the bars return to normal coloring. But i have no idea where/how i could tie into it

Hi @EMiller

Your CustomJS() callback has the ColumnDataSource s1 available as an argument.

The comment in your example code states that the callback is being invoked regardless of where you click. Perhaps you can put logic in your code to discern when the whitespace is clicked versus not and have custom behaviors to suit what you need.

Specifically, I think you can inspect the source’s selected indices in the JavaScript just as in the Python example. See how the s1.selected.indices property changes when you click on one bar, multiple bars (shift+click), or the whitespace, i.e. no bars.

I hope this helps somewhat.

Thanks @_jm that worked!

I end up with two callbacks executing when I click on a bar and one in the whitespace, but a quick logic check in the .on_click callback to see if anything is selected ensures only the desired sections of code run in each case.

Please share your code snipped. A working solution would be super useful. Many thanks!!

My working example is below.

import bokeh.plotting
import bokeh.io
import bokeh.layouts
from bokeh.models import CustomJS,TapTool
from bokeh.models import ColumnDataSource
import pandas as pd

bokeh.io.output_file("bar.html")


def get_onClick_filter_callback(s1):
    return CustomJS(args=dict(s1=s1), code=""" 
                    var selected_value = s1.selected.indices
                    console.log("a column has been selected");
                    console.log("selected_value", selected_value);
                    """)


def get_UnClick_filter_callback(s1):
    return CustomJS(args=dict(s1=s1), code=""" 
                    var selected_value = s1.selected.indices
                    if (selected_value.length==0){
                        console.log("no column selected");
                        console.log("selected_value", selected_value);
                    }
                    """)


fruit = ['apple', 'orange', 'pear']
data = pd.DataFrame({'x': fruit, 'y': [3, 4, 6]})
s1 = ColumnDataSource(data)
p3 = bokeh.plotting.figure(x_range=fruit)
p3.vbar(x='x', top='y', width=.9, source=s1)

click_cb = get_onClick_filter_callback(s1)
tap = TapTool(callback=click_cb)
p3.add_tools(tap)
layout = bokeh.layouts.layout(p3)
unselect = get_UnClick_filter_callback(s1)
p3.js_on_event('tap', unselect)
bokeh.io.show(layout)
2 Likes