I want to create an interactive python Bokeh plot. I have two dataframes which are linked by the column names .
When I select a bar in plot1 I want to show in plot 2 the data of dataframe 2 (df2) that belong to that column.
For example the df1 could contain the mean of all columns of df2. If you click on the displayed mean you can see in the second graph the rawdata that formed the basis for the mean.
Unfortunately I cannot get it working and I could not find a comparable example. Below is what I have so far. I assume the error is in mycolumn="@colnames"
and the taptool is not returning what I expect.
Source code below updated according to @bigreddot 's comment
import pandas as pd
import numpy as np
from bokeh.models import ColumnDataSource, TapTool
from bokeh.plotting import figure
from bokeh.layouts import row
#from bokeh.plotting import show
from bokeh.io import curdoc
# data for plot 2
df2 = pd.DataFrame({"A" : np.linspace(10, 20, 10),
"B" : np.linspace(20, 30, 10),
"C" : np.linspace(30, 40, 10),
"D" : np.linspace(40, 50, 10),
"E" : np.linspace(50, 60, 10),})
source2 = ColumnDataSource(
data=dict(
x=list(df2.index.values),
y=list(df2.iloc[:,0].values)
)
)
# data for plot 1
df1 = np.mean(df2)
source1 = ColumnDataSource(
data=dict(
x=list(range(0,df1.shape[0])),
y=list(df1.values),
colnames = list(df1.index.values)
)
)
# Plot graph one with data from df1 and source 1 as barplot
plot1 = figure(plot_height=300, plot_width=400, tools="tap")
plot1.vbar(x='x',top='y',source=source1, bottom=0,width =0.5)
# Plot graph two with data from df2 and source 2 as line
plot2 = figure(plot_height=300, plot_width=400, title="myvalues",
tools="crosshair,box_zoom,reset,save,wheel_zoom,hover")
r1 = plot2.line(x='x',y='y',source =source2, line_alpha = 1, line_width=1)
# safe data from plot 2 for later change in subroutine
ds1 = r1.data_source
def update_plot2(mycolumn):
try:
ds1.data['y'] = df2[mycolumn].values
except:
pass
# add taptool to plot1
taptool = plot1.select(type=TapTool)
taptool.callback = update_plot2(mycolumn="@colnames")
#show(row(plot1,plot2))
curdoc().add_root(row(plot1,plot2))
Eventually @bigreddot 's helped me to find this Bokeh Server callback from tools. Below the code which worked for me:
import pandas as pd
import numpy as np
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.layouts import row
from bokeh.io import curdoc
from random import sample
# data for plot 2
df2 = pd.DataFrame({"A" : sample(np.linspace(10, 20, 10),5),
"B" : sample(np.linspace(20, 30, 10),5),
"C" : sample(np.linspace(30, 40, 10),5),
"D" : sample(np.linspace(40, 50, 10),5),
"E" : sample(np.linspace(50, 60, 10),5),})
source2 = ColumnDataSource(
data=dict(
x=list(df2.index.values),
y=list(df2.iloc[:,0].values)
)
)
# data for plot 1
df1 = np.mean(df2)
source1 = ColumnDataSource(
data=dict(
x=list(range(0,df1.shape[0])),
y=list(df1.values),
colnames = list(df1.index.values)
)
)
# Plot graph one with data from df1 and source 1 as barplot
plot1 = figure(plot_height=300, plot_width=400, tools="tap")
barglyph = plot1.vbar(x='x',top='y',source=source1, bottom=0,width =0.5)
# Plot graph two with data from df2 and source 2 as line
plot2 = figure(plot_height=300, plot_width=400, title="myvalues",
tools="crosshair,box_zoom,reset,save,wheel_zoom,hover")
r1 = plot2.line(x='x',y='y',source =source2, line_alpha = 1, line_width=1)
# safe data from plot 2 for later change in subroutine
ds1 = r1.data_source
def callback(attr, old, new):
patch_name = source1.data['colnames'][new['1d']['indices'][0]]
ds1.data['y'] = df2[patch_name].values
print("TapTool callback executed on Patch {}".format(patch_name))
# add taptool to plot1
barglyph.data_source.on_change('selected',callback)
curdoc().add_root(row(plot1,plot2))