我最近刚开始使用bokeh,但我仍在努力使用它的某些功能。我要在这里实现的目标是,在Jupyter-notebook上构建交互式可视化效果,其中有两个图形和两个Select小部件。我的目标是,如果任何一个小部件中的值都发生更改,则会运行update_data()函数来更新所有必需的数据源,并且两个数字也会相应更改。在我的案例中,这两个数字在水平条形图中显示了足球的头对头统计数据,因此,如果一支球队发生变化,则整个对局都会发生变化,这就是为什么当其中一个变化时,两者都必须更新的原因。
运行代码时,图形和窗口小部件及其数据均与我想要的一样,但是问题是,当我更改其中一个窗口小部件中的值时,图形不会更新。在update_data()函数中,我可以看到调试时数据正确更改,但是图形从未更新。
我也尝试过在update_data函数中再次使用show(),但这只会导致最奇怪的结果,当值更改时在下面创建新的图形,但是即使新图形也不显示更新的数据,但是它给了我以下警告
WARNING:bokeh.embed.util:
You are generating standalone HTML/JS output, but trying to use real Python
callbacks (i.e. with on_change or on_event). This combination cannot work.
Only JavaScript callbacks may be used with standalone output. For more
information on JavaScript callbacks with Bokeh, see:
https://docs.bokeh.org/en/latest/docs/user_guide/interaction/callbacks.html
Alternatively, to use real Python callbacks, a Bokeh server application may
be used. For more information on building and running Bokeh applications, see:
https://docs.bokeh.org/en/latest/docs/user_guide/server.html
我的猜测是,update_data()中的那些更新值仅在函数内更新,并且从不更改原始值。引用bokeh文档
如果不表示基础数据,则无法进行数据可视化。在本节中,将介绍为图提供数据的各种方式,从直接传递数据值到创建ColumnDataSource以及使用CDSView进行过滤
因此,看来我应该能够直接传递变量,也可以直接更改它们。那么,是否有可能使它按照我现在想要的方式工作,或者我在做什么错呢?是我定义应用程序或传递数据的方式,我应该更改什么?
#!/usr/bin/env python3
from bokeh.io import output_notebook
from bokeh.layouts import widgetbox
from bokeh.models.widgets import Select
from bokeh.layouts import row, column, layout
from bokeh.plotting import figure, show
from bokeh.application import Application
from bokeh.application.handlers import FunctionHandler
# Function returns a list of team names
def get_teamnames():
...
return list_of_teamnames
# Function returns head to head statistics between two given teams
# it returns two dictionaries, for team1 and team2. The team1 dictionary
# keys are team1 player names, and their respective values are for example
# how many goals the player has scored against team2. Vice versa for the
# team2 dictionary.
def get_head2head(team1, team2, stat):
...
return team1_data_dictionary, team2_data_dictionary
# Using jupyter-notebook, so output should be notebook
output_notebook()
# Team colors dictionary just associates each team with a color
# will be used later when creating charts
team_colors = {"liverpool": "#c8102E",...}
# First we plot using the following data
team1 = "manchester_united"
team2 = "liverpool"
data1, data2 = get_head2head(team1, team2, "goals")
data1_values = list(data1.values())
data1_keys = list(data1.keys())
data2_values = list(data2.values())
data2_keys = list(data2.keys())
# x_range defined so our two charts will have the same x-dimension
x_range = (min(min(data1_values), min(data2_values))-1, max(max(data1_values), max(data2_values))+1)
# To be able to fit logos in the background nicely, x and y
# positions are calculated
img_x = x_range[1] - ((abs(x_range[0])+abs(x_range[1]))/2)
img_y = len(data1_keys) / 2
# First figure configuration
fig1 = figure(y_range=data1_keys, title=team1, x_range=x_range, toolbar_location=None, tools="")
fig1.hbar(y=data1_keys, height=0.5, left=0, right=data1_values, color=team_colors[team1])
fig1.image_url(url=["../backups/logos/" + team1 + ".png"], x=img_x, y=img_y, w=20, h=20, global_alpha=0.05, anchor="center")
# Second figure configuration
fig2 = figure(y_range=data2_keys, title=team2, x_range=x_range, toolbar_location=None, tools="")
fig2.hbar(y=data2_keys, left=0, height=0.5, right=data2_values, color=team_colors[team2])
fig2.image_url(url=["../backups/logos/" + team2 + ".png"], x=img_x, y=img_y, w=20, h=20, global_alpha=0.05, anchor="center")
# Configure two Select widgets, both will have the same values that can be
# selected
select1 = Select(title="Team1:", value=get_teamnames()[0], options=get_teamnames())
select2 = Select(title="Team2:", value=get_teamnames()[1], options=get_teamnames())
# In this update_data function all data sources are updated.
# It will be run when either Select widget's value changes.
# It is important to always update both data sources, because
# we are displaying head to head statistics, so if one changes,
# both change
def update_data(attrname, old, new):
team1 = select1.value
team2 = select2.value
data1, data2 = get_head2head(team1, team2, "plusminus")
data1_values = list(data1.values())
data1_keys = list(data1.keys())
data2_values = list(data2.values())
data2_keys = list(data2.keys())
x_range = (min(min(data1_values), min(data2_values))-1, max(max(data1_values), max(data2_values))+1)
img_x = x_range[1] - ((abs(x_range[0])+abs(x_range[1]))/2)
img_y = len(data1_keys) / 2
# When either widget's value changes, update_data()
# will be run
select1.on_change("value", update_data)
select2.on_change("value", update_data)
def modify_doc(doc):
doc.add_root(l)
# Define layout and initialize the application
l = layout([fig1, fig2], [select1, select2], sizing_mode="scale_width")
handler = FunctionHandler(modify_doc)
app = Application(handler)
show(app)
为了使其具有交互性,我尝试复制此SO answer's模型。