Search code examples
pythonpython-3.xpandasbokeh

Bokeh-Source not updating for all lines on plot correctly when changed with customjs callback


I have a dataframe that is concentration over runs for 22 elements. It's structured like this

      run           9Be       45Sc    .....
3/24/16 Run A      0.9280     nan
3/24/16 Run B      1.1052     0.4904
4/6/16 Run A       0.490      0.3374

For each element I need to plot the concentration over runs as well as the mean and standard deviations (as solid lines) for that element. I want to make a bokeh plot where I can select an element in the table and the plot will update with that element's data. Code is below

os.chdir(r'')


low=pd.read_excel(r"", sheet_name="QC LOW", skiprows=5, usecols=range(0,34))
low["run"]=low["run"].astype(str)
low.loc[~(low["run"].str.contains("A")) & ~(low["run"].str.contains("B")),"run"]=pd.to_datetime(low.loc[(~low["run"].str.contains("A")) & (~low["run"].str.contains("B")),"run"]).dt.strftime('%m/%d/%y')
cols=low.columns.tolist()
cols=cols[2:]

select = Select(title="Option:", value="9Be", options=cols)

source=ColumnDataSource(data=low)

#Using 9Be as default. Will be changed when updated
mean=source.data['9Be'].mean()
plus_three_sigma=mean+(source.data['9Be'].std()*3)
minus_three_sigma=mean-(source.data['9Be'].std()*3)
plus_two_sigma=mean+(source.data['9Be'].std()*2)
minus_two_sigma=mean-(source.data['9Be'].std()*2)

tips=[("Run", "@Run"),("Concentration", "$y")]
p = figure(plot_width=1300, plot_height=800, x_range=source.data["run"], tooltips=tips, title="QC Low", x_axis_label="Run ID",y_axis_label="Concentration ng/mL")
p.line(x=source.data["run"], y=mean, line_width=1, color="black")
p.line(x=source.data["run"], y=plus_three_sigma, line_width=1, color="red")
p.line(x=source.data["run"], y=minus_three_sigma, line_width=1, color="red")
p.line(x=source.data["run"], y=minus_two_sigma, line_width=1, color="green",line_dash="dashed")
p.line(x=source.data["run"], y=plus_two_sigma, line_width=1, color="green",line_dash="dashed")
pc=p.circle(x='run', y="9Be",source=source)
p.xaxis.major_label_orientation = 1.2

callback = CustomJS(args=dict(source=source), code="""
    var data=source.data;
    data['9Be'] = data[cb_obj.value];
    source.change.emit();
""")

output_file("output.html")


select.js_on_change('value', callback)

show(row(select,p))

Currently the scatterplot updates correctly when selecting elements...most of the time (it does not always update. For example if I select an element and then try to select 9Be again). But my main issue is that the means and standard deviations are not being updated even though the source should be changing?


Solution

  • You're using a static HTML file created with show, but you need to run Python code when the current item changes. Either you have to run the Python code that computes the mean and other parameters for all columns in advance and store them to be used later in the CustomJS.code, or you have to use bokeh serve to be able to run Python callbacks in response to some user action.

    Another issue with your code is that you change the data in CustomJS.code. Don't do that - you're just overwriting the 9Be values, losing them entirely. Instead, save each renderer (the value returned from p.line, p.circle, etc.), pass the renderers to CustomJS.args (just as with source, you will have to give them some names), and update the relevant field instead of updating the data. It should look something like this in CustomJS.code:

    pc.glyph.y = {field: cb_obj.value};
    

    The {field: ...} part is needed for BokehJS - it's created by Boken on the Python side automatically for you, but you have to apply it manually in CustomJS.code. For static values, like a precomputed mean, it would be something like:

    mean_renderer.glyph.y = {value: current_mean};
    

    How you store current_mean is up to you. You can store all precomputed means in e.g. another data source and just change the column name with {field: ...} instead of changing the value with {value: ...}.