如何用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)
关键细节说明
- 层级数据结构:用嵌套字典定义菜单的层级关系,每个父类别包含
children字段,最终选项包含value字段,方便区分可展开项和最终选择项。 - JS状态跟踪:通过
this.current_path存储当前所在的层级路径,避免丢失层级上下文。 - 视觉区分:给父类别添加
▶符号,返回选项用←,用户能快速识别操作逻辑。 - 独立部署支持:整个逻辑用纯前端JS回调实现,不需要Bokeh服务器,导出为HTML后可以直接在浏览器中打开使用。
扩展优化建议
- 可以根据当前层级动态修改Dropdown的
label,比如进入「漫画分类」后,label显示为「漫画分类 > 选择子项」,更直观。 - 如果需要给最终选项添加点击后的业务逻辑(比如更新图表、触发其他组件),可以在
else分支里添加对应的JS代码。 - 可以给不同层级的选项添加自定义样式(比如父类别用粗体),通过Bokeh的
styles属性或自定义CSS实现。
内容的提问来源于stack exchange,提问作者hamdog
相关产品推荐
相关产品推荐

