Bokeh 与 Jupyter 深度集成指南:从 Notebook 内联输出到 JupyterHub 与 IPyWidgets
【免费下载链接】bokehInteractive Data Visualization in the browser, from Python项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh
本指南基于 Bokeh 官方用户手册 jupyter.rst 展开,系统讲解 Bokeh 与 Jupyter 生态(Classic Notebook、JupyterLab、JupyterHub、Reveal.js 幻灯片、IPyWidgets)的集成方案。读完本文,你将掌握output_notebook/show的内联绘图、push_notebook的原地更新、Bokeh Server 应用在 Notebook 中的嵌入与代理配置,以及把 IPyWidgets 直接搬进 Bokeh 应用的方法,并理解其底层实现原理。
概述:为什么把 Bokeh 放进 Notebook
Jupyter Notebook 是一类"可计算文档",常用于探索性分析、数据处理、教学与演示:Notebook 由一系列输入单元格(input cells)组成,每个单元格可独立执行并立即展示输出。除了经典的ClassicNotebook,还存在更新的JupyterLab项目。Bokeh 对两者都提供完整支持,既可以嵌入独立(standalone)内容(无需 Bokeh Server,纯前端交互),也可以嵌入Bokeh Server 应用(绘图事件、内置控件直接与 Python 回调代码相连)。
这两种内容形态对应 Bokeh 输出体系中的两类目标:独立内容由 bokeh.io 的output_notebook/show驱动;Server 应用则经由 src/bokeh/io/notebook.py 中的 notebook hook 机制嵌入。下文先讲最常用的独立内容内联输出。
在 Notebook 中内联输出独立绘图
Classic Notebook:一行函数切换输出目标
在经典 Jupyter Notebook 中内联显示 Bokeh 绘图,只需使用 bokeh.io 的output_notebook函数,替代(或叠加)output_file即可,无需任何其他修改:
from bokeh.io import output_notebook, show from bokeh.plotting import figure output_notebook() # 之后 show 的输出进入 Notebook 输出单元格 p = figure() p.scatter([1, 2, 3, 4, 5], [6, 7, 2, 4, 5]) show(p)调用show后,图形会立即显示在下一个 Notebook 输出单元格中。output_file与output_notebook可以同时生效——从 output.py 的 docstring 可知,文件输出与 Notebook 输出互不排斥,show会同时向两者写入。
底层实现上,output_notebook会调用run_notebook_hook(notebook_type, "load", ...)(见 output.py),通过load_notebook向单元格发布加载 BokehJS 的 HTML/JS 资源;show则通过 notebook hook 的show_doc动作(对应 notebook.py 的show_doc函数)把绘图序列化为 HTMLdiv与执行脚本,发布为text/html与application/javascript两种 MIME 类型。
在一个单元格中显示多个绘图:只需在同一个输入单元格里多次调用show,绘图会按调用顺序依次排列显示(源码中 show 与output_notebook的 docstring 均明确支持该用法)。
提示:
output_notebook还支持verbose(显示详细 BokehJS 加载横幅)、hide_banner(隐藏横幅)、load_timeout(加载超时毫秒数,默认 5000)等参数,详见 output.py。
JupyterLab:安装 jupyter_bokeh 扩展
要在 JupyterLab 中使用 Bokeh,至少需要 JupyterLab 3.0,并安装jupyter_bokeh扩展。安装方式二选一:
# 使用 conda(需先安装 Anaconda 或 Miniconda) conda install jupyter_bokeh # 或使用 pip pip install jupyter_bokeh安装完成后,JupyterLab 中的用法与 Classic Notebook 完全一致(仍走output_notebook+show)。若你使用 JupyterLab 3.0 之前的旧版本,安装jupyter_bokeh的详细说明请参见 jupyter_bokeh 官方仓库的 README。
Notebook 幻灯片(Reveal.js)中的注意事项
你可以用 Notebook 配合 Reveal.js 从单元格生成幻灯片,独立(非 Server)Bokeh 绘图可以包含在幻灯片中,但需注意一个关键陷阱:包含output_notebook调用的单元格不能被标记为"skip"(跳过)。因为该单元格渲染出的输出负责加载 BokehJS 库——如果这个单元格被跳过,BokehJS 不会加载,所有 Bokeh 绘图都将无法显示。若想隐藏该单元格,应将其幻灯片类型设为"notes"(备注),而不是 "skip"。
使用 notebook handle 原地更新绘图
基本原理:comms 通道与事件收集
"Notebook handle"(Notebook 句柄)允许你在不重新加载、不重新执行绘图所在单元格的情况下更新已显示的图形。其核心机制是 Jupyter 的comms通信通道,与 CommsHandle 的实现直接对应:
- 当你以
notebook_handle=True调用show时,show_doc会创建一个CommsHandle,其内部持有一份与curdoc()模型 ID 完全一致的"副本文档"(cell_doc),并让该文档处于持续hold()状态——原文档上的所有变更事件会被触发并收集起来,直到调用push_notebook时才被处理清空; push_notebook从 handle 中取出累积的DocumentPatchedEvent,打包为 PATCH-DOC 协议消息,通过handle.comms.send(...)发送给前端,实现原地更新(见 push_notebook)。
注意:Notebook handle 功能目前仅在 Classic Jupyter Notebook 中受支持,尚未在 JupyterLab 与 Zeppelin 中实现。
使用步骤
首先引入标准函数与push_notebook:
from bokeh.io import output_notebook, push_notebook, show from bokeh.plotting import figure output_notebook()创建绘图并以notebook_handle=True调用show,保存返回的 handle:
p = figure() p.scatter([1, 2, 3], [4, 6, 5]) handle = show(p, notebook_handle=True)此时输出单元格会显示该 handle 的表示形式(如<Bokeh Notebook handle for In[2]>,由CommsHandle._repr_html_生成,notebook.py)。
之后修改绘图属性或数据源,再调用push_notebook传入 handle,即可原地更新——原输出单元格会直接变化,而无需重新执行该单元格:
p.title.text = "New Title" # 修改标题 p.renderers[0].glyph.fill_color = "white" # 修改字形填充色 push_notebook(handle=handle) # 把累积的变更推送到已显示的图形从 push_notebook 的 docstring 可以看出,凡是自上次push_notebook(或原始show)以来的属性更新、数据源值变化等,都会一次性应用到先前渲染的输出单元格中。仓库提供了四个可直接运行的示例 Notebook:
- Basic Usage.ipynb —— 基础用法
- Continuous Updating.ipynb —— 持续更新
- Jupyter Interactors.ipynb —— 配合 Jupyter interactors
- Numba Image Example.ipynb —— Numba 图像示例
与 Jupyter interactors 结合
你可以用 Notebook 控件(即 ipywidgets 的interactors)来更新 Bokeh 绘图,关键仍是push_notebook:interactor 的更新回调函数根据控件值修改绘图属性后调用push_notebook完成推送。完整的交互式示例见上述 Jupyter Interactors.ipynb。
在 Notebook 中嵌入 Bokeh Server 应用
除了独立内容,你还可以把完整的 Bokeh Server 应用嵌入 Notebook:绘图事件与 Bokeh 内置控件直接连接 Python 回调代码。关于 Bokeh Server 应用的通用知识参见用户手册的 Server 章节;一个完整的嵌入示例 Notebook 位于 notebook_embed.ipynb。
信任 Notebook(Trust Notebook)
取决于 Notebook 版本,你可能需要在关闭并重新打开 Notebook 后,对 Notebook 执行**信任(trust)**操作,Bokeh 绘图才能重新渲染。Trust Notebook选项通常位于File菜单下。这是因为未信任的 Notebook 不会执行其中的 JavaScript/HTML 输出。
为什么 JupyterHub 下需要额外配置
当 Notebook 运行在你自己的 JupyterHub 实例上时,嵌入 Bokeh Server 应用需要额外步骤来打通"客户端浏览器 ↔ Bokeh Server"的网络连接。原因在于:浏览器需要直连 Bokeh Server 监听的端口,但 JupyterHub 在浏览器与 JupyterLab 容器之间充当反向代理,导致浏览器无法直接访问该端口。
Bokeh 的解决方案是提供notebook_url参数,它可以接收一个可调用对象(callable),根据整数端口计算最终 URL。更进一步,如果 JupyterHub 管理员设置了环境变量JUPYTER_BOKEH_EXTERNAL_URL,notebook_url的定义过程将完全自动化,无需再手动指定——这意味着同一个 Notebook 既能在 JupyterHub 上运行,也能在独立 JupyterLab 会话中原样运行,无需任何修改。
从源码可以印证这一机制:show_app 在启动内嵌 Server 前会调用_update_notebook_url_from_env(notebook.py):若环境变量JUPYTER_BOKEH_EXTERNAL_URL已设置,则忽略(并警告)用户传入的notebook_url,改用内置的_remote_jupyter_proxy_url(notebook.py)——该函数与文档中推荐的参考实现逻辑一致,用urllib.parse.urljoin拼接外部 Hub 地址、JUPYTERHUB_SERVICE_PREFIX与proxy/<port>路径,使浏览器能穿越 JupyterHub 代理回到当前 Notebook 会话。同时,Server 以allow_websocket_origin=[origin]启动,只允许来自该 origin 的 WebSocket 连接。
必需依赖:jupyter-server-proxy
先完成上文所有 JupyterLab(而非 JupyterHub)的安装步骤,然后继续安装jupyter-server-proxy包并启用其服务端扩展:
pip install jupyter-server-proxy && jupyter server extension enable --py jupyter-server-proxy如果使用 JupyterLab,还需安装对应的扩展,可通过 GUI 安装,或用命令:
jupyter labextension install @jupyterlab/server-proxyJupyterHub 管理员:环境变量一键配置
作为 JupyterHub 管理员,可以通过在 Notebook 环境中设置环境变量,让 Bokeh 自动工作、无需改动 Notebook:
export JUPYTER_BOKEH_EXTERNAL_URL="https//our-hub.science.edu"通常这在 JupyterHub Helm chart 配置 YAML 中完成:
hub: single_user: extraEnv: JUPYTER_BOKEH_EXTERNAL_URL="https://our-public-hub-name.edu"其净效果是:下一节介绍的技术会被 Bokeh 自动采用,无需任何额外操作。
JupyterHub 用户:自定义 notebook_url 回调
对于未设置JUPYTER_BOKEH_EXTERNAL_URL的 Hub,你需要定义一个辅助函数来生成浏览器连接 Bokeh Server 的 URL。以下是参考实现(需根据你的 JupyterHub 安装地址修改代码,或把 Hub 地址赋值给环境变量EXTERNAL_URL;未设置时 JupyterHub 默认使用JUPYTERHUB_SERVICE_PREFIX):
import os import urllib.parse def remote_jupyter_proxy_url(port): """ Callable to configure Bokeh's show method when a proxy must be configured. If port is None we're asking about the URL for the origin header. """ base_url = os.environ['EXTERNAL_URL'] host = urllib.parse.urlparse(base_url).netloc # If port is None we're asking for the URL origin # so return the public hostname. if port is None: return host service_url_path = os.environ['JUPYTERHUB_SERVICE_PREFIX'] proxy_url_path = 'proxy/%d' % port user_url = urllib.parse.urljoin(base_url, service_url_path) full_url = urllib.parse.urljoin(user_url, proxy_url_path) return full_url将上面定义的函数作为notebook_url关键字参数传给show。Bokeh 会在启动 Server 并创建加载图形的 URL 时调用该函数(回调约定:传入端口生成完整 Server URL,传入None生成 origin URL):
show(obj, notebook_url=remote_jupyter_proxy_url)此后可能需要重启你的 Server,然后 Bokeh 内容即可正常加载并执行你在 Jupyter 环境中定义的 Python 回调。
IPyWidgets 在 Notebook 之外的使用
掌握了 JupyterLab 与经典 Notebook 环境中的用法后,你还可以借助 Bokeh 的ipywidgets_bokeh扩展,在这些环境之外利用 Jupyter 生态中丰富的控件。安装方式二选一:
conda install -c bokeh ipywidgets_bokeh或
pip install ipywidgets_bokeh该扩展让你在 Bokeh 中使用 IPyWidgets:只需把一个控件包装进IPyWidget模型,再把包装器加入文档或包含进布局即可,无需安装或启用任何其他扩展。
实战示例:一个输出到控制台的滑块应用
按照以下步骤构建一个单滑块应用,滑块调整时把角度值打印到控制台:
1. 构造控件并配置观察器:
from ipywidgets import FloatSlider angle = FloatSlider(min=0, max=360, value=0, step=1, description="Angle") def on_change(change): print(f"angle={change['new']} deg") angle.observe(on_change, names="value")2. 用IPyWidget包装控件以集成到 Bokeh:
from ipywidgets_bokeh import IPyWidget ipywidget = IPyWidget(widget=angle)3. 把包装器加入 Bokeh 文档:
from bokeh.plotting import curdoc doc = curdoc() doc.add_root(ipywidget)以bokeh serve ipy_slider.py运行该应用(其中ipy_slider.py是应用文件名,Bokeh Server 的通用用法参见用户手册 Server 章节),应用地址为http://localhost:5006/ipy_slider。
在此基础上你可以构建更复杂的布局,并集成高级第三方控件,例如ipyleaflet与ipyvolume。仓库中的 examples/output/jupyter/ipywidgets 目录提供了更多示例,其 README 说明这些示例均为 Bokeh Server 应用,使用bokeh server app_name启动后访问http://localhost:5006/app_name:
- ipyleaflet_tiles.py —— IPyLeaflet 与 Bokeh 的 tile renderer 并排使用;
- ipyvolume_camera.py —— 集成第三方控件,并用 Bokeh 与 IPyWidgets 的控件共同操纵它。
更多示例 Notebook
可以在 bokeh-tutorial 仓库找到更多 Notebook 用法示例:
在本地克隆仓库:
git clone https://github.com/bokeh/tutorial.git在浏览器中启动这些 Jupyter Notebook。
Bokeh 主仓库也包含一些 Notebook comms 示例,即上文列出的 push_notebook 四个 Notebook(位于 examples/output/jupyter/push_notebook):
- Basic Usage.ipynb
- Continuous Updating.ipynb
- Jupyter Interactors.ipynb
- Numba Image Example.ipynb
小结
Bokeh 与 Jupyter 的集成覆盖了从"一行代码内联出图"到"生产级 Hub 代理穿透"的完整梯度:
| 场景 | 关键函数/参数 | 要点 |
|---|---|---|
| Classic Notebook 内联绘图 | output_notebook()+show() | 无需其他配置,多次show可顺序出图 |
| JupyterLab | conda install jupyter_bokeh/pip install jupyter_bokeh | 需 JupyterLab ≥ 3.0 |
| 原地更新绘图 | show(..., notebook_handle=True)+push_notebook(handle) | 仅 Classic Notebook 支持;基于 comms 通道与事件收集 |
| 幻灯片展示 | 单元格类型设为 "notes" 而非 "skip" | 保证output_notebook单元格执行以加载 BokehJS |
| JupyterHub 嵌入 Server 应用 | 环境变量JUPYTER_BOKEH_EXTERNAL_URL或自定义notebook_url回调 +jupyter-server-proxy | 解决反向代理下浏览器无法直连 Bokeh Server 端口的问题 |
| Notebook 之外使用 IPyWidgets | ipywidgets_bokeh.IPyWidget包装控件 | bokeh serve运行,无需其他扩展 |
理解这些机制背后的实现——notebook hook 体系(notebook.py)、comms 句柄的事件收集与 PATCH-DOC 推送、以及JUPYTER_BOKEH_EXTERNAL_URL对notebook_url的自动覆写——能帮助你在实际项目中更灵活地组合 Bokeh 与 Jupyter 生态,无论是探索性分析、教学演示还是部署到多用户 Hub 环境。
【免费下载链接】bokehInteractive Data Visualization in the browser, from Python项目地址: https://gitcode.com/GitHub_Trending/bo/bokeh
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考