Skip to content
Advertisement

How to write custom JS callback in Bokeh (Python)?

I try to build a dynamic chart with Bokeh and I’m sutcked with JavaScript part, wording the Custom JS callback. I precise that I am absolutely not familiar with JavaScript.

Here is my dataframe :

 num_tra num_ts Item annee  valeur TRA_label TS_label     TRA   TS   Sensi     Cumul
        1   1    PVFP   10  62     0 bps        0 bps     0     0    Sensi     62
        1   1    PVFP   20  28     0 bps        0 bps     0     0    Sensi     90
        1   1    PVFP   30  87     0 bps        0 bps     0     0    Sensi     177
        1   2    PVFP   10  25     0 bps        - 15 bps  0     -15  Sensi     25
        1   2    PVFP   20  95     0 bps        - 15 bps  0     -15  Sensi     120
        1   2    PVFP   30  95     0 bps        - 15 bps  0     -15  Sensi     215
        2   1    PVFP   10  49     - 10 bps     0 bps     -10   0    Sensi     49
        2   1    PVFP   20  17     - 10 bps     0 bps     -10   0    Sensi     66
        2   1    PVFP   30  98     - 10 bps     0 bps     -10   0    Sensi     164
        2   2    PVFP   10  83     - 10 bps     - 15 bps  -10   -15  Sensi     83
        2   2    PVFP   20  58     - 10 bps     - 15 bps  -10   -15  Sensi     141
        2   2    PVFP   30  52     - 10 bps     - 15 bps  -10   -15  Sensi     193
        1   1    PVFP   10  44     0 bps        0 bps     0     0    Central   44
        1   1    PVFP   20  60     0 bps        0 bps     0     0    Central   104
        1   1    PVFP   30  97     0 bps        0 bps     0     0    Central   201
        1   2    PVFP   10  82     0 bps        - 15 bps  0     -15  Central   82
        1   2    PVFP   20  88     0 bps        - 15 bps  0     -15  Central   170
        1   2    PVFP   30  38     0 bps        - 15 bps  0     -15  Central   208
        2   1    PVFP   10  58     - 10 bps     0 bps     -10   0    Central   58
        2   1    PVFP   20  30     - 10 bps     0 bps     -10   0    Central   88
        2   1    PVFP   30  69     - 10 bps     0 bps     -10   0    Central   157
        2   2    PVFP   10  2      - 10 bps     - 15 bps  -10   -15  Central   2
        2   2    PVFP   20  62     - 10 bps     - 15 bps  -10   -15  Central   64
        2   2    PVFP   30  69     - 10 bps     - 15 bps  -10   -15  Central   133

What I am looking for is a chart with two sliders (slider_TRA & slider_TS) based on the values of variables num_tra and num_ts. Finally, I would like to update the sources of the plot depending on the values of the two sliders.

Based on the Bokeh documentation examples, I tried to build the following code, but have no idea how to handle the JS part :

import numpy as np
import pandas as pd
from bokeh.models import ColumnDataSource, CustomJS, Slider
from bokeh.layouts import column, row
from bokeh.plotting import ColumnDataSource, figure, output_file, show

central=pvfp.loc[pvfp.Sensi=="Central"]
sensi=pvfp.loc[pvfp.Sensi=="Sensi"]

source1 = ColumnDataSource(central)
source2 = ColumnDataSource(sensi)

plot = figure(plot_width=400, plot_height=400)
plot.line('annee', 'valeur', source=source1)
plot.line('annee', 'valeur', source=source2)

slider_TRA = Slider(start=1, end=2, value=1, step=1, title="Sensi TRA")
slider_TS = Slider(start=1, end=2, value=1, step=1, title="Sensi TS")

callback = CustomJS(

    args=dict(source1=source1,source2=source2, slider_TRA=slider_TRA,slider_TS=slider_TS),

    code="""
    const data1 = source1.data;
    const data2 = source2.data;
    const stra = slider_TRA.value;
    const sts = slider_TS.value;
    const num_tra1 = data1['num_tra']
    const num_ts1 = data1['num_ts']
    const num_tra2 = data2['num_tra']
    const num_ts2 = data2['num_ts']
    
    for ...some JS to say : 
    num_tra1=num_tra2=stra
    num_ts1=num_ts2=sts
    
    and
    
    source1=source1.loc[(source1.num_tra==num_tra1)&(source1.num_ts==num_ts1)]
    source2=source2.loc[(source2.num_tra==num_tra2)&(source2.num_ts==num_ts2)]
    
    source1.change.emit();
    source2.change.emit();
    """
)

