Bokeh 3.8.0 新特性全解析:HoverTool 过滤排序、SizeBar 尺寸标注与会话重连机制
【免费下载链接】bokehInteractive Data Visualization in the browser, from Python项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh
本文以 Bokeh 官方 3.8.0 版本发布说明(docs/bokeh/source/docs/releases/3.8.0.rst)为核心脉络,结合当前仓库中的 Python 与 BokehJS 源码、官方示例与测试配置,逐项深入解读该里程碑版本引入的新能力。读完本文,你将掌握:如何用filters/sort_by/limit精确控制 HoverTool 的提示内容与数量、如何用SizeBar为径向散点图补充"第三维度"、如何通过Document.config统一配置会话重连、通知与色彩模式,以及缓冲区压缩、SVG 图标、CSS 变量主题等底层改进的来龙去脉。
版本概览:3.8.0 在 Bokeh 演进中的定位
Bokeh3.8.0(2025 年 8 月发布)是 Bokeh 项目的一个 minor milestone(次要里程碑)。它并非一次破坏性大重构,而是沿着三条主线稳步推进:
- 交互体验深化:让 HoverTool 具备类表格查询能力的过滤、排序与数量限制;为服务端会话引入自动重连与连接状态通知。
- 可视化表达能力扩展:新增
Plot侧边面板(side panel)布局支持与SizeBar尺寸标注,并引入legend_name参数简化多图例管理。 - 工程与性能基建升级:序列化协议支持缓冲区压缩、图标全面 SVG 化、CSS 变量主题初探、BokehJS 性能优化以及 TypeScript 5.9 构建系统升级。
下文将逐条展开,每条均给出可复制的配置/代码示例,并附上仓库内的源码证据路径。
HoverTool 的数据洞察能力升级:filters、sort_by 与 limit
3.8.0 之前,HoverTool会对命中点的所有数据行弹出 tooltip;当散点图数据密集、命中点众多时,提示内容会变得杂乱无章。3.8.0 为HoverTool增加了三个互补的能力(对应 src/bokeh/models/tools.py 中HoverTool模型的定义):
| 属性 | 类型 | 默认值 | 作用 |
|---|---|---|---|
filters | Dict(String, Either(Instance(CustomJS), List(Instance(CustomJS)))) | {} | 按字段对命中结果做自定义过滤 |
sort_by | Nullable(Either(String, List(...))) | None | 按单个字段或字段序列排序悬停结果 |
limit | Nullable(Positive(Int)) | None | 限制显示 tooltip 的数据点数量 |
filters:按字段精细过滤命中点
filters以字段名为键、CustomJS回调为值,在渲染 tooltip 之前过滤命中结果。回调函数签名与 BokehJS 侧一致,可解构出value、row、index、field、data_source、vars等上下文。源码中的文档示例(src/bokeh/models/tools.py#L1630-L1643)演示了只显示x >= 0的命中点:
from bokeh.models import CustomJS, HoverTool filter_code = ''' export default (args, tool, {value: x, row, index, field, data_source, vars}) => { return x >= 0 } ''' tool = HoverTool(filters={"@x": CustomJS(args={}, code=filter_code)})从 BokehJS 实现看(bokehjs/src/lib/models/tools/inspectors/hover_tool.tsx),filters变化时会触发_update_filters()(约第 187 行),并在渲染 tooltip 前对每条命中记录逐字段执行过滤器,仅保留全部通过的数据行。
sort_by:让提示内容"按需有序"
sort_by支持两种形态:
- 简单字符串:
sort_by="@y",按单个字段排序; - 序列形式:
sort_by=[("@y", "descending"), "@x"],支持逐字段指定排序方向("ascending"/"descending",对应 src/bokeh/core/enums.py#L600-L602 的SortDirection枚举)。
需要说明的是,sort_by影响的是 tooltip 列表的展示顺序,而不是数据源本身。默认排序基于数据索引和/或与命中点的邻近程度;一旦指定sort_by,则按指定字段重新排序命中结果。
limit:限制 tooltip 条数上限
limit直接对最终展示的 tooltip 数量做截断(Positive(Int),不可为负)。BokehJS 侧在收集命中条目后执行entries.splice(limit)(hover_tool.tsx 约第 633-635 行)。合理的limit能显著降低密集散点图在 hover 时的渲染开销与视觉噪声。
三者可以组合使用:先filters剔除无关点,再sort_by排列优先级,最后用limit兜底条数,从而把 HoverTool 从"全量罗列"升级为"精准投递"。
会话可靠性:自动重连、连接事件与 UI 通知
3.8.0 为 Bokeh Server 场景引入了会话层面的可靠性机制:当 WebSocket 会话意外断开时,自动尝试恢复连接,并通过 UI 通知用户连接状态的变化。该能力由新增的文档配置模型DocumentConfig统一管理,其实现位于 src/bokeh/document/config.py。
DocumentConfig目前暴露四个可配置属性:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
reconnect_session | Bool | True | 是否启用会话重连逻辑;若禁用,断开后不再自动恢复 |
notify_connection_status | Bool | True | 是否在 UI 中告知用户连接状态;使用自定义通知系统时可关闭 |
notifications | Nullable(Instance(Notifications)) | Notifications() | 配置或替换通知 UI 与逻辑 |
color_scheme | Enum(ColorScheme) | "auto" | 界面配色方案,取值auto/light/dark |
其中notifications指向的Notifications模型(src/bokeh/models/ui/notifications.py)是一个UIElement,用于"在浏览器视口中显示全局通知(消息/错误)",即连接状态横幅背后的承载组件。color_scheme的取值来自 src/bokeh/core/enums.py#L341-L343 定义的ColorScheme字符串枚举。
一个典型的自定义配置示例:
from bokeh.document import Document from bokeh.models import Notifications doc = Document() doc.config.reconnect_session = True # 保持自动重连(默认即开启) doc.config.notify_connection_status = True # 显示连接状态通知 doc.config.notifications = Notifications() # 可替换为自定义通知组件 doc.config.color_scheme = "dark" # 强制深色 UI对于部署了自定义前端通知体系(如独立的消息中心)的用户,将notify_connection_status设为False可避免 Bokeh 默认横幅与自有系统重复提示。
Plot 侧边面板布局与 SizeBar 标注
3.8.0 引入的SizeBar是本次发布中最具"可视化表现力"的新增标注:它允许在 2D 散点图上为径向 glyph(如Circle、Ngon)提供尺寸比例尺,等价于给散点图增加"第三维度"——用圆半径编码数据值,再用SizeBar告诉读者"多大的圆对应多大的数值"。
SizeBar 的完整属性面
SizeBar继承自BaseBar(两者均定义于 src/bokeh/models/annotations/legends.py,SizeBar本体见 第 915-952 行)。从基类继承的布局与刻度属性(legends.py#L785-L913)包括:
location:"top_right"等锚点枚举或(x, y)屏幕坐标元组;若放置在侧边面板中,通常需要设为(0, 0);orientation:"vertical"/"horizontal"/"auto";width/height:像素尺寸,支持"max"或整数,默认 200 / 50;margin/padding:外部边距(默认 30)与内部留白(默认 10);title与title_standoff:标题文本及与条身的间距;标题默认字号 13px、斜体;ticker/formatter:刻度计算器与格式化器,默认"auto";major_label_overrides/major_label_policy:刻度标签覆盖与防重叠策略(默认NoOverlap);major_tick_in/major_tick_out、minor_tick_in/minor_tick_out:主/次刻度内外延伸长度;bar_*/border_*/background_*:条身轮廓、边框与背景填充样式(背景默认白色、alpha 0.95)。
SizeBar自身专属的三个属性(legends.py#L926-L952):
renderer:Either(GlyphRendererOf(RadialGlyph), Auto),默认"auto"。当图中只有一个径向 glyph renderer 时可安全使用自动模式;多个时需显式指定目标 renderer;bounds:Either(Auto, Tuple(Float, Float)),默认"auto",用于限制显示半径的数值范围;glyph_*(glyph_line_props/glyph_fill_props/glyph_hatch_props):条内示意 glyph 的线/填充/填充纹理样式,glyph_line_color默认None。
BokehJS 侧的实现(bokehjs/src/lib/models/annotations/size_bar.ts)印证了上述设计:renderer == "auto"时会在 plot 的所有 renderer 中筛选径向 glyph renderer,找不到或找到多个时分别给出警告并回退(约第 264-284 行);bounds为"auto"时展开为[-Infinity, Infinity],并基于glyph_view.radius的最小/最大值与 bounds 求交集(约第 304-310 行);绘制时按方向组合标题、条身与刻度(约第 123-213 行)。
官方示例:SizeBar 实战
仓库自带可直接运行的示例 examples/basic/annotations/size_bar.py:
import numpy as np from bokeh.models import SizeBar from bokeh.plotting import figure, show N = 100 x = np.random.random(size=N) * 100 y = np.random.random(size=N) * 100 radii = np.random.random(size=N) * 10 colors = np.array([(r, g, 150) for r, g in zip(50 + 2*x, 30 + 2*y)], dtype=np.uint8) p = figure() cr = p.circle(x, y, radius=radii, fill_color=colors, fill_alpha=0.6, line_color=None) size_bar = SizeBar( renderer=cr, # 绑定圆 renderer(也可省略以使用 "auto") title="SizeBar component", width="max", # 条宽占满可用空间 orientation="horizontal", # 水平条 glyph_fill_color="violet", glyph_fill_alpha=0.8, glyph_line_color="black", border_line_color="gray", border_line_dash="dotted", ) p.add_layout(size_bar, "below") # 挂载到下方侧边面板 show(p)注意p.add_layout(size_bar, "below")正是 3.8.0 侧边面板(side panel)布局能力的一种体现——SizeBar被安放在 plot 的 below 面板中,而非叠加在绘图区域内部。
Document.config:文档级配置的统一入口
DocumentConfig不仅承载会话重连相关配置,它同时也是 3.8.0 引入的"文档配置"(Document.config)入口本身。通过doc.config这一属性,开发者可以在一个位置集中控制文档级行为,避免散落在各处的手工模型属性调整。从 src/bokeh/document/config.py 可以看到,DocumentConfig本身是一个Model子类,因此它同样遵循 Bokeh 的属性系统与序列化流程,可以在服务端设置、随文档传输到前端生效。
这一设计的意义在于:连接策略、通知样式、配色方案等"应用级"偏好,从此有了明确的归属与统一的编程接口,也为后续版本扩展更多文档级配置项预留了空间。
legend_name:更简单的多图例管理
3.8.0 为 glyph API 增加了legend_name关键字参数,用于"按图例名归组",彻底简化了多图例场景下的 renderer 分配。其实现位于 src/bokeh/plotting/_legends.py:
pop_legend_kwarg(约第 62-63 行)从调用参数中取出legend_name;update_legend(第 65-67 行)根据legend_name查找或创建对应的Legend实例;_get_or_create_legend(第 81-107 行)的核心逻辑是:先收集图中所有legend类型的 renderer,若指定了legend_name,则筛选出name == legend_name的图例——找不到时抛出RuntimeError("can't find Legend instance with '...' name"),找到多个时抛出RuntimeError("found multiple Legend instances with '...' name");若未指定名称,则沿用"图中只允许一个图例"的传统约束,否则提示用户"用 name 区分图例,并用 legend_name 参数将 renderer 指派到对应图例"。
典型用法(区别于旧的p.legend单例模式):
from bokeh.plotting import figure p = figure() p.add_layout(Legend(name="first"), "right") p.add_layout(Legend(name="second"), "right") p.circle(x, y, legend_label="first", legend_name="first") p.square(x, y, legend_label="second", legend_name="second")配合Legend.name属性,开发者可以在同一张图中维护多个独立图例,并精准控制每个 renderer 归属于哪个图例,无需再手动拼接p.legend的 renderer 列表。
序列化协议:缓冲区压缩的底层实现
3.8.0 为 Bokeh 序列化协议加入了 buffer(如大型数值数组)的压缩支持,这对大数据集的前端传输有明显收益。其 Python 侧核心位于 src/bokeh/core/serialization.py 的Buffer数据类(第 159-180 行):
@dataclass class Buffer: id: ID data: bytes | memoryview def to_compressed_bytes(self) -> bytes: level = settings.compression_level() # Python 3.11/3.12 存在 mtime=0 时 Gzip 头部 OS 字段不稳定的问题, # 因此这里使用 mtime=1 以保证可复现输出 return gzip.compress(self.to_bytes(), mtime=1, compresslevel=level) def to_base64(self) -> str: return base64.b64encode(self.to_compressed_bytes()).decode("utf-8")对应地,BokehJS 侧的解压逻辑位于 bokehjs/src/lib/core/util/buffer.ts:buffer_to_base64使用fflate的gzipSync(bytes, {mtime: 0})压缩后 base64 编码(第 32-38 行,注释说明固定mtime=0是为了测试结果可复现),base64_to_buffer则用gunzipSync解压还原(第 40-43 行)。
压缩级别通过BOKEH_COMPRESSION_LEVEL环境变量控制,默认值为 2(src/bokeh/settings.py#L656 附近的compression_level设置),即默认启用但级别偏低、速度优先。此外,bokeh serve命令行也暴露了websocket_compression_level与websocket_compression_mem_level参数(见 src/bokeh/command/subcommands/serve.py#L860-L876 的参数收集列表,以及 src/bokeh/server/tornado.py 中对应的 WebSocket 压缩配置),用于调整 WebSocket 通道自身的压缩级别。
视觉与主题:SVG 图标与 CSS 变量主题初探
3.8.0 将原先的 PNG 图标全面转换为 SVG,并对既有图标做了一轮整体刷新。仓库中可见佐证是 bokehjs/src/less/icons/ 目录下 83 个.svg图标文件,它们由 bokehjs/src/less/icons.less 统一组织为图标字体/样式资源。SVG 化的收益在于:任意分辨率下保持清晰、易于通过 CSS 着色与缩放、资源体积更小。
同期引入的"基于 CSS 变量的主题化"属于初步支持(preliminary support)。相关 CSS 变量定义集中在 bokehjs/src/less/vars.less,配合上文的DocumentConfig.color_scheme(auto/light/dark)可让 Bokeh UI 初步跟随系统或应用指定的明暗模式。值得强调的是,该特性在 3.8.0 中仍处于早期阶段,主题变量的覆盖面与稳定性会随后续版本逐步完善。
BokehJS 性能改进与 TypeScript 5.9 构建升级
3.8.0 还包含多项 BokehJS 性能改进(涉及渲染管线与序列化相关路径),以及将 bokehjs 构建系统升级至 TypeScript 5.9 的工程调整。TypeScript 版本升级主要影响库内部开发与构建,对最终用户 API 透明。性能改进的细节分散在 bokehjs/src/lib 各模块中,若需进一步定位,可重点检索核心渲染(core/visuals、models/glyphs)与序列化(core/util)相关目录的近期改动。
升级与验证建议
- 升级方式:通过
pip install bokeh==3.8.0安装对应版本;运行bokeh serve前可通过BOKEH_COMPRESSION_LEVEL调整 buffer 压缩级别(默认 2)。 - 快速验证新特性:
- 运行 examples/basic/annotations/size_bar.py 查看
SizeBar效果; - 为
HoverTool组合filters/sort_by/limit,观察密集散点图 tooltip 的数量与排序变化; - 在 Bokeh Server 应用中通过
doc.config开启重连并切换color_scheme,测试断线恢复与通知展示。
- 运行 examples/basic/annotations/size_bar.py 查看
- 回归测试参考:仓库的测试套件(tests/unit/bokeh、bokehjs/test)覆盖了上述模型属性与序列化路径,升级后建议运行相关单元测试以确认与自定义扩展的兼容性。
总体而言,3.8.0 是一个"体验与基建并重"的版本:面向用户的新交互能力(HoverTool 三件套、SizeBar、legend_name)直接提升了图表的可用性,而会话重连、缓冲区压缩、SVG 图标与 CSS 变量主题则为更大规模、更可靠、更现代的应用形态打下了基础。开发者可以根据自身场景,优先采用其中与业务最相关的若干项能力。
【免费下载链接】bokehInteractive Data Visualization in the browser, from Python项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考