Bokeh多仪表盘生成器下拉菜单失效问题求助
Bokeh 2.4.3下拉菜单无法切换仪表盘的问题排查与解决
问题描述
我编写了一个脚本,通过easgui收集用户关注的系统名称、参数及其他配置,使用Bokeh 2.4.3版本为每个系统生成包含对应参数数据的仪表盘。但生成后用于切换系统仪表盘的下拉菜单仅显示默认项,选择其他选项无法切换显示内容。
最小复现代码如下:
import os import pandas as pd import numpy as np import os from bokeh import plotting from bokeh import layouts from bokeh.models.tools import HoverTool from bokeh.models.widgets.markups import Div from bokeh.models import CustomJS, TapTool, Button, ColumnDataSource, Select, DatetimeTickFormatter from bokeh.io import curdoc systems=['TestSys1', 'TestSys2'] dashboards={} dash_sources={} index_list = list(range(len(systems))) sys_figs={} for idx in index_list: system=systems[idx] save_loc=r'.\Test' customer='TestCustomer' all_or_view='all' conceal_params=False sys_figs[system] = {} system_params=['p_1', 'p_2', 'p_3', 'p_4'] first_logtime = '1990-01-01 00:00:00.000' last_logtime = '2023-01-01 00:00:00.000' # Initialise objects for tracking things that need to be tracked indices = list(range(len(system_params))) shared_x_range = None figures = {} sources = {param:ColumnDataSource() for param in system_params} for idx in indices: param_name = system_params[idx] param_id = system_params[idx] param_name_normalised = param_name.replace(' ', '') raw_label=param_name dates = pd.date_range(start=first_logtime, end=last_logtime, freq='D') data = np.random.randn(len(dates), 1) df = pd.DataFrame(data=data, index=dates, columns=[param_name]) df.index.name='LogTime' src=ColumnDataSource(df) prepared_param_data={'source':src} # Save the param_last_log_time for this param so that the update function can then query # the database and stream and plot only data that has LogTime greater than the previous # param_last_log_time, if any. sources[param_name].data = dict(prepared_param_data['source'].data) ## Set shared_x_range ## if len(figures) > 0: first_plotted_param = list(figures.keys())[0] shared_x_range=figures[first_plotted_param].x_range else: shared_x_range=None ## Instantiate figures ## if shared_x_range != None: sys_figs[system][param_name] = plotting.figure(x_axis_label='DateTime', y_axis_label=raw_label, x_range=shared_x_range, x_axis_type='datetime', title=raw_label, toolbar_location='right') else: sys_figs[system][param_name] = plotting.figure(x_axis_label='DateTime', y_axis_label=raw_label, x_axis_type='datetime', title=raw_label, toolbar_location='right') ## Plot line glyph for data ## sys_figs[system][param_name].line(x='LogTime', y=raw_label, source=src, color='#47ed00', line_alpha=0.7, muted_alpha=0.2, legend_label=raw_label) param_fig = sys_figs[system][param_name] if param_fig != None: figures[param_name_normalised] = param_fig # Construct dashboard figure_columns=[layouts.column(figures['p_1'], figures['p_2']), layouts.column(figures['p_3'], figures['p_4'])] dashboard = layouts.row(figure_columns) dashboard_title = Div(text=f"{system} Dashboard", style={'font-size':'30px', 'color':'black'}, width=3000) dashboard = layouts.column(dashboard_title, dashboard) dashboards[system]=dashboard dash_sources[system]=sources print(f'Dashboard for {system} created.') # Create a dropdown menu with options for each dashboard key dashboard_keys = list(dashboards.keys()) dashboard_select = Select(title="Select Dashboard", value=dashboard_keys[0], options=dashboard_keys) # Create a ColumnDataSource to hold the selected dashboard selected_dashboard = ColumnDataSource(data={'dashboard': [dashboards[dashboard_keys[0]]]}) def update_dashboard(attrname, old, new): selected_dashboard.data = {'dashboard': [dashboards[new]]} selected_dashboard.change.emit() dashboard_select.on_change('value', update_dashboard) # Create a layout with the dropdown menu and the selected dashboard layout = layouts.column(dashboard_select, selected_dashboard.data['dashboard'][0]) # Add a callback to update the layout when the selected_dashboard changes def update_layout(attrname, old, new): layout.children[1] = selected_dashboard.data['dashboard'][0] selected_dashboard.on_change('data', update_layout) # Add the layout to the current document curdoc().add_root(layout)
启动命令:
bokeh serve --show "C:\\Users\\a00555655\\OneDrive - ONEVIRTUALOFFICE\\Documents\\Test\\StackExchangeExample.py" --port 1535
问题原因
- 错误使用ColumnDataSource存储布局对象:
ColumnDataSource的设计目的是存储可视化所需的数值、字符串等数据,并非用来保存Bokeh的布局组件(如Column、Row)。将布局对象放入ColumnDataSource的data中不会被Bokeh正确识别和处理。 - 冗余的回调逻辑:通过
selected_dashboard中转更新的逻辑完全多余,服务器端回调可以直接操作layout的子组件,不需要额外的数据源触发。
解决方案
去掉ColumnDataSource的中转逻辑,直接在下拉菜单的on_change回调中更新layout的子组件:
修改代码的最后部分(从创建下拉菜单开始):
# Create a dropdown menu with options for each dashboard key dashboard_keys = list(dashboards.keys()) dashboard_select = Select(title="Select Dashboard", value=dashboard_keys[0], options=dashboard_keys) # Create a layout with the dropdown menu and the initial dashboard layout = layouts.column(dashboard_select, dashboards[dashboard_keys[0]]) def update_dashboard(attrname, old, new): # 直接替换layout的第二个子组件为选中的仪表盘 layout.children[1] = dashboards[new] # 绑定下拉菜单的变更事件到回调函数 dashboard_select.on_change('value', update_dashboard) # Add the layout to the current document curdoc().add_root(layout)
说明
- 服务器端回调(
on_change绑定的Python函数)可以直接修改Bokeh文档中的组件,不需要通过ColumnDataSource传递布局对象。 - 直接操作
layout.children是最直接且高效的方式,Bokeh服务器会自动同步更新到前端页面。
内容的提问来源于stack exchange,提问作者Sazed
相关产品推荐
相关产品推荐