slider_TRA.js_on_change('value', callback)
slider_TS.js_on_change('value',callback)

layout = row(
    plot,
    column(slider_TRA, slider_TS),
)

show(layout)

As mentioned above, I’m not famliliar with JS and I’m looking for someone who can help me out. If you have any ideas or suggestions, it would be very appreciated.

Advertisement

Answer

This solution works for Bokeh v2.3.0. You need to pass the complete data to the callback function and make there filtering based on the slider values. But you cannot assign the resulting filtered data to the original data as you will loose information this way. So you should assign the filtered data to the data_source object of the corresponding glyphs. Also the starting data for both lines should get filtered according to initial slider positions.

import os
import pandas as pd
from bokeh.models import ColumnDataSource, CustomJS, Slider, BooleanFilter, CDSView
from bokeh.layouts import column, row
from bokeh.plotting import figure, show

pvfp = pd.read_csv(os.path.join(os.path.dirname(__file__), 'data', 'data.csv'), sep = ",")

central = pvfp.loc[pvfp.Sensi=="Central"]
sensi = pvfp.loc[pvfp.Sensi=="Sensi"]

min_tra_central = central['num_tra'].min()
max_tra_central = central['num_tra'].max()
min_ts_central = central['num_ts'].min()
max_ts_central = central['num_ts'].max()

min_tra_sensi = sensi['num_tra'].min()
max_tra_sensi = sensi['num_tra'].max()
min_ts_sensi = sensi['num_ts'].min()
max_ts_sensi = sensi['num_ts'].max()

start_tra = min(min_tra_central, min_tra_sensi)
start_ts = min(min_ts_central, min_ts_sensi)

end_tra = max(max_tra_central, max_tra_sensi)
end_ts = max(max_ts_central, max_ts_sensi)

slider_TRA = Slider(start = start_tra, end = end_tra, value=start_tra, step=1, title="Sensi TRA", show_value = False)
slider_TS = Slider(start = start_ts, end = end_ts, value=start_ts, step=1, title="Sensi TS", show_value = False)

##########################################################################

plot = figure(plot_width=400, plot_height=400)

source_central = ColumnDataSource(central)
source_sensi = ColumnDataSource(sensi)

source_start_central = central.loc[(central['num_tra'] == start_tra) & (central['num_ts'] == start_ts)]
source_start_sensi = sensi.loc[(sensi['num_tra'] == start_tra) & (sensi['num_ts'] == start_ts)]

line_central = plot.line('annee', 'valeur', color = 'red', source=source_start_central)
line_sensi = plot.line('annee', 'valeur', color = 'blue', source=source_start_sensi)

##########################################################################

callback = CustomJS(
    args=dict(source_central=source_central, source_sensi=source_sensi, line_central=line_central, line_sensi=line_sensi, slider_TRA=slider_TRA, slider_TS=slider_TS),
    code="""
    const data_central = source_central.data;
    const data_sensi = source_sensi.data;
    const tra_value = slider_TRA.value;
    const ts_value = slider_TS.value;
    
    var new_central_y = []
    var new_sensi_y = []
    
    for (var i=0; i<data_central['num_tra'].length; i++) {
        if(data_central['num_tra'][i] == tra_value && data_central['num_ts'][i] == ts_value) {
            new_central_y.push(data_central['valeur'][i])
        }
        
        if(data_sensi['num_tra'][i] == tra_value && data_sensi['num_ts'][i] == ts_value) {
            new_sensi_y.push(data_sensi['valeur'][i])
        }
    }
    
    line_central.data_source.data['valeur'] = new_central_y
    line_sensi.data_source.data['valeur'] = new_sensi_y

    line_central.data_source.change.emit();
    line_sensi.data_source.change.emit();
    """
)

slider_TRA.js_on_change('value', callback)
slider_TS.js_on_change('value', callback)

layout = row(
    plot,
    column(slider_TRA, slider_TS),
)

show(layout)

Result: enter image description here

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement