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

如何用Bokeh实现类似XKCD的多级折叠下拉菜单?

实现Bokeh单组件多级折叠下拉菜单(类似XKCD风格)

好问题!Bokeh原生的Dropdown组件本身并不直接支持多级折叠/展开的层级结构——它只能展示平级选项。不过我们可以通过前端JS回调+动态更新菜单内容的方式,完美模拟出你要的单菜单内的多级折叠效果,不需要多个关联下拉组件,也完全可以脱离Jupyter Notebook在独立网页中使用。

核心思路

我们用一个单一的Dropdown组件,通过JS回调跟踪当前所处的层级:

  • 点击父类别时,动态替换菜单选项为对应的子类别,同时添加「返回上一级」的选项
  • 点击「返回上一级」时,切换回上一层级的选项列表
  • 给父类别添加视觉标识(比如▶),让用户能直观区分可展开的选项和最终选项

完整实现代码

from bokeh.models import Dropdown, CustomJS
from bokeh.plotting import show

# 定义多级菜单的层级数据结构
menu_hierarchy = {
    "root": [
        {"label": "▶ 漫画分类", "children": {
            "漫画分类": [
                {"label": "科技漫画", "value": "tech_comics"},
                {"label": "生活漫画", "value": "life_comics"},
                {"label": "▶ 科幻子分类", "children": {
                    "科幻子分类": [
                        {"label": "太空题材", "value": "space"},
                        {"label": "赛博朋克", "value": "cyberpunk"}
                    ]
                }}
            ]
        }},
        {"label": "▶ 专栏内容", "children": {
            "专栏内容": [
                {"label": "每周问答", "value": "qa"},
                {"label": "科普短文", "value": "essay"}
            ]
        }},
        {"label": "关于XKCD", "value": "about"}
    ]
}

# 创建初始Dropdown,默认显示根层级
dropdown = Dropdown(label="选择内容", menu=[item["label"] for item in menu_hierarchy["root"]])

# 编写JS回调,处理层级切换逻辑
dropdown.js_on_event("menu_item_click", CustomJS(args=dict(menu_hierarchy=menu_hierarchy), code="""
    // 跟踪当前层级的路径,默认从root开始
    if (!this.current_path) {
        this.current_path = ["root"];
    }

    const clicked_label = cb_obj.item.label;
    const current_level = menu_hierarchy[this.current_path[this.current_path.length - 1]];
    const clicked_item = current_level.find(item => item.label === clicked_label);

    // 处理「返回上一级」的情况
    if (clicked_label === "← 返回上一级") {
        this.current_path.pop();
        const prev_level = menu_hierarchy[this.current_path[this.current_path.length - 1]];
        this.menu = prev_level.map(item => item.label);
        this.label = "选择内容"; // 可以根据层级动态修改label
        return;
    }

    // 处理可展开的父类别(包含children属性)
    if (clicked_item.children) {
        // 添加返回上一级选项,再拼接子类别选项
        const sub_menu = ["← 返回上一级"].concat(clicked_item.children[clicked_label].map(item => item.label));
        this.menu = sub_menu;
        this.current_path.push(clicked_label);
        this.label = clicked_label.replace("▶ ", ""); // 更新label显示当前层级
    } else {
        // 处理最终选项,这里可以添加你需要的业务逻辑
        console.log("选中了最终选项:", clicked_item.value);
        // 比如可以触发其他操作,比如更新图表内容等
    }
"""))

# 显示组件(导出为HTML的话用output_file+save即可)
show(dropdown)

关键细节说明

  1. 层级数据结构:用嵌套字典定义菜单的层级关系,每个父类别包含children字段,最终选项包含value字段,方便区分可展开项和最终选择项。
  2. JS状态跟踪:通过this.current_path存储当前所在的层级路径,避免丢失层级上下文。
  3. 视觉区分:给父类别添加▶符号,返回选项用←,用户能快速识别操作逻辑。
  4. 独立部署支持:整个逻辑用纯前端JS回调实现,不需要Bokeh服务器,导出为HTML后可以直接在浏览器中打开使用。

扩展优化建议

  • 可以根据当前层级动态修改Dropdown的label,比如进入「漫画分类」后,label显示为「漫画分类 > 选择子项」,更直观。
  • 如果需要给最终选项添加点击后的业务逻辑(比如更新图表、触发其他组件),可以在else分支里添加对应的JS代码。
  • 可以给不同层级的选项添加自定义样式(比如父类别用粗体),通过Bokeh的styles属性或自定义CSS实现。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 04:18:57