两个交互式散景图:select 一个图中的值并更改另一个
Two interactive bokeh plots: select a value in one graph and change the other
我想创建一个交互式 python 散景图。我有两个由列名链接的数据框。
当我 select plot1 中的一个条形图时,我想在 plot 2 中显示属于该列的数据帧 2 (df2) 的数据。
例如,df1 可以包含 df2 所有列的平均值。如果单击显示的均值,您可以在第二张图中看到构成均值基础的原始数据。
不幸的是,我无法让它工作,也找不到可比较的例子。以下是我到目前为止所拥有的。我假设错误在 mycolumn="@colnames"
中并且 taptool 没有返回我所期望的。
根据@bigreddot 的评论更新了下面的源代码
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))
您缺少一个基本概念。 Bokeh 实际上是两个库,Python Bokeh API 和在浏览器中完成所有工作的 JavaScript BokehJS 库。这些片段可以通过两种方式进行交互:
独立文档
这些是不受 Bokeh 服务器支持的 Bokeh 文档。它们可能有许多工具和交互(例如来自 CustomJS 回调),但它们是 单向 行程,生成独立的 HTML、JavaScript 和 CSS 与任何 Python 运行 时间没有进一步的联系。
散景应用程序
这些是由 Bokeh 服务器支持的 Bokeh 文档,并自动同步 Python 和 JS 状态。除了独立文档的所有功能外,还可以将事件和工具连接到真正的 Python 回调,以在 Bokeh 服务器中执行。
当您像上面那样使用 output_file
、output_notebook
和 show
时,您正在创建一个 独立 Bokeh 文档。这意味着一旦文档在浏览器中显示,就不再与任何 Python 有任何联系。特别是,这意味着您无法访问 Pandas 数据帧或 NumPy 数组之类的东西,也无法在任何回调中使用 Python 代码,因为浏览器根本不知道这些,或者 Python.您只能按照文档 JavaScript Callbacks 部分中的说明使用 CustomJS
回调。
如果你需要运行 真正的Python代码来响应事件、选择、工具等,这就是Bokeh服务器可以提供的.请参阅文档中的 Running a Bokeh Server。
根据您的数据大小,通过预先将 Bokeh 数据源中的所有数据发送到一个 Standlone 文档中,并让在它之间切换的 CustomJS
回调。
最终@bigreddot 帮助我找到了这个。在对我有用的代码下面:
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))
这是独立文档的 JS 回调版本(在 Bokeh 1.0.4 上测试):
from bokeh.layouts import row
from bokeh.models import ColumnDataSource, CustomJS, TapTool
from bokeh.plotting import figure, show
import numpy as np
source_bars = ColumnDataSource({'x': [1, 2, 3], 'y': [2, 4, 1] , 'colnames': ['A', 'B', 'C']})
lines_y = [np.random.random(5) for i in range(3)]
plot1 = figure(tools = 'tap')
bars = plot1.vbar(x = 'x', top = 'y', source = source_bars, bottom = 0, width = 0.5)
plot2 = figure()
lines = plot2.line(x = 'x', y = 'y', source = ColumnDataSource({'x': np.arange(5), 'y': lines_y[0]}))
lines.visible = False
code = '''if (cb_data.source.selected.indices.length > 0){
lines.visible = true;
var selected_index = cb_data.source.selected.indices[0];
lines.data_source.data['y'] = lines_y[selected_index]
lines.data_source.change.emit();
}'''
plots = row(plot1, plot2)
plot1.select(TapTool).callback = CustomJS(args = {'lines': lines, 'lines_y': lines_y}, code = code)
show(plots)
结果:
我想创建一个交互式 python 散景图。我有两个由列名链接的数据框。
当我 select plot1 中的一个条形图时,我想在 plot 2 中显示属于该列的数据帧 2 (df2) 的数据。
例如,df1 可以包含 df2 所有列的平均值。如果单击显示的均值,您可以在第二张图中看到构成均值基础的原始数据。
不幸的是,我无法让它工作,也找不到可比较的例子。以下是我到目前为止所拥有的。我假设错误在 mycolumn="@colnames"
中并且 taptool 没有返回我所期望的。
根据@bigreddot 的评论更新了下面的源代码
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))
您缺少一个基本概念。 Bokeh 实际上是两个库,Python Bokeh API 和在浏览器中完成所有工作的 JavaScript BokehJS 库。这些片段可以通过两种方式进行交互:
独立文档
这些是不受 Bokeh 服务器支持的 Bokeh 文档。它们可能有许多工具和交互(例如来自 CustomJS 回调),但它们是 单向 行程,生成独立的 HTML、JavaScript 和 CSS 与任何 Python 运行 时间没有进一步的联系。
散景应用程序
这些是由 Bokeh 服务器支持的 Bokeh 文档,并自动同步 Python 和 JS 状态。除了独立文档的所有功能外,还可以将事件和工具连接到真正的 Python 回调,以在 Bokeh 服务器中执行。
当您像上面那样使用 output_file
、output_notebook
和 show
时,您正在创建一个 独立 Bokeh 文档。这意味着一旦文档在浏览器中显示,就不再与任何 Python 有任何联系。特别是,这意味着您无法访问 Pandas 数据帧或 NumPy 数组之类的东西,也无法在任何回调中使用 Python 代码,因为浏览器根本不知道这些,或者 Python.您只能按照文档 JavaScript Callbacks 部分中的说明使用 CustomJS
回调。
如果你需要运行 真正的Python代码来响应事件、选择、工具等,这就是Bokeh服务器可以提供的.请参阅文档中的 Running a Bokeh Server。
根据您的数据大小,通过预先将 Bokeh 数据源中的所有数据发送到一个 Standlone 文档中,并让在它之间切换的 CustomJS
回调。
最终@bigreddot 帮助我找到了这个
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))
这是独立文档的 JS 回调版本(在 Bokeh 1.0.4 上测试):
from bokeh.layouts import row
from bokeh.models import ColumnDataSource, CustomJS, TapTool
from bokeh.plotting import figure, show
import numpy as np
source_bars = ColumnDataSource({'x': [1, 2, 3], 'y': [2, 4, 1] , 'colnames': ['A', 'B', 'C']})
lines_y = [np.random.random(5) for i in range(3)]
plot1 = figure(tools = 'tap')
bars = plot1.vbar(x = 'x', top = 'y', source = source_bars, bottom = 0, width = 0.5)
plot2 = figure()
lines = plot2.line(x = 'x', y = 'y', source = ColumnDataSource({'x': np.arange(5), 'y': lines_y[0]}))
lines.visible = False
code = '''if (cb_data.source.selected.indices.length > 0){
lines.visible = true;
var selected_index = cb_data.source.selected.indices[0];
lines.data_source.data['y'] = lines_y[selected_index]
lines.data_source.change.emit();
}'''
plots = row(plot1, plot2)
plot1.select(TapTool).callback = CustomJS(args = {'lines': lines, 'lines_y': lines_y}, code = code)
show(plots)
结果: