You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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"

关键修改点说明

  1. 修复日期索引问题:在get_data中用reset_index()把日期索引转为date列,之后画图时x轴统一使用'date',终于符合时间序列图表的逻辑。
  2. 动态更新图表线条:在update函数中先清空plot的所有渲染器,再根据新数据源的列重新添加线条,这样就能随Select和CheckboxGroup的操作动态新增/删除线条。
  3. 优化年份过滤:直接用索引的year属性过滤,比字符串切片更可靠,避免日期格式不统一的问题。
  4. 优化图例显示:固定图例位置,提升图表可读性。

这样修改后,所有控件操作都能正确触发图表更新,而且图表展示的是各股票/指数的归一化价格随时间的变化,完全符合你的需求。

内容的提问来源于stack exchange,提问作者Bruce

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 14:57:51