1

I am creating a plot that will display a range of product based on their specific attributes. I modeled the design of the graph off of Bokeh's "movies" example. I can run my plot by navigating to the source folder and executing "bokeh serve --show shafts" from Andaconda Prompt.

My issue is that I need to save an HTML file so I can distribute it to multiple people without the accompanying database. If I attempt to save an HTML file

"output_file("slider.html", title="slider.py example")"

from either the movies example or my code and then run the plot from the HTML file the sliders do not update the graph. I believe the issue is that when you run the file from "bokeh serve --show shafts" from Andaconda Prompt it is running on the server and is able to access the python code continuously.

Alternatively, when you run it from the HTML it complies all the code into a JASON format and can no longer access the python code. To get around this Bokeh added small Javascript sections that will continue to update off the server.

Bokeh gives multiple example of how to do this but I don't fully grasp what is being updated in the Javascript to update the graph. I'm not extremely familiar with JS so it makes it a little difficult. A simple example they give is:

from bokeh.layouts import column
from bokeh.models import CustomJS, ColumnDataSource, Slider
from bokeh.plotting import figure, output_file, show

output_file("callback.html")
x = [j*0.005 for j in range(0, 200)]
y = x

source = ColumnDataSource(data=dict(x=x, y=y))
plot = figure(plot_width=400, plot_height=400)
plot.line('x', 'y', source=source, line_width=3, line_alpha=0.6)

callback = CustomJS(args=dict(source=source), code="""
        var data = source.data;
        var f = cb_obj.value
        x = data['x']
        y = data['y']
        for (i = 0; i < x.length; i++) {
            y[i] = Math.pow(x[i], f)
        }
        source.change.emit();
    """)

slider = Slider(start=0.1, end=4, value=1, step=.1, title="power", callback=callback)

layout = column(slider, plot)
show(layout)

The y[i] is clearly what is being updated to update the plot but I can't figure out how it relates to the original python update or why this change can effect the graph which seems to be outside its scope. My real question is in my code, what variables do I need to update in order for the code to run off the Bokeh server.

