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

勾选复选框时Bokeh图表无法显示的技术问题求助

需求实现:勾选DataTable复选框显示对应ID的Bokeh图表

需求说明

  • 基于包含id、data_point、max、min列的DataFrame,实现勾选ID旁复选框时,展示对应ID的Bokeh折线图
  • 若data_point为空、None或空数组[],则不显示该ID的图表
  • 每个唯一ID需独立控制显示/隐藏,单ID图表样式如下:
    单ID图表样式

原始尝试代码

import logging
import random
import pandas as pd
from bokeh.plotting import figure
from bokeh.models import HoverTool, ColumnDataSource, DataTable, CustomJS
from bokeh.palettes import Turbo256

data = df

if len(data) == 0:
    logging.debug(" No data")
else:
    req_hover = HoverTool(
        tooltips=[
            ('data_point: ', '@data_point'),
            ('id: ', '@id')])

    req_figure = figure(title='Graph', x_axis_label="Index", y_axis_label="id/data_point [ms]",
                        plot_width=800, plot_height=400, output_backend="webgl")

    req_figure.add_tools(handover_hover)

    id_values = data['id'].drop_duplicates()

    column_view_data = data[
        ['id', 'tag', 'sw', 'name']].drop_duplicates().dropna()
    column_view_data_source = ColumnDataSource(column_view_data)

    data_table = DataTable(selectable='checkbox', source=column_view_data_source, columns=columns, width=1450,
                           height=400, css_classes=['blueTable'])

    name_dict_req = {'name': [], 'legend': [], 'label': []}

    logging.info('START OF DRAWINGS')

    for ind, element in enumerate(elements):
        d = []
        for i, item in data.iterrows():
            it_color = Turbo256[random.randint(0, 255)]
            if element == item['id']:
                if item['data_point'] is None or str(item['data_point']) == '[]':
                    item['data_point'] = '0'
                else:
                    item['data_point'] = [float(x) for x in item['data_point'].strip('[').strip(']').split(',')]
                raw_data_element_count = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
                                          20]  # this is my x axis which will be 1 to 20

                d.append({'id': id,
                          'number of raw data points': data_point_element_count,
                          'data_point': item['data_point']})
                d_new = pd.DataFrame(d)
                name_glyph_req = req_figure.line(x='number of raw data points',
                                                 y='data_point',
                                                 line_width=2,
                                                 legend_label=str(item['id']),
                                                 source=d_new,
                                                 color=it_color)

                name_dict_req['name'].append(name_glyph_req)
                name_dict_req['label'].append(str(item['id']))

    logging.info('AFTER DRAWINGS LOOP')

    for label in range(len(data.id.unique())):
        name_dict_req['legend'].append(req_figure.legend.items[label])

    initial_value = []
    options = list(data.id.unique())
    for i, name in enumerate(options):
        options[i] = str(name)

    for i in range(len(options)):
        if name_dict_req['label'][i] in initial_value:
            name_dict_req['name'][i].visible = True
            name_dict_req['legend'][i].visible = True
        else:
            name_dict_req['name'][i].visible = False
            name_dict_req['legend'][i].visible = False

    #########################
    callback_datatable = CustomJS(args=dict(source=column_view_data_source), code=callback_file_content_mq)
    ###################

    column_view_data_source.selected.js_on_change('indices', callback_datatable)

    req_figure.legend.location = "top_left"
    req_figure.legend.click_policy = "hide"
    logging.info('END DRAWINGS END SETUP')

修正后的完整实现代码

核心改进

  • 预处理过滤无效data_point的ID
  • 用字典存储每个ID对应的图表元素,简化控制逻辑
  • 完善JS回调,实现复选框与图表显示的联动
  • 修复原始代码中未定义变量、变量名错误等问题
import logging
import random
import pandas as pd
from bokeh.plotting import figure, show
from bokeh.models import HoverTool, ColumnDataSource, DataTable, TableColumn, CustomJS
from bokeh.palettes import Turbo256
from bokeh.layouts import column

