Can't get selected indices from TapTool with CustomJS callback

Hi,

I’m trying to use a CustomJS callback with a TapTool to get the selected indices on a circle plot but it’s not working. Minimum example code below:

from bokeh.models.tools import TapTool
from bokeh.models.callbacks import CustomJS
from bokeh.plotting import Figure, show

cjs = """
console.log('Tap');
console.log(cb_obj.selected.indices);
"""

cb = CustomJS(code=cjs)
ttool = TapTool(callback=cb)

fig = Figure(x_range=[0, 1], y_range=[0, 1])
fig.tools.append(ttool)
fig.circle([0.25, 0.75], [0.25, 0.75], size=20)

show(fig)

When running this and tapping on either circle the following appears in the Browser console:
Tap
TypeError: cb_obj.selected is undefined

I must be getting something simple wrong but can’t work out what it is, any advice welcome!

Thanks,
Marcus.

Hi Marcus,

The ‘selected’ ought to be an attribute on a ColumnDataSource, rather than the callback object. Try something like this:

from bokeh.models.tools import TapTool
from bokeh.models.callbacks import CustomJS
from bokeh.plotting import Figure, show

cjs = """
console.log('Tap');
console.log(source.selected.indices);
"""

fig = Figure(x_range=[0, 1], y_range=[0, 1])
circle_renderer = fig.circle([0.25, 0.75], [0.25, 0.75], size=20)

cb = CustomJS(args=dict(source=circle_renderer.data_source), code=cjs)
ttool = TapTool(callback=cb)
fig.tools.append(ttool)

show(fig)

Hi Carolyn,

Thanks for the suggestion. I just tried it - the error has gone but the console now logs:
Tap
<unavailable>

@carolyn code is working as expected for me, and I can see the indices on a tap. Some more information (versions) etc would be needed to speculate more.

Thanks @Bryan, something must be wrong with my setup. Versions as follows:
Bokeh 1.4.0
Python 3.7.5
Firefox 70.0.1

If it’s any use the browser console error occurs at line 428 of bokeh-1.4.0.min.js. I’ve extracted the relevant code…

function _(e,t,n){var r=e(113),i=e(271),o=e(121),u=e(125),s=e(127),c=function(t){function n(e){return t.call(this,e)||this}return r.__extends(n,t),n.init_CustomJS=function(){this.define({args:[o.Any,{}],code:[o.String,""],use_strict:[o.Boolean,!1]})},Object.defineProperty(n.prototype,"names",{get:function(){return u.keys(this.args)},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"values",{get:function(){return u.values(this.args)},enumerable:!0,configurable:!0}),Object.defineProperty(n.prototype,"func",{get:function(){var e=this.use_strict?s.use_strict(this.code):this.code;return new(Function.bind.apply(Function,r.__spreadArrays([void 0],this.names,["cb_obj","cb_data","require","exports",e])))},enumerable:!0,configurable:!0}),n.prototype.execute=function(t,n){return void 0===n&&(n={}),this.func.apply(t,this.values.concat(t,n,e,{}))},n}(i.Callback);n.CustomJS=c,c.__name__="CustomJS",c.init_CustomJS()},

You can set the environment variable BOKEH_MINIFIED=no and it will use the unminified version.

Thanks @Bryan , here’s the unminified version…

/* models/callbacks/customjs.js */ function _(require, module, exports) {
    var tslib_1 = require(113) /* tslib */;
    var callback_1 = require(271) /* ./callback */;
    var p = require(121) /* ../../core/properties */;
    var object_1 = require(125) /* ../../core/util/object */;
    var string_1 = require(127) /* ../../core/util/string */;
    var CustomJS = /** @class */ (function (_super) {
        tslib_1.__extends(CustomJS, _super);
        function CustomJS(attrs) {
            return _super.call(this, attrs) || this;
        }
        CustomJS.init_CustomJS = function () {
            this.define({
                args: [p.Any, {}],
                code: [p.String, ''],
                use_strict: [p.Boolean, false],
            });
        };
        Object.defineProperty(CustomJS.prototype, "names", {
            get: function () {
                return object_1.keys(this.args);
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(CustomJS.prototype, "values", {
            get: function () {
                return object_1.values(this.args);
            },
            enumerable: true,
            configurable: true
        });
        Object.defineProperty(CustomJS.prototype, "func", {
            get: function () {
                var code = this.use_strict ? string_1.use_strict(this.code) : this.code;
/* ERROR OCCURS AT THE NEXT LINE */
                return new (Function.bind.apply(Function, tslib_1.__spreadArrays([void 0], this.names, ["cb_obj", "cb_data", "require", "exports", code])))();
            },
            enumerable: true,
            configurable: true
        });
        CustomJS.prototype.execute = function (cb_obj, cb_data) {
            if (cb_data === void 0) {
                cb_data = {};
            }
            return this.func.apply(cb_obj, this.values.concat(cb_obj, cb_data, require, {}));
        };
        return CustomJS;
    }(callback_1.Callback));
    exports.CustomJS = CustomJS;
    CustomJS.__name__ = "CustomJS";
    CustomJS.init_CustomJS();
},

The line numbers have been lost but I’ve inserted a comment above the line which throws the error.

I’ll try another browser to see if this looks like a Firefox-specific problem.

@Marcus_Donnelly That line is trying to compile the JS text in to actual JS code (i.e. a Function) An error there usually indicates a syntax error in your JS callback code. Perhaps there is some unintentional formatting issue in your version, or unicode or escape character that has creapt in to the source code string?

Thanks @Bryan. I can’t find a formatting issue - I simply copied and pasted the code @carolyn posted so there shouldn’t be one. However, I’ve just tried the same code with the Chromium browser (and also with Chrome on Windows using Bokeh 1.2.0) and it works fine! So there may be some Firefox-specific issue occurring here. I’ll do some more digging and report back if I find anything helpful. In the meantime though thanks to you both for the help.

1 Like

FWIW I juts tried in FF 70.0.1 and also working there for me. I’m not really sure what to suggest at this point :confused:

Thanks @Bryan, the problem must be elsewhere then. I hadn’t had any problems with Firefox before so was a bit suspicious of the browser itself being the problem. I might have a particular set of dependencies which is causing the problem, will keep searching…

It also works as expected for me, using exact the same versions you have.

At some point I also had some strange errors and it turned out that removing dask from Anaconda solved these problems. However I’m not sure if it really was a dask-issue since I don’t think bokeh uses it anyway.
Maybe some other package was removed when I removed dask, but can’t tell which :confused: