Search code examples
bokehbokehjs

Once a dropdown option is selected, how do I "change.emit" or "trigger change" on the plot?


Any ideas what's supposed to go where the triple '?'s are?

import pandas as pd
from bokeh.layouts import column
from bokeh.models import CustomJS, ColumnDataSource, Slider, Select
import bokeh.plotting as bp
from bokeh.plotting import Figure, output_file, show
from bokeh.models import HoverTool, DatetimeTickFormatter

# Create an output file
bp.output_file('columnDataSource.html')

# Create your plot as a bokeh.figure object
myPlot = bp.figure(height = 600,
               width = 800,
               y_range=(0,3))

x_values = [1, 2, 3, 4, 5]
y_values = [1, 2, 3, 4, 5]

myPlot.line(x = x_values, y= y_values, line_width=2)

callback = CustomJS(args={
    'source1': {'x': [1,2,3,4], 'y':[1,1,1,1]},
    'source2': {'x': [0,0,0,0], 'y':[2,2,2,2]},
    'source3': {'x': [1,2,3,4], 'y':[1,1,1,1]}}, 

    code="""

    var data1 = source1;
    var data2 = source2;
    var data3 = source3;

    var f = cb_obj.value;

    if(f == 'A'){
    console.log("A selected from dropdown.");
    data1.x = data1.x;
    data1.y = data1.y;
    }

    else if(f == 'B'){
    // Substitute all old data1 values in with data2 values
    console.log("B selected from dropdown.");
    data1.x = data2.x;
    data1.y = data2.y;
    }

    else{
    console.log("C selected.");
    // Substitute all old data1 values in with data3 values
    data1.x = data3.x;
    data1.y = data3.y;
    }

    // Problematic line!
    ???.change.emit();
""")


select = Select(title='Choose', value='A', options=['A','B','C'])
select.js_on_change('value', callback)

layout = column(select, myPlot)

show(layout) # et voilà.

I expect my x and y values to change and plot accordingly to my Bokeh graph.

Nothing is changing at the moment as I don't know what object's "trigger" function I'm supposed to be calling. Please help, I'm new to Bokeh.


Solution

  • You do ColumnDataSource.change.emit() if you updated the data source fields by reference e.g. when you update only x or only y:

    ColumnDataSource.data['x'] = [4, 3, 2, 1]
    ColumnDataSource.change.emit()
    

    When you update them both you do:

    ColumnDataSource.data = new_data
    

    Where new_data is a new json object like {'x': [1], 'y':[2]}. The reason for this is that JS can automatically detect a change when existing object is replaced with a new one but it cannot detect changes by reference so in those cases you need explicitly to call: ColumnDataSource.change.emit() to update the BokehJS model.

    Here is your modified code:

    from bokeh.models import CustomJS, ColumnDataSource, Select, Column
    from bokeh.plotting import figure, show
    
    myPlot = figure(y_range = (0, 4))
    
    data =  {'A': {'x': [1, 2, 3, 4], 'y':[1, 1, 1, 1]},
             'B': {'x': [1, 2, 3, 4], 'y':[2, 2, 2, 2]},
             'C': {'x': [1, 2, 3, 4], 'y':[3, 3, 3, 3]} }
    
    source = ColumnDataSource(data['A'])
    myPlot.line('x', 'y', line_width = 2, source = source)
    
    callback = CustomJS(args = {'source': source, 'data': data},
    code = """source.data = data[cb_obj.value]; """)
    
    select = Select(title = 'Choose', value = 'A', options = ['A', 'B', 'C'])
    select.js_on_change('value', callback)
    
    layout = Column(select, myPlot)
    show(layout)