news 2026/9/14 3:42:35

Bokeh 自定义扩展实战:用 Surface3d 封装 vis.js 实现 3D 曲面可视化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Bokeh 自定义扩展实战:用 Surface3d 封装 vis.js 实现 3D 曲面可视化

Bokeh 自定义扩展实战:用 Surface3d 封装 vis.js 实现 3D 曲面可视化

【免费下载链接】bokehInteractive Data Visualization in the browser, from Python项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh

本文以 Bokeh 仓库中的 surface3d 示例 为主线,系统讲解如何通过 Bokeh 的**自定义扩展(Custom Extensions)**机制,把第三方 JavaScript 库(vis.js Graph3d)封装为可在 Python 侧直接实例化的 Bokeh 模型,并通过 Bokeh Server 的ColumnDataSource与周期性回调实现数据驱动的实时 3D 渲染。读完本文,你将掌握自定义扩展的 Python/TypeScript 双端模型结构、属性定义与信号同步机制,并能独立编写、运行自己的扩展型 Bokeh Server 应用。

示例背景:为什么需要自定义扩展

Bokeh 内置了大量绘图能力,但"任何 Web 工具、组件或框架"都不可能全部原生内置。官方在 templates/index.html 中明确指出:借助自定义扩展 + Bokeh Server,可以轻松把 Python 数据分析工具(NumPy、SciPy、Pandas 等)与几乎任意的 Web 库连接起来。

surface3d 正是这一理念的样板:它包装了 vis.js 的 Graph3d 组件,做出一张不断被 Server 端回调刷新的 3D 曲面图,并且支持鼠标拖拽旋转、滚轮/捏合缩放。在 examples/server/app/README.md 的示例总览表中,它的定位被概括为"通过 Bokeh 自定义扩展包装第三方 JavaScript 库的动态 3D 图"。

环境准备与启动

官方 README 强调:运行本示例不需要任何额外安装的包或步骤——因为 Bokeh 本身、NumPy(用于计算曲面数据)都属于既有依赖,vis.js 库则在应用模板中通过 CDN 加载(见下文"模板与静态资源")。

在仓库根目录下进入示例父目录examples/server/app,直接执行:

bokeh serve --show surface3d

--show会在启动 Server 后自动在浏览器新标签页打开应用。这与 examples/server/app/README.md 中描述的统一运行方式一致:bokeh serve --show <脚本名或目录名>。若希望指定端口或地址,可追加--port等标准 Server 参数。该示例同样被 tests/examples.yaml 登记在测试清单中(注明其因无Bokeh.index无法计算 bbox 而走特殊检查路径),说明它是仓库内被 CI 覆盖的正式示例之一。

扩展的 Python 侧:surface3d.py

自定义扩展的核心是成对出现的模型:Python 侧定义属性与默认值,TypeScript 侧定义浏览器渲染行为。先看 surface3d.py。

继承LayoutDOM:让扩展可参与 Bokeh 布局

class Surface3d(LayoutDOM): __implementation__ = "surface3d.ts"

源码注释给出选基类的通用法则:

  • 想让扩展拥有 DOM 视图、可被放入 Bokeh 布局 → 继承LayoutDOM(本示例所选);
  • 想创建自定义工具 → 继承Tool
  • 想创建自定义字形 → 继承Glyph

特殊的类属性__implementation__指向实现浏览器侧逻辑的 TypeScript 文件。从 Bokeh 的编译机制看(见 src/bokeh/util/compiler.py),__implementation__支持多种写法:内联的TypeScript("""...""")/JavaScript("""...""")字符串,或通过FromFile读取.ts.js.css.less独立文件(扩展名决定了语言类型)。本示例直接赋文件路径字符串,等价于从文件读取实现。

属性声明:Python 与浏览器的"自动通信协议"

Bokeh 属性(Property)是类属性,定义模型字段及其类型,由框架在 Python 与浏览器之间自动序列化传输,并附带类型校验。Surface3d 声明了四组属性:

data_source = Instance(ColumnDataSource) # 可被 Server 端 Python 代码更新 x = String() # 指定 ColumnDataSource 中用作 x 的列名 y = String() z = String() options = Dict(String, Any, default=DEFAULTS) # 透传给 vis.js Graph3d 的选项字典

值得注意的设计点:

  • data_source使用Instance(ColumnDataSource),使其可以持有真实数据列,并在 Server 端被更新后自动向浏览器推送变更事件;
  • x/y/z列名字符串而非数值,数据本身存在ColumnDataSource中,模型只声明"取哪一列",实现了解耦;
  • optionsDict(String, Any)Any允许任意 JSON 值,从而把 vis.js Graph3d 的全部可配置项开放给 Python 使用者。

默认选项DEFAULTS

Python 侧DEFAULTS字典(与 TypeScript 侧的OPTIONS常量一一对应,注释要求二者保持一致):

DEFAULTS = { 'width': '600px', 'height': '600px', 'style': 'surface', 'showPerspective': True, 'showGrid': True, 'keepAspectRatio': True, 'verticalRatio': 1.0, 'legendLabel': 'stuff', 'cameraPosition': { 'horizontal': -0.35, 'vertical': 0.22, 'distance': 1.8, }, }

含义速览:width/height固定组件尺寸(源码注释特别说明尺寸在options中固定,若进一步改造可做成响应式);style为曲面绘制风格;showPerspective开启透视投影;showGrid显示网格;keepAspectRatio保持宽高比;verticalRatio垂直方向比例;legendLabel图例标签;cameraPosition设置初始相机位置(水平角、仰角、距离)。这些选项可在实例化时通过options属性覆盖。

扩展的 TypeScript 侧:surface3d.ts

浏览器侧的实现在 surface3d.ts 中,由四个部分组成,结构与 Python 模型严格对应。

1. 外部库类型声明

由于仓库不内置 vis.js 类型定义,文件顶部用declare namespace vis手工声明了Graph3dDataSet的最小类型接口,便于 TypeScript 编译,并注明其"假设 vis.js 已被加载(例如在自定义应用模板中)"。

2. View 子类:负责渲染

export class Surface3dView extends LayoutDOMView { private _graph: vis.Graph3d override render(): void { super.render() this._graph = new vis.Graph3d(this.shadow_el, this.get_data(), to_object(this.model.options)) } ... }
  • 注释解释了 Bokeh 视图的通用约定:View 默认创建<div>元素(即@el),多数视图会忽略它改画 HTML canvas,而这里直接利用该<div>(shadow DOM 中的shadow_el)挂载 Graph3d;
  • child_models返回空数组,表明该布局项没有子 Bokeh 模型。

3. 信号连接:数据变更自动重绘

override connect_signals(): void { super.connect_signals() this.connect(this.model.data_source.change, () => this._graph.setData(this.get_data())) }

这是"Python 改数据 → 浏览器自动更新"的关键一环:监听data_sourcechange信号,触发时把最新数据转换为 vis.js 的DataSet并调用setData重绘。

4. 数据适配:Bokeh → vis.js

get_data(): vis.DataSet { const data = new vis.DataSet() const source = this.model.data_source for (let i = 0; i < source.get_length()!; i++) { data.add({ x: source.get(this.model.x)[i], y: source.get(this.model.y)[i], z: source.get(this.model.z)[i], }) } return data }

其职责单一:遍历ColumnDataSource每一行,按x/y/z属性指定的列名取出数值,组装成{x, y, z}对象加入DataSet——即源码注释所说"把 Bokeh 数据源适配为 vis.js DataSet 格式"。

5. 模型子类与属性注册

export class Surface3d extends LayoutDOM { static __name__ = "Surface3d" // 必须与 Python 类名一致(TS 编译时会自动填充) static { this.prototype.default_view = Surface3dView this.define<Surface3d.Props>(({Unknown, Str, Dict, Ref}) => ({ x: [ Str ], y: [ Str ], z: [ Str ], data_source: [ Ref(ColumnDataSource) ], options: [ Dict(Unknown), OPTIONS ], })) } }

要点:

  • __name__必须与 Python 类名精确一致,否则模型无法序列化/反序列化(源码注释特别提醒避免手写拼错);
  • default_view把模型与 View 绑定;
  • define块注册的属性与 Python 侧一一对应,类型对应关系为String → StrDict(String, Any) → Dict(Unknown)Instance(ColumnDataSource) → Ref(ColumnDataSource);在 JS 类型系统尚不丰富的场景可用Unknown作为通配类型。

数据驱动与动画:main.py 的完整链路

main.py 把以上模型组装成可运行的应用,展示了"Python 计算 → ColumnDataSource → 浏览器 3D 图"的完整数据流。

生成曲面网格数据

x = np.arange(0, 300, 20) y = np.arange(0, 300, 20) xx, yy = np.meshgrid(x, y) xx = xx.ravel() yy = yy.ravel() def compute(t): value = np.sin(xx/50 + t/10) * np.cos(yy/50 + t/10) * 50 + 50 return dict(x=xx, y=yy, z=value)

在 0~300 范围内每 20 取一个采样点,形成 15×15 的网格;compute(t)以时间参数t驱动正弦/余弦乘积,得到随时间变化的波浪曲面。

实例化扩展并挂载到文档

source = ColumnDataSource(data=compute(0)) surface = Surface3d(x="x", y="y", z="z", data_source=source) curdoc().add_root(surface) curdoc().title = "Surface3d"

注意Surface3dx/y/z传入的是列名字符串,与 Python 侧属性定义呼应。

周期性回调驱动动画

from bokeh.driving import count @count() def update(t): source.data = compute(t) curdoc().add_periodic_callback(update, 100)
  • count()来自 src/bokeh/driving.py,是一个"驱动函数"装饰器:每次调用回调时自动注入递增的计数值作为t,从而让compute(t)持续演化;
  • curdoc().add_periodic_callback(update, 100)100 毫秒触发一次update(实现见 src/bokeh/document/document.py),每次执行source.data = compute(t)
  • 该赋值触发ColumnDataSourcechange信号,进而驱动 TypeScript 侧connect_signals中注册的回调 →setData→ 3D 图无刷新重绘。

这就是"Server 端 Python 每 100ms 算一帧、浏览器端同步重绘"的完整闭环。

模板与静态资源:如何加载第三方库

templates/index.html 是一个继承 Bokeh 默认模板({% extends base %})的自定义应用模板,承担两项任务:

