I want to plot some data that has multiple features, and want to make an interactive 2d plot where the user can choose the axis from a list of features, to see how any two features are related. However, in the code I have, the plot does not update based on user input.
I'm using Jupyter notebook, and am trying to do the plots with the bokeh package. I want to stick to using bokeh widgets, rather than iPython widgets. Any help would be greatly appreciated.
Here is some minimal code
import numpy as np
import pandas as pd
from bokeh.layouts import row, widgetbox
from bokeh.models import CustomJS, Slider, Select
from bokeh.plotting import figure, output_file, show, ColumnDataSource
from bokeh.io import push_notebook, output_notebook, curdoc
from bokeh.client import push_session
output_notebook()
#create sample pandaframe to work with, this will store the actual data
a = np.arange(50).reshape((5,10))
labels = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]
val_a = pd.DataFrame(a, columns=labels )
# Here is a dict of some keys that I want to be able to pick from for plotting
axis_map = {
"A": "A",
"B": "B",
"C": "C"
}
#This is to update during the callback
code = ''' var data = val_a;
var val1 = x_axis.value;
var val2 = y_axis.value;
x = data['val1'];
y = data['val2'];
source.trigger('change');
print x
'''
source = ColumnDataSource(data=dict(x=[], y=[]))
callback = CustomJS(args=dict(source=source), code=code)
#Create two select widgets to pick the features of interest
x_axis = Select(title="X Axis", options=sorted(axis_map.keys()), value="A", callback = callback)
callback.args["val1"] = x_axis
y_axis = Select(title="Y Axis", options=sorted(axis_map.keys()), value="B", callback = callback)
callback.args["val2"] = y_axis
#plot the figures
plot = figure(plot_width=400, plot_height=400)
plot.circle(x= "x",y="y", source=source, line_width=3, line_alpha=0.6)
#update the plot
def update():
x_name = axis_map[x_axis.value]
y_name = axis_map[y_axis.value]
plot.xaxis.axis_label = x_axis.value
plot.yaxis.axis_label = y_axis.value
print x_name
print val_a[x_name]
source.data = dict(
x=val_a[x_name],
y=val_a[y_name],
)
controls = [ x_axis, y_axis]
for control in controls:
control.on_change('value', lambda attr, old, new: update())
update()
push_notebook()
#Display the graph in a jupyter notebook
layout = row(plot, x_axis, y_axis)
show(layout, notebook_handle=True)
I think to simplify your code, you can just stick with either a JS callback or a python callback, no need for both.
To change the data source you need to feed in the original data to the JS callback, and then select the appropriate values corresponding to the selected value in the widgets.
You can also set the axis labels in the identical way in JS. Not sure if this is exactly your desired implementation, but should take you alot closer.
import numpy as np
import pandas as pd
from bokeh.layouts import row, widgetbox
from bokeh.models import CustomJS, Slider, Select
from bokeh.plotting import figure, output_file, show, ColumnDataSource
from bokeh.io import push_notebook, output_notebook, curdoc
from bokeh.client import push_session
output_notebook()
#create sample pandaframe to work with, this will store the actual data
a = np.arange(50).reshape((5,10))
labels = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"]
val_a = pd.DataFrame(a, columns=labels )
# Here is a dict of some keys that I want to be able to pick from for plotting
axis_map = {
"A": "A",
"B": "B",
"C": "C"
}
#This is to update during the callback
code = ''' var data = source.data;
var value1 = val1.value;
var value2 = val2.value;
var original_data = original_source.data
// get data corresponding to selection
x = original_data[value1];
y = original_data[value2];
data['x'] = x;
data['y'] = y;
source.trigger('change');
// set axis labels
x_axis.axis_label = value1
y_axis.axis_label = value2
'''
source = ColumnDataSource(data=dict(x=val_a['A'], y=val_a['B']))
original_source = ColumnDataSource(data=val_a.to_dict(orient='list'))
#plot the figures
plot = figure(plot_width=400, plot_height=400)
plot.circle(x= "x",y="y", source=source, line_width=3, line_alpha=0.6)
callback = CustomJS(args=dict(source=source, original_source = original_source, x_axis=plot.xaxis[0],y_axis=plot.yaxis[0]), code=code)
#Create two select widgets to pick the features of interest
x_axis = Select(title="X Axis", options=sorted(axis_map.keys()), value="A", callback = callback)
callback.args["val1"] = x_axis
y_axis = Select(title="Y Axis", options=sorted(axis_map.keys()), value="B", callback = callback)
callback.args["val2"] = y_axis
plot.xaxis[0].axis_label = 'A'
plot.yaxis[0].axis_label = 'B'
#Display the graph in a jupyter notebook
layout = row(plot, x_axis, y_axis)
show(layout, notebook_handle=True)