Bellow is my code. The for control in controls: control.on_change('value'....... is the portion of the code that updates the graph when on the server but I need to replace it with the JavaScript code so it will update when saved as a HTML.

callback = CustomJS(args=dict(source=source), code="""
var data = source.data;
selected = shafts[
    ((data.Weight2g >= min_weight_slider.value) &&
    (data.Weight2g <= max_weight_slider.value) &&
    (data.Butt_Frequency >= min_butt_freq_slider.value) &&
    (data.Butt_Frequency <= max_butt_freq_slider.value) &&
    (data.Tip_Frequency >= min_tip_freq_slider.value) &&
    (data.Tip_Frequency <= max_tip_freq_slider.value) &&
    (data.Torque >= min_torque_slider.value) &&
    (data.Torque <= max_torque_slider.value))
];
data = selected;
source.data = selected;
}
source.change.emit;
""")

min_weight_slider = Slider(title="Minimum Weight", value=40,

    start=40.0, end=200.0, step=0.5, callback = callback)
callback.args["min_weight_slider"] = min_weight_slider

max_weight_slider = Slider(title="Maximum Weight", value=200, start=40.0, end=200.0, step=0.5, callback = callback)
callback.args["max_weight_slider"] = max_weight_slider

min_butt_freq_slider = Slider(title="Minimum Butt Frequency", value=180.0, start=100.0, end=500.0, step=10.0, callback = callback)
callback.args["min_butt_freq_slider"] = min_butt_freq_slider

max_butt_freq_slider = Slider(title="Maximum Butt Frequency", value=500.0, start=100.0, end=500.0, step=10.0, callback = callback)
callback.args["max_butt_freq_slider"] = max_butt_freq_slider

min_tip_freq_slider = Slider(title="Minimum Tip Frequency", value=180, start=100, end=500, step=10, callback = callback)
callback.args["min_tip_freq_slider"] = min_tip_freq_slider

max_tip_freq_slider = Slider(title="Maximum Tip Frequency", value=400, start=100, end=500, step=10, callback = callback)
callback.args["max_tip_freq_slider"] = max_tip_freq_slider

min_torque_slider = Slider(title="Minimum Torque", value=2, start=1, end=20, step=0.1, callback = callback)
callback.args["min_torque_slider"] = min_torque_slider

max_torque_slider = Slider(title="Maximum Torque", value=15, start=1, end=20, step=0.1, callback = callback)
callback.args["max_torque_slider"] = max_torque_slider

x_axis = Select(title="X Axis", options=sorted(axis_map.keys()), value="Butt_Frequency")
callback.args["x_axis"] = x_axis
y_axis = Select(title="Y Axis", options=sorted(axis_map.keys()), value="Tip_Frequency")
callback.args["y_axis"] = y_axis


def select_shafts():
selected = shafts[
    (shafts.Weight2g >= min_weight_slider.value) &
    (shafts.Weight2g <= max_weight_slider.value) &
    (shafts.Butt_Frequency >= min_butt_freq_slider.value) &
    (shafts.Butt_Frequency <= max_butt_freq_slider.value) &
    (shafts.Tip_Frequency >= min_tip_freq_slider.value) &
    (shafts.Tip_Frequency <= max_tip_freq_slider.value) &
    (shafts.Torque >= min_torque_slider.value) &
    (shafts.Torque <= max_torque_slider.value) 
]
return selected      



#updates the 
def update():
df = select_shafts()

        #re-names the above function
x_name = axis_map[x_axis.value]
y_name = axis_map[y_axis.value]

p.xaxis.axis_label = x_axis.value
p.yaxis.axis_label = y_axis.value
p.title.text = "%d shafts selected" % len(df)
source.data = dict(
    x=df[x_name],
    y=df[y_name],
    color=df["color"],
    Manufacture=df["Manufacture"],
    Model = df["Model"],
    Type = df["Type"],
    Weight = df["Weight"],
    Flex=df["Flex"],
    Butt_Frequency = df["Butt_Frequency"],
    Tip_Frequency = df["Tip_Frequency"],
    Torque=df["Torque"],
    Weight2G = df["Weight2g"],
    Availability = df["Availability"],
    alpha=df["alpha"]
)








controls = [min_weight_slider, max_weight_slider,   min_butt_freq_slider, max_butt_freq_slider, min_tip_freq_slider, max_tip_freq_slider, min_torque_slider, max_torque_slider,x_axis, y_axis]


#for control in controls:
#control.on_change('value', lambda attr, old, new: update())

sizing_mode = 'fixed'  # 'scale_width' also looks nice with this   example


inputs = widgetbox(*controls, sizing_mode=sizing_mode)
    #Widget box produced with bokeh

l = layout([
[inputs, p]
], sizing_mode=sizing_mode)

update()  # initial load of the data

curdoc().add_root(l)
curdoc().title = "Shafts"

show(l)

Thanks so much in advance

I wanted to add an update on my attempts to solve my problem. I am realizing that when the program runs on the Bokeh server it is able to continually update the source data that the plot function has access to. When the program runs the JS function it can only update the values inside individual dictionary entry keys.

I am attempting to modify this code to mock my need.

3
  • do you need to use a bokeh server, or do can you make do with just javascript callbacks? Commented Sep 18, 2017 at 14:51
  • @Anthonydouc I ultimately got it running using javascript callbacks. I had to figure out how to pass two dictionaries to the callback, one that remained static with the original data set and one that I modified based on the selected criteria. I spent a lot of time printing outputs to the browser console. Commented Sep 18, 2017 at 21:20
  • Ok looks like the correct approach, and i doubt the time spent will be wasted if you frequently use bokeh. I have frequently maintained the original data in separate data source to the one being used for plotting etc. Commented Sep 18, 2017 at 23:18

1 Answer 1

2

To get my desired result I passed two data sets through the javascript callback. The original data that is never modified and then a copy of the original data that is modified based on selected criteria that ultimatly updates the plot.

callback = CustomJS(args={"orgData": originalData, "modData": sourceData}, code="""

var oData = orgData.data;
var updateData = modData.data;



var holdData = {'x':[],'y':[],'color':[], 'Manufacture':[],'Model':[],'Type':[],
'Weight':[],'Flex':[],'Butt_Frequency':[],'Tip_Frequency':[],
'Torque':[],'WeightMes':[],'Availability':[],'alpha':[]};

console.log(Manufacture_Select.value.includes(oData.Manufacture[1])); 
//console.log(min_weight_slider.value);
//console.log((oData.WeightMes[1] >= min_weight_slider.value) && (oData.WeightMes[1] <= max_weight_slider.value));

var xAxisSelection = String(x_axis.value);
var yAxisSelection = String(y_axis.value);
var avalButSelNames = [];

for (i = 0; i < avalibility_Button.active.length; i++){

    avalButSelNames.push(avalibility_Button.labels[avalibility_Button.active[i]]);

}
console.log(avalButSelNames)
for(i = 0; i < oData.Manufacture.length; i++){
       if((oData.WeightMes[i] >= weight_slider.value[0])&& 
            (oData.WeightMes[i] <= weight_slider.value[1]) &&
            (oData.Butt_Frequency[i] >= butt_freq_slider.value[0]) &&
            (oData.Butt_Frequency[i] <= butt_freq_slider.value[1]) &&
            (oData.Tip_Frequency[i] >= tip_freq_slider.value[0]) &&
            (oData.Tip_Frequency[i] <= tip_freq_slider.value[1]) &&
            (oData.Torque[i] >= torque_slider.value[0]) &&
            (oData.Torque[i] <= torque_slider.value[1]) &&
            (oData.Balance_Point[i] <= Balance_Point_Slider.value[1]) &&
            (oData.Balance_Point[i] >= Balance_Point_Slider.value[0]) &&
            (Manufacture_Select.value.includes(oData.Manufacture[i])) &&
            (Type_Select.value.includes(oData.Type[i]))&&
            (Flex_Select.value.includes(oData.Flex[i]))&&
            (avalButSelNames.includes(oData.Availability[i]))

       ){
           holdData['x'].push(oData[xAxisSelection][i]);
           holdData['y'].push(oData[yAxisSelection][i]);
           holdData['color'].push(oData.color[i]);
           holdData['Manufacture'].push(oData.Manufacture[i]);
           holdData['Model'].push(oData.Model[i]);
           holdData['Type'].push(oData.Type[i]);
           holdData['Weight'].push(oData.Weight[i]);
           holdData['Flex'].push(oData.Flex[i]);
           holdData['Butt_Frequency'].push(oData.Butt_Frequency[i]);
           holdData['Tip_Frequency'].push(oData.Tip_Frequency[i]);
           holdData['Torque'].push(oData.Torque[i]);
           holdData['WeightMes'].push(oData.WeightMes[i]);
           holdData['Availability'].push(oData.Availability[i]);
           holdData['alpha'].push(oData.alpha[i]);
           //console.log(i);
       }
    }




modData.data = holdData;

labels = plot.get('renderers');
labels[0]["attributes"]["axis_label"] = xAxisSelection;
labels[2]["attributes"]["axis_label"] = yAxisSelection;



""")
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.