  1. 加载 vis.js:在preamble块中引入 CDN 脚本
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vis/4.16.1/vis.min.js"></script>

    这正是surface3d.tsnew vis.Graph3d(...)能全局使用vis的前提;源码注释也说明"未来 Bokeh 模型将能自动指定并加载外部脚本",当前阶段需要模板手动引入;

  2. 页面说明文案:在contents块中输出标题与两段说明,并通过{{ super() }}保留 Bokeh 默认内容(即渲染出的 3D 图)。文中还预告了交互方式——拖拽旋转、滚动/捏合缩放。

同目录的__init__.py为空文件,作用是让examples/server/app/surface3d成为可导入的 Python 包,保证main.pyfrom .surface3d import Surface3d的相对导入成立。

扩展机制总结与可复用模板

把整个示例抽象成"自定义扩展四件套",即可复用到任意第三方 JS 库:

文件角色关键内容
surface3d.pyPython 模型继承LayoutDOM,声明__implementation__与 Bokeh 属性
surface3d.ts浏览器实现LayoutDOMView子类渲染、connect_signals监听数据变更、模型属性注册
main.py应用逻辑构造数据源、实例化扩展、add_periodic_callback驱动动画
templates/index.html应用模板加载第三方库脚本、定制页面文案

写作自定义扩展时的通用要点(均有源码佐证):

  • 基类选择决定能力边界:布局型用LayoutDOM,工具型用Tool,字形型用Glyph(见 surface3d.py 注释);
  • Python 与 TypeScript 的类名必须一致,属性需两侧成对声明;
  • 通过connect_signals监听ColumnDataSource.change,即可在 Server 端任意更新数据而无需触碰 DOM;
  • __implementation__支持内联代码与外部文件两种形态,.ts/.js/.css/.less均可作为实现文件(见 src/bokeh/util/compiler.py)。

借助这套机制,Bokeh 生态之外的 Web 可视化库(尤其是各类 3D、地图、富交互组件)都能被封装成 Python 友好的 Bokeh 模型,与 NumPy 计算、ColumnDataSource数据流和 Server 端回调无缝衔接——这正是 surface3d 示例希望传递的核心价值。

【免费下载链接】bokehInteractive Data Visualization in the browser, from Python项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 3:41:54

SLM工艺仿真与Fluent热源UDF开发实战

1. SLM工艺仿真背景与Fluent方案选型选择性激光熔化&#xff08;Selective Laser Melting, SLM&#xff09;作为金属增材制造的核心工艺&#xff0c;其过程涉及复杂的多物理场耦合现象。传统试错法开发参数成本高昂&#xff0c;而数值仿真成为优化工艺参数的有效手段。在主流CF…

作者头像 李华
网站建设 2026/9/14 3:41:16

Keep AIOps告警管理:部署、接入与降噪

Keep AIOps告警管理&#xff1a;部署、接入与降噪 【免费下载链接】keep The open-source AIOps and alert management platform 项目地址: https://gitcode.com/GitHub_Trending/kee/keep Keep 是一个开源的告警管理与 AIOps 平台&#xff0c;把多个监控工具的告警聚合…

作者头像 李华
网站建设 2026/9/14 3:41:14

OpenHarmony与Flutter融合开发:相机模块实现详解

1. OpenHarmony与Flutter融合开发背景在移动应用开发领域&#xff0c;跨平台框架与操作系统深度结合的案例正在成为新趋势。OpenHarmony作为开源分布式操作系统&#xff0c;其生态建设需要吸引更多开发者参与。而Flutter凭借其出色的跨平台能力和高性能渲染引擎&#xff0c;已经…

作者头像 李华
网站建设 2026/9/14 3:39:34

国产电源芯片选型实战:从DC-DC到LDO的验证清单与避坑指南

过去三个月&#xff0c;我把手里能接触到的国产电源芯片原厂基本摸了一圈&#xff0c;线上加线下&#xff0c;十几家是有的。起因很直接&#xff1a;有个量产项目&#xff0c;一颗进口DC-DC交期拖到二十周&#xff0c;产线等料&#xff0c;方案评估群里天天有人催。人被逼到这份…

作者头像 李华
网站建设 2026/9/14 3:37:10

嵌入式VxWorks NAT协议栈实现:hook机制与TCP状态机映射管理

简介&#xff1a;面向 VxWorks 嵌入式网络开发者&#xff0c;这份资源是一套基于风河系统的网络地址转换功能实现源码&#xff0c;核心围绕 IP 协议栈钩子机制展开&#xff0c;用于解决嵌入式设备在连接公网时的地址转换与访问管控问题。压缩包共 45 个文件&#xff0c;以 C 语…

作者头像 李华