I am plotting several patches that are grouped by a category "group" in the data source. What would I like to achieve is the following: By clicking on one patch, not only the patch itself but all patches of the same group should be highlighted as selected.
I found that ColumnDataSource has an attribute selected. However, manipulating this attribute in the callback function does not have the desired effect.
import os
from bokeh.models import ColumnDataSource, Patches
from bokeh.plotting import figure
from bokeh.layouts import row
from bokeh.io import output_file, curdoc
import pandas as pd
x = [[1,2,4], [3,5,6], [7,9,7], [5,7,6]]
y = [[4,2,1], [6,5,8], [3,9,6], [2,2,1]]
group = ['A', 'A', 'B', 'B']
id = [0,1,2,3]
df = pd.DataFrame(data=dict(x=x, y=y, group=group, id=id))
source = ColumnDataSource(df)
p = figure(tools="tap")
renderer = p.patches('x', 'y', source=source)
# Event handler
def my_tap_handler(attr,old,new):
global source
group_name = source.data['group'][new['1d']['indices'][0]]
group_indices = df['id'][df['group'] == group_name]
source.selected.indices = list(group_indices)
print("source.selected.indices", source.selected.indices)
selected_patches = Patches(fill_color="#a6cee3")
renderer.selection_glyph = selected_patches
# Event
renderer.data_source.on_change("selected", my_tap_handler)
#######################################
# Set up layouts and add to document
curdoc().add_root(row(p, width=800))
You can do the selection in Javascript, but if you really want to do this in Python, here is an example:
import os
from bokeh.models import ColumnDataSource, Patches, CustomJS
from bokeh.plotting import figure
from bokeh.layouts import row
from bokeh.io import output_file, curdoc
import pandas as pd
def app(doc):
x = [[1,2,4], [3,5,6], [7,9,7], [5,7,6]]
y = [[4,2,1], [6,5,8], [3,9,6], [2,2,1]]
group = ['A', 'A', 'B', 'B']
id = [0,1,2,3]
df = pd.DataFrame(data=dict(x=x, y=y, group=group, id=id))
source = ColumnDataSource(df)
p = figure(tools="tap")
renderer = p.patches('x', 'y', source=source)
def my_tap_handler(attr,old,new):
indices = source.selected.indices
if len(indices) == 1:
group = source.data["group"][indices[0]]
new_indices = [i for i, g in enumerate(source.data["group"]) if g == group]
if new_indices != indices:
source.selected = Selection(indices=new_indices)
selected_patches = Patches(fill_color="#a6cee3")
renderer.selection_glyph = selected_patches
source.on_change("selected", my_tap_handler)
doc.add_root(row(p, width=800))
show(app)
I am trying to update Bokeh DataTable by selecting a plot.Not sure why it's not getting updated?In the code below,source_devon is ColumnDataSource for the plot- I wanted Datatable to get updated when we select a particular portion.Currently DataTable is created(initialization),but it doesn't changes on selection
from bokeh.io import curdoc
from bokeh.layouts import layout,row
from bokeh.models import CDSView,HoverTool,GroupFilter,ColumnDataSource,Button,Select,TextInput,Slider,DataTable,TableColumn,DateFormatter,LinearAxis,Range1d,CustomJS,Rect
from bokeh.plotting import figure,output_file,show
from datetime import datetime, timedelta
from bokeh.client import push_session
import pandas as pd
import numpy as np
TOOLS='pan,wheel_zoom,box_zoom,reset,tap,save,lasso_select,xbox_select'
# Select widget
ccy_options = ['AUDUSD', 'USDJPY']
menu = Select(options=['AUDUSD','USDJPY'], value='AUDUSD')
slider = Slider(start=-1000, end=10000, step=1000, value=-1000, title='Volume Cutoff')
# Function to get Order/Trade/Price Datasets
def get_combined_dataset(src,name):
df = src[(src.CCYPAIR == name)].copy()
return ColumnDataSource(data=df)
# Function to Make Plots
def make_plot(source_order):
x = 'DATE'
y = 'PRICE'
y1 = 'Volume'
size = 10
alpha = 0.5
hover = HoverTool(
tooltips = [
('OrderId', '#ORDER_ID_108'),
('Volume', '#Volume'),
('Price', '#PRICE')
]
)
view1 = CDSView(source=source_order, filters=[GroupFilter(column_name='TYPE',group='ORDER'),GroupFilter(column_name='SIDE',group='B')])
view2 = CDSView(source=source_order, filters=[GroupFilter(column_name='TYPE',group='ORDER'),GroupFilter(column_name='SIDE',group='S')])
view3 = CDSView(source=source_order, filters=[GroupFilter(column_name='TYPE',group='TRADE'),GroupFilter(column_name='SIDE',group='B')])
view4 = CDSView(source=source_order, filters=[GroupFilter(column_name='TYPE',group='TRADE'),GroupFilter(column_name='SIDE',group='S')])
view5 = CDSView(source=source_order, filters=[GroupFilter(column_name='TYPE',group='DevonTrade')])
view6 = CDSView(source=source_order, filters=[GroupFilter(column_name='TYPE',group='Prices')])
plot2 = figure(plot_width=1000, plot_height=300, tools=[hover, TOOLS],
title='Agg. Position Snapshot for Devon',
x_axis_label='Date', y_axis_label='Price',x_axis_type="datetime")
plot = figure(plot_width=1000, plot_height=300,tools=TOOLS,x_axis_type="datetime",title='Order/Execution Snapshot with Price Levels')
plot.circle(x=x,y=y,source=source_order,view=view1,alpha=0.6,color='blue')
plot.circle(x=x,y=y,source=source_order,view=view2,alpha=0.6,color='red')
plot.triangle(x=x,y=y,source=source_order,view=view3,alpha=0.6,color='blue')
plot.triangle(x=x,y=y,source=source_order,view=view4,alpha=0.6,color='red')
plot.line(x=x,y=y,source=source_order,view=view6,color='green')
plot2.line(x=x,y=y1,source=source_order,view=view5,color='blue')
plot.legend.location = 'top_left'
return plot,plot2
def make_table(source):
columns = [
TableColumn(field='DATE', title="DATE", formatter=DateFormatter()),
TableColumn(field='CCYPAIR', title="CCYPAIR"),
TableColumn(field='SIDE', title="SIDE"),
TableColumn(field='PRICE', title="PRICE"),
TableColumn(field='TYPE', title="TYPE"),
TableColumn(field='Volume', title="Volume"),
TableColumn(field='ORDER_ID_108', title="ORDERID"),
]
data_table = DataTable(source=source, columns=columns, width=1000, height=200)
return data_table
def update_plot(attrname, old, new):
newccy = menu.value
newvalue = slider.value
src_data_table = get_combined_dataset(Combined,newccy)
DisplayData.data.update(src_data_table.data)
def update_plot(attrname, old, new):
newccy = menu.value
newvalue = slider.value
src_data_table = get_combined_dataset(Combined,newccy)
DisplayData.data.update(src_data_table.data)
def selection_change(attrname, old, new):
data = get_all_dataset(Combined,menu.value)
selected = DisplayData.selected['1d']['indices']
if selected:
data = data.iloc[selected, :]
update_datatable(data)
def update_datatable(data):
src_data_table = get_combined_dataset(data,menu.value)
s2.data.update(src_data_table.data)
# Input Files
date_today = datetime.now()
days = pd.date_range(date_today, date_today + timedelta(5), freq='D')
Combined1 = {'DATE': days,
'CCYPAIR': ['USDJPY', 'USDJPY', 'USDJPY','USDJPY', 'USDJPY', 'USDJPY'],
'SIDE' : ['B', 'B', 'B','B', 'B', 'B'],
'PRICE': [100.00, 200.00, 300.00,100.00, 200.00, 300.00],
'TYPE' : ['ORDER', 'ORDER', 'ORDER','DevonTrade', 'DevonTrade', 'DevonTrade'],
'Volume': [100, 200, 300, 100, 200, 300],
'ORDER_ID_108': [111,222,333,111,222,333]
}
Combined = pd.DataFrame(Combined1)
DisplayData = get_combined_dataset(Combined,menu.value)
plot,plot2 = make_plot(DisplayData)
menu.on_change('value', update_plot)
plot.x_range = plot2.x_range
s2 = ColumnDataSource(data=dict(DATE=[],CCYPAIR=[],SIDE=[],PRICE=[],TYPE=[],Volume=[],ORDER_ID_108=[]))
columns = [
TableColumn(field='DATE', title="DATE", formatter=DateFormatter()),
TableColumn(field='CCYPAIR', title="CCYPAIR"),
TableColumn(field='SIDE', title="SIDE"),
TableColumn(field='PRICE', title="PRICE"),
TableColumn(field='TYPE', title="TYPE"),
TableColumn(field='Volume', title="Volume"),
TableColumn(field='ORDER_ID_108', title="ORDER_ID_108")
]
data_table = DataTable(source=s2,columns=columns,width=1000, height=200)
layout = layout([menu],
[plot],
[plot2],
[data_table])
curdoc().add_root(layout)
DisplayData.on_change('selected', selection_change)
Your main issue is that the source you are updating does not have the same fields as the source containing the original data. In your callback you are trying to access d2['DATES'] and d2['PRICES'], however you defined the fields as 'x' and 'y'. See below code which has corrected this. I also defined the plotting ranges of the second plot so the data will appear.
from bokeh.io import curdoc
from bokeh.layouts import layout,row
from bokeh.models import HoverTool,ColumnDataSource,Button,Select,TextInput,Slider,DataTable,TableColumn,DateFormatter,LinearAxis,Range1d,CustomJS,Rect
from bokeh.plotting import figure,output_file,show
from datetime import datetime, timedelta
from bokeh.client import push_session
import pandas as pd
import numpy as np
TOOLS='pan,wheel_zoom,box_zoom,reset,tap,save,lasso_select,xbox_select'
# Select widget
menu = Select(options=['AUDUSD','USDJPY'], value='USDJPY')
# Function to get Order/Trade/Price Datasets
def get_order_dataset(src,name):
df = src[(src.CCYPAIR == name) & (src.TYPE == 'ORDER') & (src.SIDE == 'B')].copy()
return ColumnDataSource(data=df)
# Function to Make Plots
def make_plot(source_order):
x = 'DATE'
y = 'PRICE'
size = 10
alpha = 0.5
hover = HoverTool(
tooltips = [
('OrderId', '#ORDER_ID_108'),
('Volume', '#Volume'),
('Price', '#PRICE')
]
)
plot = figure(plot_width=1000, plot_height=300, tools=[hover, TOOLS],
title='Order/Execution Snapshot with Price Levels',
x_axis_label='Date', y_axis_label='Price',x_axis_type="datetime",active_drag="xbox_select")
plot.circle(x=x, y=y, size=size, alpha=alpha, color='blue',
legend='Orders', source=source_order,selection_color="orange")
plot.legend.location = 'top_left'
return plot
def update_plot(attrname, old, new):
newccy = menu.value
src_order = get_order_dataset(Combined,newccy)
source_order.data.update(src_order.data)
date_today = datetime.now()
days = pd.date_range(date_today, date_today + timedelta(2), freq='D')
Combined1 = {'DATE': days,
'CCYPAIR': ['USDJPY', 'USDJPY', 'USDJPY'],
'SIDE' : ['B', 'B', 'B'],
'PRICE': [100.00, 200.00, 300.00],
'TYPE' : ['ORDER', 'ORDER', 'ORDER'],
'Volume': [100, 200, 300],
'ORDER_ID_108': [111,222,333]
}
Combined = pd.DataFrame(Combined1)
source_order = get_order_dataset(Combined,menu.value)
plot = make_plot(source_order)
menu.on_change('value', update_plot)
s2 = ColumnDataSource(data=dict(DATE=[], PRICE=[]))
p2 = figure(plot_width=1000, plot_height=400,
tools="", title="Watch Here",x_axis_type="datetime", y_range=(90,310),x_range=(days[0],days[-1]))
p2.circle('DATE', 'PRICE', source=s2, alpha=0.6, size=10)
source_order.callback = CustomJS(args=dict(s2=s2), code="""
var inds = cb_obj.selected['1d'].indices;
console.log(inds)
var d1 = cb_obj.data;
var d2 = s2.data;
d2['DATE'] = []
d2['PRICE'] = []
for (i = 0; i < inds.length; i++) {
d2['DATE'].push(d1['DATE'][inds[i]])
d2['PRICE'].push(d1['PRICE'][inds[i]])
}
s2.change.emit();""")
layout = layout([menu],
[plot],
[p2])
curdoc().add_root(layout)
from bokeh.io import curdoc
from bokeh.layouts import layout,row
from bokeh.models import CDSView,HoverTool,GroupFilter,ColumnDataSource,Button,Select,TextInput,Slider,DataTable,TableColumn,DateFormatter,LinearAxis,Range1d,CustomJS,Rect
from bokeh.plotting import figure,output_file,show
from datetime import datetime, timedelta
from bokeh.client import push_session
import pandas as pd
import numpy as np
TOOLS='pan,wheel_zoom,box_zoom,reset,tap,save,lasso_select,xbox_select'
# Select widget
ccy_options = ['AUDUSD', 'USDJPY']
menu = Select(options=['AUDUSD','USDJPY'], value='AUDUSD')
slider = Slider(start=-1000, end=10000, step=1000, value=-1000, title='Volume Cutoff')
# Function to get Order/Trade/Price Datasets
def get_combined_dataset(src,name):
df = src[(src.CCYPAIR == name)].copy()
return ColumnDataSource(data=df)
# Function to Make Plots
def make_plot(source_order):
x = 'DATE'
y = 'PRICE'
y1 = 'Volume'
size = 10
alpha = 0.5
hover = HoverTool(
tooltips = [
('OrderId', '#ORDER_ID_108'),
('Volume', '#Volume'),
('Price', '#PRICE')
]
)
view1 = CDSView(source=source_order, filters= [GroupFilter(column_name='TYPE',group='ORDER'),GroupFilter(column_name='SIDE',group='B')])
view2 = CDSView(source=source_order, filters=[GroupFilter(column_name='TYPE',group='ORDER'),GroupFilter(column_name='SIDE',group='S')])
view3 = CDSView(source=source_order, filters=[GroupFilter(column_name='TYPE',group='TRADE'),GroupFilter(column_name='SIDE',group='B')])
view4 = CDSView(source=source_order, filters=[GroupFilter(column_name='TYPE',group='TRADE'),GroupFilter(column_name='SIDE',group='S')])
view5 = CDSView(source=source_order, filters=[GroupFilter(column_name='TYPE',group='DevonTrade')])
view6 = CDSView(source=source_order, filters=[GroupFilter(column_name='TYPE',group='Prices')])
plot2 = figure(plot_width=1000, plot_height=300, tools=[hover, TOOLS],
title='Agg. Position Snapshot for Devon',
x_axis_label='Date', y_axis_label='Price',x_axis_type="datetime")
plot = figure(plot_width=1000, plot_height=300,tools=TOOLS,x_axis_type="datetime",title='Order/Execution Snapshot with Price Levels')
plot.circle(x=x,y=y,source=source_order,view=view1,alpha=0.6,color='blue')
plot.circle(x=x,y=y,source=source_order,view=view2,alpha=0.6,color='red')
plot.triangle(x=x,y=y,source=source_order,view=view3,alpha=0.6,color='blue')
plot.triangle(x=x,y=y,source=source_order,view=view4,alpha=0.6,color='red')
plot.line(x=x,y=y,source=source_order,view=view6,color='green')
plot2.line(x=x,y=y1,source=source_order,view=view5,color='blue')
plot.legend.location = 'top_left'
return plot,plot2
def make_table(source):
columns = [
TableColumn(field='DATE', title="DATE", formatter=DateFormatter()),
TableColumn(field='CCYPAIR', title="CCYPAIR"),
TableColumn(field='SIDE', title="SIDE"),
TableColumn(field='PRICE', title="PRICE"),
TableColumn(field='TYPE', title="TYPE"),
TableColumn(field='Volume', title="Volume"),
TableColumn(field='ORDER_ID_108', title="ORDERID"),
]
data_table = DataTable(source=source, columns=columns, width=1000, height=200)
return data_table
def update_plot(attrname, old, new):
newccy = menu.value
newvalue = slider.value
src_data_table = get_combined_dataset(Combined,newccy)
DisplayData.data.update(src_data_table.data)
def update_plot(attrname, old, new):
newccy = menu.value
newvalue = slider.value
src_data_table = get_combined_dataset(Combined,newccy)
DisplayData.data.update(src_data_table.data)
def selection_change(attrname, old, new):
data = get_all_dataset(Combined,menu.value)
selected = DisplayData.selected['1d']['indices']
if selected:
data = data.iloc[selected, :]
update_datatable(data)
def update_datatable(data):
src_data_table = get_combined_dataset(data,menu.value)
s2.data.update(src_data_table.data)
# Input Files
date_today = datetime.now()
days = pd.date_range(date_today, date_today + timedelta(5), freq='D')
Combined1 = {'DATE': days,
'CCYPAIR': ['USDJPY', 'USDJPY', 'USDJPY','USDJPY', 'USDJPY', 'USDJPY'],
'SIDE' : ['B', 'B', 'B','B', 'B', 'B'],
'PRICE': [100.00, 200.00, 300.00,100.00, 200.00, 300.00],
'TYPE' : ['ORDER', 'ORDER', 'ORDER','DevonTrade', 'DevonTrade', 'DevonTrade'],
'Volume': [100, 200, 300, 100, 200, 300],
'ORDER_ID_108': [111,222,333,111,222,333]
}
Combined = pd.DataFrame(Combined1)
DisplayData = get_combined_dataset(Combined,menu.value)
plot,plot2 = make_plot(DisplayData)
menu.on_change('value', update_plot)
plot.x_range = plot2.x_range
s2 = ColumnDataSource(data=dict(DATE=[],CCYPAIR=[],SIDE=[],PRICE=[],TYPE= [],Volume=[],ORDER_ID_108=[]))
columns = [
TableColumn(field='DATE', title="DATE", formatter=DateFormatter()),
TableColumn(field='CCYPAIR', title="CCYPAIR"),
TableColumn(field='SIDE', title="SIDE"),
TableColumn(field='PRICE', title="PRICE"),
TableColumn(field='TYPE', title="TYPE"),
TableColumn(field='Volume', title="Volume"),
TableColumn(field='ORDER_ID_108', title="ORDER_ID_108")
]
data_table = DataTable(source=s2,columns=columns,width=1000, height=200)
layout = layout([menu],
[plot],
[plot2],
[data_table])
curdoc().add_root(layout)
DisplayData.on_change('selected', selection_change)
I'm trying to create a bokeh plot of the US States, and color each of the state according to some data. Now using this tutorial I managed to create this, but I also want to enhance it, and add a slider to it, to change the values displayed. For example like displaying separate years.
With the help of this tutorial, I managed to add the slider, and the underlying data does change, according to the hover text, but the colors aren't recalculated, and so the visual representation does not match the values.
This is the code I've used, from a Jupyter notebook, so anybody who wants to try can reproduce
from bokeh.io import show, output_notebook
from bokeh.models import (
ColumnDataSource,
HoverTool,
LogColorMapper,
Range1d, CustomJS, Slider
)
from bokeh.palettes import Inferno256 as palette
from bokeh.plotting import figure
from bokeh.layouts import row, widgetbox
from bokeh.sampledata.us_counties import data as counties
from bokeh.sampledata.us_states import data as states
from bokeh.sampledata.unemployment import data as unemployment
import pandas as pd
import random
output_notebook()
palette.reverse()
states_accumulated ={}
available_state_codes = states.keys()
for key, value in counties.items():
state_name = value["state"].upper()
if state_name in states.keys() and "number" not in states[state_name]:
states[state_name]["number"] = key[0]
for key,state in states.items():
state["code"] = key
state_list = []
for key,state in states.items():
state_list.append(state)
unemployment_transf = []
for key,value in unemployment.items():
unemployment_transf.append({
"State":key[0],
"County":key[1],
"Value":value
})
unemp_df = pd.DataFrame(unemployment_transf)
unemp_sum = unemp_df.groupby("State").mean()["Value"]
unemp_sum = unemp_sum.sort_index()
unemp_sum_flat = {key:value for key, value in unemp_sum.items()}
for state in state_list:
state["value"] = unemp_sum_flat[state["number"]]
state_df = pd.DataFrame(state_list)
color_mapper = LogColorMapper(palette=palette)
state_xy = (list(state_df["lons"].values),list(state_df["lats"].values))
max_x = max([max(l) for l in state_xy[0]])
max_y = max([max(l) for l in state_xy[1]])
min_x = min([min(l) for l in state_xy[0]])
min_y = min([min(l) for l in state_xy[1]])
data=dict(
x=state_xy[0],
y=state_xy[1],
name=list(state_df["name"].values),
used = list(state_df["value"].values)
)
data['1999'] = list(state_df["value"].values)
data['2000'] = [random.randrange(0,10) for i in range(len(state_xy[0]))]
source = ColumnDataSource(data)
TOOLS = "pan,wheel_zoom,reset,hover,save"
p = figure(
title="States", tools=TOOLS,
x_axis_location=None, y_axis_location=None
)
p.width=450
p.height = 450
p.x_range= Range1d(-170,-60)
p.y_range = Range1d(min_y-10,max_y+10)
p.grid.grid_line_color = None
renderer = p.patches('x', 'y', source=source,
fill_color={'field': 'used', 'transform': color_mapper},
fill_alpha=0.7, line_color="white", line_width=0.5)
hover = p.select_one(HoverTool)
hover.point_policy = "follow_mouse"
hover.tooltips = [
("Name", "#name"),
("Unemployment rate)", "#used%"),
("(Long, Lat)", "($x, $y)"),
]
callback = CustomJS(args=dict(source=source,plot=p,color_mapper = color_mapper,renderer = renderer), code="""
var data = source.data;
var year = year.value;
used = data['used']
should_be = data[String(year)]
for (i = 0; i < should_be.length; i++) {
used[i] = should_be[i];
}
""")
year_slider = Slider(start=1999, end=2000, value=1999, step=1,
title="year", callback=callback)
callback.args["year"] = year_slider
layout = row(
p,
widgetbox(year_slider),
)
show(layout)
Sample images of the plot:
What I would like to accomplish, is that when I change the slider, the colors on the plot should change. Now I think the JS callback should call some kind of redraw or recalculate, but I haven't found any documentation about it. Is there a way to do this?
append source.change.emit() to the Javascipt code to trigger the change event.
Appending source.trigger("change"); to the CustomJS seems to solve the problem, now as the slider changes, the colors change.
I am trying to produce a dashboard like interactions for my bar chart using callback function without using bokeh serve functionality. Ultimately, I would like to be able to change the plot if any of the two drop-down menus is changed. So far this only works when threshold value is hard-coded. I only know how to extract cb_obj value but not from dropdown that is not actually called. I have looked at this and this answer to formulate first attempt.
Here is my code:
from bokeh.io import show, output_notebook, output_file
from bokeh.models import ColumnDataSource, Whisker
from bokeh.plotting import figure
from bokeh.transform import factor_cmap
from bokeh.models import CustomJS, ColumnDataSource, Slider, Select
from bokeh.layouts import column
import numpy as np
import pandas as pd
def generate_data(factor=10):
rawdata = pd.DataFrame(np.random.rand(10,4)*factor, columns = ["A","B","C","D"])
idx = pd.MultiIndex.from_product([["Exp "+str(i) for i in range(5)],
[20,999]],names=["Experiment","Threshold"])
rawdata.index = idx
return rawdata.reset_index()
# Generate data
output_notebook()
count_data = generate_data()
error_data = generate_data(factor=2)
groups = ["A","B","C","D"]
initial_counts = count_data[(count_data.Experiment == "Exp 0")
& (count_data.Threshold == 20)][["A","B","C","D"]].values[0]
initial_errors = error_data[(error_data.Experiment == "Exp 0")
& (error_data.Threshold == 20)][["A","B","C","D"]].values[0]
# Create primary sources of data
count_source = ColumnDataSource(data=count_data)
error_source = ColumnDataSource(data=error_data)
# Create plotting source of data
source = ColumnDataSource(data=dict(groups=groups, counts=initial_counts,
upper=initial_counts+initial_errors,
lower=initial_counts-initial_errors))
# Bar chart and figure
p = figure(x_range=groups, plot_height=350, toolbar_location=None, title="Values", y_range=(0,20))
p.vbar(x='groups', top='counts', width=0.9, source=source, legend="groups",
line_color='white', fill_color=factor_cmap('groups', palette=["#962980","#295f96","#29966c","#968529"],
factors=groups))
# Error bars
p.add_layout(
Whisker(source=source, base="groups", upper="upper", lower="lower", level="overlay")
)
def callback(source=source, count_source = count_source, error_source=error_source, window=None):
def slicer(data_source, experiment, threshold, dummy_col, columns):
""" Helper function to enable lookup of data."""
count = 0
for row in data_source[dummy_col]:
if (data_source["Experiment"][count] == experiment) & (data_source["Threshold"][count] == threshold):
result = [data_source[col][count] for col in columns]
count+=1
return result
# Initialise data sources
data = source.data
count_data = count_source.data
error_data = error_source.data
# Initialise values
experiment = cb_obj.value
threshold = 20
counts, upper, lower = data["counts"], data["upper"], data["lower"]
tempdata = slicer(count_data, experiment, threshold,"Experiment", ["A","B","C","D"])
temperror = slicer(error_data, experiment, threshold,"Experiment", ["A","B","C","D"])
# Select values and emit changes
for i in range(len(counts)):
counts[i] = tempdata[i]
for i in range(len(counts)):
upper[i] = counts[i]+temperror[i]
lower[i] = counts[i]-temperror[i]
source.change.emit()
exp_dropdown = Select(title="Select:", value="Exp 0", options=list(count_data.Experiment.unique()))
thr_dropdown = Select(title="Select:", value="12", options=list(count_data.Threshold.astype(str).unique()))
exp_dropdown.callback = CustomJS.from_py_func(callback)
p.xgrid.grid_line_color = None
p.legend.orientation = "horizontal"
p.legend.location = "top_center"
layout = column(exp_dropdown,thr_dropdown, p)
show(layout)
The solution to the question is that Select menu needs to be defined before callback function. This code works:
from bokeh.io import show, output_notebook, output_file
from bokeh.models import ColumnDataSource, Whisker
from bokeh.plotting import figure
from bokeh.transform import factor_cmap
from bokeh.models import CustomJS, ColumnDataSource, Slider, Select
from bokeh.layouts import column
import numpy as np
import pandas as pd
def generate_data(factor=10):
rawdata = pd.DataFrame(np.random.rand(10,4)*factor, columns = ["A","B","C","D"])
idx = pd.MultiIndex.from_product([["Exp "+str(i) for i in range(5)],
[20,999]],names=["Experiment","Threshold"])
rawdata.index = idx
return rawdata.reset_index()
# Generate data
output_notebook()
count_data = generate_data()
error_data = generate_data(factor=2)
groups = ["A","B","C","D"]
initial_counts = count_data[(count_data.Experiment == "Exp 0")
& (count_data.Threshold == 20)][["A","B","C","D"]].values[0]
initial_errors = error_data[(error_data.Experiment == "Exp 0")
& (error_data.Threshold == 20)][["A","B","C","D"]].values[0]
# Create primary sources of data
count_source = ColumnDataSource(data=count_data)
error_source = ColumnDataSource(data=error_data)
# Create plotting source of data
source = ColumnDataSource(data=dict(groups=groups, counts=initial_counts,
upper=initial_counts+initial_errors,
lower=initial_counts-initial_errors))
# Bar chart and figure
p = figure(x_range=groups, plot_height=350, toolbar_location=None, title="Values", y_range=(0,20))
p.vbar(x='groups', top='counts', width=0.9, source=source, legend="groups",
line_color='white', fill_color=factor_cmap('groups', palette=["#962980","#295f96","#29966c","#968529"],
factors=groups))
# Error bars
p.add_layout(
Whisker(source=source, base="groups", upper="upper", lower="lower", level="overlay")
)
exp_dropdown = Select(title="Select:", value="Exp 0", options=list(count_data.Experiment.unique()))
thr_dropdown = Select(title="Select:", value="20", options=list(count_data.Threshold.astype(str).unique()))
def callback(source=source, count_source = count_source, error_source=error_source, exp_dropdown = exp_dropdown,
thr_dropdown=thr_dropdown,window=None):
def slicer(data_source, experiment, threshold, dummy_col, columns):
""" Helper function to enable lookup of data."""
count = 0
for row in data_source[dummy_col]:
if (data_source["Experiment"][count] == experiment) & (data_source["Threshold"][count] == threshold):
result = [data_source[col][count] for col in columns]
count+=1
return result
# Initialise data sources
data = source.data
count_data = count_source.data
error_data = error_source.data
# Initialise values
experiment = exp_dropdown.value
threshold = thr_dropdown.value
counts, upper, lower = data["counts"], data["upper"], data["lower"]
tempdata = slicer(count_data, experiment, threshold,"Experiment", ["A","B","C","D"])
temperror = slicer(error_data, experiment, threshold,"Experiment", ["A","B","C","D"])
# Select values and emit changes
for i in range(len(counts)):
counts[i] = tempdata[i]
for i in range(len(counts)):
upper[i] = counts[i]+temperror[i]
lower[i] = counts[i]-temperror[i]
source.change.emit()
exp_dropdown.callback = CustomJS.from_py_func(callback)
thr_dropdown.callback = CustomJS.from_py_func(callback)
p.xgrid.grid_line_color = None
p.legend.orientation = "horizontal"
p.legend.location = "top_center"
layout = column(exp_dropdown,thr_dropdown, p)
show(layout)