Accessing selected idxs using box_select inside the select widget

Hey there!

I’m new to bokeh. I’m trying to access using the button’s js_on_click to the selected indices the I’ve selected using the ‘select_box’ tool and a value that I’ve selected inside a Select widget. When inspecting the following code I get an empty array for the selected points in the scatter plots and an empty category from the select although there are values inside.
the flow should be selecting using the select_box, selecting a category from the select widget and then saving it using the button (and resetting).
note that I can’t get the selected indices from the class object itself and from the cds too.
I’m doing something wrong, but not sure what. Will be happy for your help :slight_smile:

import pandas as pd
from bokeh.plotting import figure, show, output_notebook
from bokeh.transform import factor_cmap
from bokeh.palettes import Turbo256
from bokeh.models import LabelSet, ColumnDataSource, CustomJS
from bokeh.models.layouts import Column
from bokeh.models.widgets import Select, Button

class LabelingPlotWithSelection:
    def __init__(self, path_to_file):
        self.df = pd.read_csv(path_to_file)[['text', 'label', 'x', 'y']]
        data = dict(text=self.df['text'].tolist(), label=self.df['label'].tolist(), x=self.df['x'].tolist(), y=self.df['y'].tolist())
        self.data_src = ColumnDataSource(data=(data.copy()))
        self.output_data = ColumnDataSource(data=data.copy())
        self.classes = self.df['label'].unique().tolist()
        self.selected_idxs = {'idxs': []}
        
    def change_selected_indices(self, idxs):
        self.selected_idxs = idxs
        
    def render_initial_plot(self):
        p = figure(height=800, width=800, align='center', tooltips=tooltip, tools='hover,pan,wheel_zoom,box_zoom,reset,lasso_select,box_select')
        p.scatter(x='x', y='y', source = self.data_src)
        select = Select(title='label selector', value=None, options=self.classes)
        button = Button(label='Save')
        self.data_src.selected.js_on_change('indices', CustomJS(
            args=dict(selected_idxs = self.selected_idxs),code='''
            const selected_data = cb_obj.indices;
            for (var i=0;i<selected_data.length;i++) {
                selected_idxs['idxs'].push(selected_data[i])
            }
            console.log(selected_idxs)
            '''))
        button.js_on_click(CustomJS(
            args=dict(src=self.data_src, output=self.output_data, selected=self.selected_idxs, label=select.value), code='''
            console.log(selected) # getting []
            console.log(src.selected.indices) #getting []
            console.log(label) # getting ''
            '''))
        
        show(p)
        show(Column(select, button))

Hi @emaite it seems to me you have an expectation of a back-and-forth synchronization between a Python runtime, and the JavaScript the does all the work in the browser? If so, you would need to Run a Bokeh Server to achieve that. It is possible to define and embed Bokeh server apps in the notebook, but they have a specific way to define them (see the linked docs). The code above generates standalone output, i.e.it executes and produces static HTML and JS that has no persistent connection to any Python runtime.

Otherwise I’d just mention:

  • passing label=select.value to args configures the CustomJS with whatever value select.value has when the Python code is run, i.e. the empty string. If in stead you pas widget=select to args then the CustomJS with be configured with the “live” JavaScript widget, so in in the JS code you could inspect wiget.value to get the current value. Generally speaking it usually only makes sense to pass Bokeh objects in the args dict.

  • similar comments about passing self.selected. The python code runs once, and passes an empty list, which turns into an empty array in the CustomJS code. Modifying this JS Array has no effect on any Python code anywhere. The only things that can be synchronized automatically are Bokeh model objects (e.g. a widget, or a plot, or a layout) and unless you run a Bokeh server, the synchronizationis only one-time, one-way (when the Python code is run to generate the static HTML output)

1 Like

Thanks a lot!
this really helped!

1 Like