我最近学习了如何使用bokeh,但在使python回调与bokeh服务器一起工作时遇到了麻烦。
这是我的代码(我正在使用天气数据构建烛台图:]
from bokeh.client import push_session
from bokeh.plotting import figure, curdoc
from bokeh.models import ColumnDataSource, DatetimeTickFormatter, CDSView, BooleanFilter,HoverTool
from bokeh.models import Select
from bokeh.layouts import column, widgetbox
from make_datasets import hourly_all_winters
# Convert dataset to a column data source
source = ColumnDataSource(data={
'date': hourly_all_winters['date'],
'max': hourly_all_winters['max'],
'min': hourly_all_winters['min'],
'first': hourly_all_winters['first'],
'last': hourly_all_winters['last'],
'average': hourly_all_winters['mean'],
'winter': hourly_all_winters['winter'],
})
# Create first plot and select only the box_zoom and reset tools
y_range = (-30, 30)
p = figure(y_range=y_range, x_axis_type="datetime", plot_width=950, plot_height=200,
title=f"Daily temperature variations - winter {source.data['winter'][0]}",
x_axis_label='Months', y_axis_label='Temperature in °C',
tools="box_zoom,reset")
p.xaxis.formatter = DatetimeTickFormatter(months=['%B'])
# Create the range line glyph
p.segment('date', 'max', 'date', 'min', source=source, color="black")
# Create the ascending bars glyph - we need to create a view of our data with a boolean mask to only plot the data
# we want
booleans_inc = [True if last > first else False for last, first in zip(source.data['last'], source.data['first'])]
view_inc = CDSView(source=source, filters=[BooleanFilter(booleans_inc)])
booleans_dec = [True if last < first else False for last, first in zip(source.data['last'], source.data['first'])]
view_dec = CDSView(source=source, filters=[BooleanFilter(booleans_dec)])
w = 365 * 60 * 2000
p.vbar('date', w, 'first', 'last', source=source, view=view_inc, fill_color="#2c8cd1", line_color="#2c8cd1")
p.vbar('date', w, 'first', 'last', source=source, view=view_dec, fill_color="#F2583E", line_color="#F2583E")
# Create a hover tool so that we can see min, max, first and last values for each record
# over the plot
hover = HoverTool(tooltips=[("First", "@first{first.1f}"),
("Last", "@last{last.1f}"),
("Min", "@min{min.1f}"),
("Max", "@max{max.1f}"), ], mode='vline')
p.add_tools(hover)
# Make a slider object
slider = Select(options=['', '2013-2014', '2014-2015', '2015-2016', '2016-2017', '2017-2018', '2018-2019'],
value='', title='Winter')
def update_plot(attr, old, new):
if new == '':
source.data = ColumnDataSource(data={'date': hourly_all_winters['date'],
'max': hourly_all_winters['max'],
'min': hourly_all_winters['min'],
'first': hourly_all_winters['first'],
'last': hourly_all_winters['last'],
'average': hourly_all_winters['mean'],
'winter': hourly_all_winters['winter'],}).data
else:
new_source = ColumnDataSource(data={'date': hourly_all_winters[hourly_all_winters.winter == winter]['date'],
'max': hourly_all_winters[hourly_all_winters.winter == new]['max'],
'min': hourly_all_winters[hourly_all_winters.winter == new]['min'],
'first': hourly_all_winters[hourly_all_winters.winter == new]['first'],
'last': hourly_all_winters[hourly_all_winters.winter == new]['last'],
'average': hourly_all_winters[hourly_all_winters.winter == new]['mean'],
'winter': hourly_all_winters[hourly_all_winters.winter == new]['winter'],
})
source.data = new_source.data
# Attach the callback to the 'value' property of slider
slider.on_change('value', update_plot)
# put the button and plot in a layout and add to the document
curdoc().add_root(column(widgetbox(slider), p))
show(curdoc())
我尝试从“ 2013-2014”数据和完整数据集开始。每次运行bokeh serve myapp.py
时,基本可视化效果都很好,但是通过下拉列表选择其他时间段只会返回空白图。对我可能错过的事情有什么想法吗?我还尝试使用push_session()来查看是否有帮助(但我知道无论如何它很快都会被弃用)。
编辑-这是hourly_all_winters数据的示例:
Year Month Day first last min max mean date winter
0 2013 10 1 17.3 14.5 14.5 23.1 18.529167 2013-10-01 2013-2014
1 2013 10 2 14.8 14.5 13.9 24.0 18.545833 2013-10-02 2013-2014
2 2013 10 3 13.9 14.3 10.0 21.6 15.516667 2013-10-03 2013-2014
3 2013 10 4 14.7 14.3 13.6 16.1 14.900000 2013-10-04 2013-2014
4 2013 10 5 14.0 13.4 12.8 18.2 14.804167 2013-10-05 2013-2014
我从bokeh community得到了一个很好的答案-因此在此处发布解决方案:
show(curdoc())
不是正确的方法,因此删除最后一行,然后在终端中运行bokeh serve --show myapp.py
应该可以解决问题。source.data = new_source.data
是我的第二个错误,无法将.data从一个CDS“迁移”到另一个CDS。从Bokeh 2.0开始,这将立即产生异常。 (当前导致静默故障)。 .data的值始终必须是纯Python字典。
下面的更新代码有效:
from bokeh.plotting import figure, curdoc
from bokeh.models import ColumnDataSource, DatetimeTickFormatter, CDSView, BooleanFilter,HoverTool
from bokeh.models import Select
from bokeh.layouts import column, widgetbox
from make_datasets import hourly_all_winters
# Convert dataset to a column data source
source = ColumnDataSource(data={
'date': hourly_all_winters['date'],
'max': hourly_all_winters['max'],
'min': hourly_all_winters['min'],
'first': hourly_all_winters['first'],
'last': hourly_all_winters['last'],
'average': hourly_all_winters['mean'],
'winter': hourly_all_winters['winter'],
})
# Create first plot and select only the box_zoom and reset tools
y_range = (-30, 30)
p = figure(y_range=y_range, x_axis_type="datetime", plot_width=950, plot_height=400,
title=f"Daily temperature variations - winter {source.data['winter'][0]}",
x_axis_label='Months', y_axis_label='Temperature in °C',
tools="box_zoom,reset")
p.xaxis.formatter = DatetimeTickFormatter(months=['%B'])
# Create the range line glyph
p.segment('date', 'max', 'date', 'min', source=source, color="black")
# Create the ascending bars glyph - we need to create a view of our data with a boolean mask to only plot the data
# we want
booleans_inc = [True if last > first else False for last, first in zip(source.data['last'], source.data['first'])]
view_inc = CDSView(source=source, filters=[BooleanFilter(booleans_inc)])
booleans_dec = [True if last < first else False for last, first in zip(source.data['last'], source.data['first'])]
view_dec = CDSView(source=source, filters=[BooleanFilter(booleans_dec)])
w = 365 * 60 * 2000
p.vbar('date', w, 'first', 'last', source=source, view=view_inc, fill_color="#2c8cd1", line_color="#2c8cd1")
p.vbar('date', w, 'first', 'last', source=source, view=view_dec, fill_color="#F2583E", line_color="#F2583E")
# Create a hover tool so that we can see min, max, first and last values for each record
# over the plot
hover = HoverTool(tooltips=[("First", "@first{first.1f}"),
("Last", "@last{last.1f}"),
("Min", "@min{min.1f}"),
("Max", "@max{max.1f}"), ], mode='vline')
p.add_tools(hover)
# Make a slider object
slider = Select(options=['', '2013-2014', '2014-2015', '2015-2016', '2016-2017', '2017-2018', '2018-2019'],
value='', title='Winter')
def update_plot(attr, old, new):
if new == '':
source.data = ColumnDataSource(data={'date': hourly_all_winters['date'],
'max': hourly_all_winters['max'],
'min': hourly_all_winters['min'],
'first': hourly_all_winters['first'],
'last': hourly_all_winters['last'],
'average': hourly_all_winters['mean'],
'winter': hourly_all_winters['winter'],}).data
else:
new_source = {'date': hourly_all_winters[hourly_all_winters.winter == new]['date'],
'max': hourly_all_winters[hourly_all_winters.winter == new]['max'],
'min': hourly_all_winters[hourly_all_winters.winter == new]['min'],
'first': hourly_all_winters[hourly_all_winters.winter == new]['first'],
'last': hourly_all_winters[hourly_all_winters.winter == new]['last'],
'average': hourly_all_winters[hourly_all_winters.winter == new]['mean'],
'winter': hourly_all_winters[hourly_all_winters.winter == new]['winter'],
}
source.data = new_source
# Attach the callback to the 'value' property of slider
slider.on_change('value', update_plot)
print(slider.value)
# put the button and plot in a layout and add to the document
curdoc().add_root(column(widgetbox(slider), p))