Bokeh Server应用图表更新异常:Select与CheckboxGroup无响应
问题原因分析与解决方案
我看了你的代码,发现两个核心问题导致Select和CheckboxGroup操作时图表无更新,而Slider看似正常(其实也藏着逻辑错误):
1. 数据源未包含日期索引,x轴逻辑完全错误
你的price数据把日期设为索引,但ColumnDataSource不会自动将DataFrame的索引转为数据源的列。初始画图时,你用data_columns[0]作为x轴,这其实是选中的股票代码(比如AAPL),而不是日期!这意味着你的图表本质是用一支股票的价格作为x轴,另一支作为y轴,完全不是你想要的时间序列图。
Slider操作时,只是过滤了行数据,列名没有变化,旧的线条还能找到对应的数据,所以看起来“正常更新”,但逻辑是错的。而当你切换Select或CheckboxGroup时,数据源的列名改变了,原来的线条绑定的旧列名可能不存在,直接导致图表无显示。
2. 图表线条仅初始化一次,未随控件动态更新
你只在程序启动时调用了一次make_plot生成图表,之后的update函数只更新了数据源,却没更新图表里的线条。当Select或CheckboxGroup改变时,数据源的列发生变化,旧的线条要么找不到对应数据,要么无法新增/删除线条,所以图表看起来毫无反应。
修正后的完整代码
from bokeh.io import curdoc from bokeh.layouts import column, row from bokeh.models import ColumnDataSource, Slider, Select, CheckboxGroup from bokeh.plotting import figure import pandas as pd # 加载数据并确保索引为datetime类型 price = pd.read_excel('price.xlsx', index_col=0) price.index = pd.to_datetime(price.index) # 输入控件 stock = Select(title='Stock', value='AAPL', options=[x for x in price.columns if x not in ['S&P', 'DOW']]) yr_1 = Slider(title='Start year', value=2015, start=2000, end=2020, step=1) yr_2 = Slider(title='End year', value=2020, start=2000, end=2020, step=1) index = CheckboxGroup(labels=['S&P', 'DOW'], active=[0, 1]) def get_data(): # 获取选中的对比指数与股票 compare_index = [index.labels[i] for i in index.active] selected_stock = stock.value select_list = [selected_stock] + compare_index # 筛选列与年份范围 selected = price[select_list].copy() start_year = yr_1.value end_year = yr_2.value selected = selected[(selected.index.year >= start_year) & (selected.index.year <= end_year)] # 归一化处理 for col in selected.columns: first_val = selected[col].dropna().iloc[0] selected[col] = selected[col] / first_val # 将日期索引转为数据源列 selected = selected.reset_index().rename(columns={'index': 'date'}) return ColumnDataSource(selected) def make_plot(source): fig = figure(plot_height=600, plot_width=700, title="Normalized Stock & Index Prices", sizing_mode="scale_both", x_axis_type="datetime") data_columns = [col for col in source.data.keys() if col != 'date'] for col in data_columns: fig.line(x='date', y=col, source=source, line_width=3, line_alpha=0.6, legend_label=col) fig.legend.location = "top_left" return fig def update(attrname, old, new): # 更新数据源 new_source = get_data() source.data = new_source.data # 清空旧线条并重新添加新线条 plot.renderers = [] data_columns = [col for col in source.data.keys() if col != 'date'] for col in data_columns: plot.line(x='date', y=col, source=source, line_width=3, line_alpha=0.6, legend_label=col) plot.legend.location = "top_left" # 初始化数据源与图表 source = get_data() plot = make_plot(source) # 绑定控件更新事件 stock.on_change('value', update) yr_1.on_change('value', update) yr_2.on_change('value', update) index.on_change('active', update) # 布局并添加到文档 inputs = column(stock, yr_1, yr_2, index) curdoc().add_root(row(inputs, plot, width=800)) curdoc().title = "Stocks"
关键修改点说明
- 修复日期索引问题:在
get_data中用reset_index()把日期索引转为date列,之后画图时x轴统一使用'date',终于符合时间序列图表的逻辑。 - 动态更新图表线条:在
update函数中先清空plot的所有渲染器,再根据新数据源的列重新添加线条,这样就能随Select和CheckboxGroup的操作动态新增/删除线条。 - 优化年份过滤:直接用索引的
year属性过滤,比字符串切片更可靠,避免日期格式不统一的问题。 - 优化图例显示:固定图例位置,提升图表可读性。
这样修改后,所有控件操作都能正确触发图表更新,而且图表展示的是各股票/指数的归一化价格随时间的变化,完全符合你的需求。
内容的提问来源于stack exchange,提问作者Bruce
相关产品推荐
相关产品推荐

