How to make Customjs-for-selections works on multiple plots that share a same datasource?

Basically, I’m trying to define a method that can plot each column of the pandas.dataframe in one html page with row index as X, column as Y; meanwhile, I’d like to make these 2 functions work:
(1) When select dots on one of the plot, those points with same row index will show as selected on other plots;
(2) In the customJS of the source.selected.js_on_change, I would like to store the row indexes of selection in an JS array; currently I use document.write() to test whether the row index has been stored successfully. For this part, I’ve use the official Bokeh example JavaScript callbacks — Bokeh 2.4.3 Documentation as reference.

My problem is: the customJS is not working when multiple plot is displayed. I tested and the row index of selection is not pushed in to JS array.

I’ve attached my code below as reference. Kindly let me know what I did wrong, and how to make the customJS for selection works on multiple plots that shares the same datasource! Appreciate your help in advance!

from bokeh.plotting import figure, output_file, show
from bokeh.models import ColumnDataSource, HoverTool, TapTool, CustomJS
from collections import Counter
from bokeh.layouts import gridplot
from bokeh.layouts import column, row
import pandas as pd
import numpy as np
from sklearn import preprocessing

def draw_distributiondot_new(dataf):
    for column in range(len(dataf.columns.tolist())):
        y = dataf.iloc[:,column].values.tolist()
        if type(y[0]) == str:
            encoder= preprocessing.LabelEncoder().fit(dataf.iloc[:,column])
            dataf.iloc[:,column] = encoder.transform(dataf.iloc[:,column])
    title=dataf.columns.tolist()
    source=ColumnDataSource(dataf)
    z = [[None]*len(title) for _ in range(len(title))]
    
    for column in title:
        y = source.data[column]
        title_sub=column
        x = source.data['index'].tolist()
        TOOLTIPS_html = """
                <div>
                        <span style="font-size: 10px; font-weight: bold;">Info</span><br>
                        <span style="font-size: 8px; color: #0059b3;">Index: [$index]</span><br>
                        <span style="font-size: 8px; color: #0059b3;">Value:@y</span><br>
                    </div>
                </div>
        """   
        hover = HoverTool(tooltips=TOOLTIPS_html)
        p = figure(plot_width=400, plot_height=400, title=title_sub, tools="tap,lasso_select,reset,save")
        p.add_tools(hover)
        p.xaxis.axis_label = "row_number"
        p.yaxis.axis_label = "value" + title_sub
        p.circle(x,y, size=5, line_color="navy", fill_color="red", fill_alpha=0.5) 

        z[title.index(column)][0]=p
    source.selected.js_on_change('indices', CustomJS(args=dict(s1=source), code="""
                const inds = cb_obj.indices;
                const d1 = s1.data;
                const indexes = [];
                for (let i = 0; i < inds.length; i++) {
                    indexes.push(d1['x'][inds[i]])
                }
                setTimeout(() => document.write(indexes.join(',')), 1000);
                //(the line above is for testing purpose only)
                //setTimeout(() => console.log(indexes.join(',')), 1000);
            """)
            )
    layout = gridplot(z)
    show(layout)
    return layout

data = [[623,10],[611,32],[600,33],[623,20],[611,12],[600,12],[423,23],[611,34],[600,54],[423,34],[611,24],[600,62]]
dummy = pd.DataFrame(data,columns=['Score','Age'],dtype=float)
draw_distributiondot_new(dummy)

Hi @saltfish please edit your post to use code formatting so that the code is intelligible (either with the </> icon on the editing toolbar, or triple backtick ``` fences around the code blocks).

Additionally, please update the code so that it is a complete Minimal Reproducible Example (i.e. can be copy and pasted then immediately executed to investigate, without any changes).

Hello Bryan, thx for the quick reply. I’ve edited the code, it should be executed without change now.

You are populating an array of indexes but not doing anything with it. It looks to me like indexes will return the 'x’s for the selected source. What do you want to do with that?

As pe: Providing data — Bokeh 2.4.3 Documentation , if renderers share the same datasource, selecting certain indices of one renderer = those same indices are selected on the other renderers it shares the same datasource with. See the last two sections in particular, they should help.

2 Likes

Hello @ gmerritt123 and thx for the suggestion! I’m saving the "x"s(index of row) for further selection; basically I’d like to select correspoding rows in the data source - which will be display inanother js-written application for displaying data.

I checked the page you provided and indexfilter section Providing data — Bokeh 2.4.3 Documentation should help. However the number of chart (like one plot one view) is predifined, and I’m not sure whether it can work in a loop as for my situation there might be multiple views.

Right. FWIW i’ve had success with the IndexFilter too, you can instantiate one with all datasource indices (i.e. range(len(src.to_df())) and then on CustomJS collect the indices you want to keep as per the conditions of your widgets etc, and update the indices property of the IndexFilter instance. I made an example in this vein discussing a bug here → [BUG] CDSView/CustomJSFilter correct rendering failing dependent on number of indices? · Issue #12041 · bokeh/bokeh · GitHub , maybe it’ll help you out too.