# 替换为你的实际DataFrame
data = pd.DataFrame({
    'id': ['A', 'A', 'B', 'C', 'C'],
    'data_point': ['[1,2,3,4,5]', None, '[6,7,8,9,10]', '[11,12,13,14,15]', '[]'],
    'tag': ['tag1', 'tag1', 'tag2', 'tag3', 'tag3'],
    'sw': ['sw1', 'sw1', 'sw2', 'sw3', 'sw3'],
    'name': ['nameA', 'nameA', 'nameB', 'nameC', 'nameC'],
    'max': [5,5,10,15,15],
    'min': [1,1,6,11,11]
})

# 预处理:过滤无效data_point
def process_data_point(val):
    if pd.isna(val) or str(val).strip() in ('[]', ''):
        return None
    try:
        return [float(x.strip()) for x in str(val).strip('[]').split(',')]
    except:
        return None

data['processed_data'] = data['data_point'].apply(process_data_point)
valid_ids = data[data['processed_data'].notna()]['id'].unique()
data = data[data['id'].isin(valid_ids)]

if len(data) == 0:
    logging.debug("No data available")
else:
    # 创建Hover工具
    req_hover = HoverTool(
        tooltips=[
            ('ID', '@id'),
            ('数据点', '@data_point')
        ]
    )

    # 创建基础图表
    req_figure = figure(
        title='ID数据折线图',
        x_axis_label='数据点索引',
        y_axis_label='数据值 [ms]',
        plot_width=800,
        plot_height=400,
        output_backend="webgl"
    )
    req_figure.add_tools(req_hover)

    # 准备DataTable数据
    column_view_data = data[['id', 'tag', 'sw', 'name']].drop_duplicates().reset_index(drop=True)
    column_view_data_source = ColumnDataSource(column_view_data)

    # 定义表格列
    columns = [
        TableColumn(field='id', title='ID'),
        TableColumn(field='tag', title='标签'),
        TableColumn(field='sw', title='软件版本'),
        TableColumn(field='name', title='名称')
    ]
    data_table = DataTable(
        selectable='checkbox',
        source=column_view_data_source,
        columns=columns,
        width=1450,
        height=400,
        css_classes=['blueTable']
    )

    # 存储每个ID对应的图表元素
    id_glyph_map = {}
    x_axis = list(range(1, 21))  # 固定x轴1-20

    # 绘制每个有效ID的折线
    for unique_id in valid_ids:
        id_data = data[(data['id'] == unique_id) & (data['processed_data'].notna())]
        if len(id_data) == 0:
            continue
        
        # 构造匹配x轴的数据源
        plot_data = []
        for _, row in id_data.iterrows():
            data_vals = row['processed_data']
            matched_vals = data_vals + [None]*(20 - len(data_vals)) if len(data_vals) <20 else data_vals[:20]
            plot_data.extend([
                {'id': unique_id, 'x': x, 'data_point': val}
                for x, val in zip(x_axis, matched_vals) if val is not None
            ])
        
        source = ColumnDataSource(data=plot_data)
        color = Turbo256[random.randint(0, 255)]
        glyph = req_figure.line(
            x='x',
            y='data_point',
            line_width=2,
            legend_label=str(unique_id),
            source=source,
            color=color,
            visible=False  # 默认隐藏
        )
        id_glyph_map[unique_id] = glyph

    # 配置图例
    req_figure.legend.location = "top_left"
    req_figure.legend.click_policy = "hide"

    # 复选框联动回调
    callback_code = """
        const selected_ids = source.data.id.filter((_, idx) => source.selected.indices.includes(idx));
        Object.keys(id_glyph_map).forEach(id => {
            const glyph = id_glyph_map[id];
            glyph.visible = selected_ids.includes(id);
        });
    """

    callback_datatable = CustomJS(
        args=dict(source=column_view_data_source, id_glyph_map=id_glyph_map),
        code=callback_code
    )
    column_view_data_source.selected.js_on_change('indices', callback_datatable)

    # 布局并展示
    layout = column(data_table, req_figure)
    show(layout)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 11:01:08