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

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

问题原因

  1. 错误使用ColumnDataSource存储布局对象:ColumnDataSource的设计目的是存储可视化所需的数值、字符串等数据,并非用来保存Bokeh的布局组件(如Column、Row)。将布局对象放入ColumnDataSource的data中不会被Bokeh正确识别和处理。
  2. 冗余的回调逻辑:通过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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 03:25:07