Get points inside a geometry on callback (Bokeh 3.0.2)

Hello :slight_smile: I’m working with Bokeh 3.0.2 and I am trying to understand callbacks, specially for tools. My main goal right now is to be able to list and modify only points inside a region selected with bokeh tools like lasso, or box or poly. And my second goal is to understand CustomJS.

I have now explored a lot with the example of js-on-event-callback-triggers but I would like to know more about cb_obj and cb_data, are there any more internal objects that I can use?

Whenever a select method is used, it highlights the points inside such region, so there is already a default callback somewhere that does this for me.

With this code I can plot my scatter and any selection highlights points and the polygon/lasso/box points appear written in a div on the right, so I can see the attributes of a cb_obj:

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

output_notebook()


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="""
        const attrs = %s;
        const args = [];
        for (let i = 0; i < attrs.length; i++) {
            const val = JSON.stringify(cb_obj[attrs[i]], function(key, val) {
                return val.toFixed ? Number(val.toFixed(2)) : val;
            })
            args.push(attrs[i] + '=' + val)
        }
        const line = "<span 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");
    """ % (attributes, style))

x=df["u_x"].values
y=df["u_y"].values
colors=list(df["color_hour"].values)

p = figure(tools="pan,wheel_zoom,zoom_in,zoom_out,reset,lasso_select,box_select")

p.scatter(x,y,line_width=0, fill_color=colors)

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

p.js_on_event(events.SelectionGeometry, display_event(div, attributes=['geometry', 'final']))

show(layout)

I know I can use a ColumnDataSource instead of a pandas df but then the p.scatter wasn’t working properly when I tried.

Can anyone tell me how it knows which points are inside and how to get them? either via python or js or both? or using a notebook preferably? but if no then a server is ok. I am basically learning by playing around. There are some things that is a bit tough to learn with no documentation other than examples that change a lot between bokeh versions and solutions out there are outdated.

I know I can use a ColumnDataSource instead of a pandas df but then the p.scatter wasn’t working properly when I tried.

It’s not clear at all what this means, without code to demonstrate. FWIW there is always ultimately a ColumnDataSource, but Bokeh will create one for you as a convenience in some cases.

In any case getting the selected indices does require making a callback on the CDS, see:

If you don’t want to create your own, the data source is available as the .data_source attribute of the glyph renderer (the thing returned by the glyph method).

1 Like

Thank you. That is a perfect example.

I wanted to be able to copy such indices back into python and in a notebook so I did this based on an example:

def bkapp(doc):
    TOOLS="pan,wheel_zoom,reset,hover,lasso_select,box_select"
    source = ColumnDataSource(data=df)
    
    plot = figure(title="UMAP only on bikes",tools=TOOLS, width=500, height=500)
    
    plot.circle('UMAP_x', 'UMAP_y',color="color_hour", source=source)
    
    custom = CustomJS(args={"source":source,"plot":plot}, code="""
        if(cb_obj.final){
            const command="inds = [" + source.properties.selected._value.properties.indices._value.toString()+"]";
            IPython.notebook.kernel.execute(command);
        }
        
        """
    )
    
    plot.js_on_event(events.SelectionGeometry, custom)
    
    doc.add_root( column(plot) )

show(bkapp)

#I can print and use inds here
print(inds)

This only works on notebooks, not on jupyter lab

I think that is expected, the Jupyter team has restricted access to the kernel to proper Jupyter extensions AFAIK. Regardless, this is a pretty fragile approach in any case, and I would not recommend it.

For robust Python<–>JS synchronization, embed a Bokeh server app in the notebook, e.g. following this example:

bokeh/notebook_embed.ipynb at branch-3.1 · bokeh/bokeh · GitHub

Yeah this was exactly what I used, thanks.

Is there any way to see which events are triggered by different models, and sources and all of them? and is cb_obj related to this?

@lesolorzanov in general every property of every model in the reference guide can be used with on_change or js_on_change. For JS callbacks, cb_obj is the specific model instance that triggered the callback.

UI Events have their own reference: bokeh.events — Bokeh 3.0.2 Documentation