第八章:输出选项
Bokeh提供了多种输出方式,让你的可视化作品能够在不同场景中展示和使用。本章将详细介绍各种输出选项的特点、配置方法和适用场景。
在进入具体 API 之前,先抓住一个总原则:
- standalone 文档:不需要 Bokeh Server,适合
output_file()、save()、file_html()、components()、Notebook 内联展示 - Bokeh Server 应用:需要
bokeh serve运行,适合server_document()、server_session()这类“嵌入一个活的 Python 应用”的场景 - 静态导出:PNG / SVG 适合报告、论文、幻灯片,但会失去 Python 端交互能力
如果你还不熟悉 Bokeh 的运行模式,建议先回看第一章 1.5和第七章。
8.1 输出格式概览
8.1.1 先按“运行模式”理解输出
| 运行模式 | 是否需要 Bokeh Server | 常见 API | 适用场景 |
|---|---|---|---|
| Standalone HTML | 否 | output_file()、save()、file_html() | 分享单图、保存本地 HTML、导出离线文档 |
| Notebook 内联 | 否 | output_notebook()、show() | Jupyter 教学、探索式分析 |
| Standalone 嵌入 | 否 | components()、json_item() | 嵌入 Flask / Django / 任意网页模板 |
| Server 嵌入 | 是 | server_document()、server_session() | 嵌入需要 Python 回调的交互应用 |
| 静态图片 | 否(但需要浏览器驱动) | export_png()、export_svg() | 论文、汇报、印刷、快照导出 |
8.1.2 格式对比
| 输出格式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| HTML | 独立分享、Web嵌入 | 交互完整、易于分享 | 需要浏览器、文件较大 |
| PNG | 报告、演示文稿 | 通用性强、易于查看 | 静态图片、失去交互性 |
| SVG | 出版印刷、矢量编辑 | 无损缩放、可编辑 | 部分交互丢失 |
| Notebook | 数据分析、教学 | 内联展示、便于记录 | 依赖Jupyter环境 |
| 组件 | Web应用集成 | 灵活嵌入、可定制 | 需要Web开发知识 |
| Server 应用嵌入 | Web 应用集成 | 保留 Python 回调、状态同步 | 需要 Bokeh Server 进程 |
8.1.3 选择决策指南
需要输出图表?
│
├── 只是想保存或分享一份交互式结果?
│ └── standalone HTML:output_file / save / file_html
│
├── 想在 Jupyter 中直接显示?
│ └── output_notebook + show
│
├── 想嵌入现有网页?
│ ├── 不需要 Python 回调 → components / json_item
│ └── 需要 Python 回调 → server_document / server_session
│
└── 需要静态图片?
├── 屏幕展示 / 报告 → PNG
└── 印刷 / 矢量编辑 → SVG
8.1.4 components()、file_html()、server_document() 怎么选
这是最容易混淆的三个接口,可以先这样记:
| 接口 | 是否需要 Bokeh Server | 返回内容 | 适合什么 |
|---|---|---|---|
file_html() | 否 | 一整份 HTML 字符串 | 想自己生成完整 HTML 页面 |
components() | 否 | <script> + <div> 片段 | 想把 standalone 图表嵌入模板 |
server_document() | 是 | 指向 Bokeh Server 应用的 <script> | 想把“活的” Bokeh Server 应用嵌入网页 |
你可以把它们理解成:
file_html():整页导出components():页面局部嵌入server_document():嵌入远端运行中的 Server 应用
后面在 8.6 节会分别展开。
8.2 HTML 输出
HTML 是最常用的输出格式,生成的文件包含完整的浏览器端交互功能。
运行方式说明:本节讨论的
output_file()、save()、file_html()都属于 standalone 输出。它们不需要bokeh serve,但也不能承载 Python 回调。若你需要 Python 回调,请跳到第七章和本章 8.6 节的server_document()。
基础用法
from bokeh.plotting import figure, output_file, save
# 创建图表
p = figure(title="销售趋势", width=800, height=400)
p.line([1, 2, 3, 4, 5], [100, 150, 120, 180, 200], line_width=2)
p.circle([1, 2, 3, 4, 5], [100, 150, 120, 180, 200], size=8)
# 输出为HTML文件
output_file("sales_chart.html")
save(p)
高级配置
from bokeh.plotting import figure, output_file, save
from bokeh.resources import CDN, INLINE
# 使用CDN资源(推荐,文件更小)
output_file("chart_cdn.html",
title="我的图表",
mode="cdn" # 使用CDN加载BokehJS
)
# 使用内联资源(自包含文件)
output_file("chart_inline.html",
title="我的图表",
mode="inline" # 将BokehJS内联到HTML中
)
# 自定义资源
from bokeh.resources import Resources
custom_resources = Resources(mode="server", root_url="http://myserver.com/static")
output_file("chart_custom.html", resources=custom_resources)
自包含HTML文件
当你需要生成一个完全自包含的HTML文件时(比如通过邮件发送),使用内联模式:
from bokeh.plotting import figure, output_file, save
from bokeh.resources import INLINE
# 创建图表
p = figure(title="自包含图表")
p.circle([1, 2, 3], [4, 5, 6])
# 生成自包含HTML(所有JavaScript都内联在文件中)
output_file("self_contained.html", mode="inline")
save(p)
# 文件可以离线查看,但文件较大
多图表输出
from bokeh.plotting import figure, output_file, save
from bokeh.layouts import column, row
# 创建多个图表
p1 = figure(title="图表1", width=400, height=300)
p1.circle([1, 2, 3], [4, 5, 6])
p2 = figure(title="图表2", width=400, height=300)
p2.line([1, 2, 3], [6, 5, 4])
p3 = figure(title="图表3", width=400, height=300)
p3.vbar(x=[1, 2, 3], top=[4, 5, 6], width=0.5)
# 布局并输出
layout = row(column(p1, p2), p3)
output_file("multiple_charts.html")
save(layout)
8.3 PNG 导出
PNG导出需要额外的依赖,适合生成静态报告图片。
依赖安装
方式1:使用 conda(推荐)
# Firefox 路线:安装 Selenium、Firefox 和 geckodriver
conda install selenium firefox geckodriver -c conda-forge
# Chrome 路线:安装 Selenium 和 ChromeDriver
# 注意:ChromeDriver 版本必须与本机 Chrome/Chromium 主版本匹配
conda install selenium python-chromedriver-binary -c conda-forge
方式2:使用pip
# 安装Selenium
pip install selenium
# 下载并配置WebDriver
# Firefox: 下载geckodriver并添加到PATH
# Chrome: 下载ChromeDriver并添加到PATH
基础导出
from bokeh.plotting import figure
from bokeh.io import export_png
# 创建图表
p = figure(title="导出示例", width=800, height=400)
p.line([1, 2, 3, 4, 5], [2, 5, 8, 2, 7], line_width=2)
# 导出为PNG
export_png(p, filename="chart.png")
# 默认分辨率:屏幕分辨率
# 可以通过webdriver参数指定使用Chrome或Firefox
from selenium import webdriver
driver = webdriver.Chrome() # 或 webdriver.Firefox()
export_png(p, filename="chart.png", webdriver=driver)
driver.quit()
高分辨率导出
from bokeh.plotting import figure
from bokeh.io import export_png
from bokeh.models import Plot
from bokeh.layouts import column
# 创建高分辨率图表
p = figure(title="高分辨率图表",
width=1600, # 2倍宽度
height=800, # 2倍高度
sizing_mode="scale_width"
)
# 调整字体大小以适应高分辨率
p.title.text_font_size = "24pt"
p.xaxis.major_label_text_font_size = "14pt"
p.yaxis.major_label_text_font_size = "14pt"
p.line([1, 2, 3, 4, 5], [2, 5, 8, 2, 7], line_width=4)
# 导出
export_png(p, filename="high_res_chart.png")
批量导出
from bokeh.plotting import figure
from bokeh.io import export_png
import os
# 确保输出目录存在
os.makedirs("charts", exist_ok=True)
# 创建多个图表并导出
datasets = [
("sales", [100, 150, 120, 180, 200]),
("profit", [20, 30, 25, 40, 35]),
("customers", [50, 60, 55, 70, 65])
]
for name, data in datasets:
p = figure(title=f"{name.title()} 数据", width=800, height=400)
p.line(range(len(data)), data, line_width=2)
p.circle(range(len(data)), data, size=8)
# 导出到charts目录
export_png(p, filename=f"charts/{name}_chart.png")
print(f"已导出: charts/{name}_chart.png")
print("所有图表导出完成!")
8.4 SVG 导出
SVG是矢量格式,适合出版印刷和进一步编辑。
基础导出
from bokeh.plotting import figure
from bokeh.io import export_svg
# 创建图表,指定SVG后端
p = figure(title="SVG导出", width=800, height=400,
output_backend="svg") # 重要:指定SVG渲染
p.circle([1, 2, 3, 4, 5], [2, 5, 8, 2, 7], size=10, color="navy")
p.line([1, 2, 3, 4, 5], [2, 5, 8, 2, 7], line_width=2)
# 导出SVG
export_svg(p, filename="chart.svg")
SVG vs Canvas 渲染
from bokeh.plotting import figure, show
# Canvas渲染(默认,适合大数据集)
p_canvas = figure(title="Canvas渲染", output_backend="canvas")
p_canvas.circle(range(10000), [i**0.5 for i in range(10000)], size=1)
# SVG渲染(适合出版,支持矢量编辑)
p_svg = figure(title="SVG渲染", output_backend="svg")
p_svg.circle([1, 2, 3], [4, 5, 6], size=10)
# 根据需求选择渲染后端
# 大数据集:使用canvas
# 需要导出SVG:使用svg
# 需要最佳性能:使用webgl
8.5 Jupyter Notebook 输出
Bokeh 与 Jupyter Notebook / JupyterLab 可以很好地集成,支持内联交互式图表。
交叉引用:如果你只想快速在 Notebook 里显示图表,可先看第一章 1.5;如果你需要排查 JupyterLab 显示问题,可结合常见问题与故障排查一起看。
经典 Jupyter Notebook
from bokeh.plotting import figure, show
from bokeh.io import output_notebook
# 初始化Notebook输出
output_notebook()
# 创建图表
p = figure(title="Notebook内联图表", width=600, height=400)
p.circle([1, 2, 3, 4, 5], [2, 5, 8, 2, 7], size=10)
# 显示图表(直接在Notebook中显示)
show(p)
JupyterLab 配置
安装扩展:
# 使用pip
pip install jupyter_bokeh
# 使用conda
conda install jupyter_bokeh -c conda-forge
使用方法:
from bokeh.plotting import figure, show
from bokeh.io import output_notebook
# JupyterLab中同样使用output_notebook
output_notebook()
# 创建图表
p = figure(title="JupyterLab图表", width=600, height=400)
p.line([1, 2, 3], [4, 5, 6])
show(p)
Notebook 高级技巧
from bokeh.plotting import figure, show
from bokeh.io import output_notebook
from bokeh.layouts import column, row
from bokeh.models import Slider, CustomJS
output_notebook()
# 在一个单元格中显示多个图表
p1 = figure(title="图表1", width=300, height=200)
p1.circle([1, 2, 3], [4, 5, 6])
p2 = figure(title="图表2", width=300, height=200)
p2.line([1, 2, 3], [6, 5, 4])
# 水平排列显示
show(row(p1, p2))
# 在Notebook中使用交互控件
slider = Slider(start=0, end=10, value=1, title="数值")
slider.js_on_change('value', CustomJS(code="""
console.log('滑块值变为: ' + this.value)
"""))
show(slider)
Notebook 常见问题
问题1:图表不显示
# 解决方案:确保调用output_notebook()
from bokeh.io import output_notebook
output_notebook() # 必须在show()之前调用
问题2:JupyterLab中无响应
# 解决方案:安装jupyter_bokeh扩展
pip install jupyter_bokeh
# 然后重启JupyterLab
8.6 嵌入 Web 框架
Bokeh 可以轻松嵌入到 Flask、Django 等 Web 框架中。但在写代码之前,必须先分清两类嵌入:
- 嵌入 standalone 文档
- 不需要 Bokeh Server
- 适合
components()、json_item()、file_html() - 只能使用浏览器端交互和
CustomJS
- 嵌入 Bokeh Server 应用
- 需要单独运行
bokeh serve - 适合
server_document()、server_session() - 支持 Python 回调、会话状态和实时更新
- 需要单独运行
如果你不确定该选哪种,优先问自己一句:这张图是否需要 Python 在后台持续参与交互?
- 如果答案是否定的,通常用
components()就够了 - 如果答案是肯定的,就应该考虑
server_document()+ Bokeh Server
8.6.1 使用 components() 生成 standalone 组件
from bokeh.plotting import figure
from bokeh.embed import components
# 创建图表
p = figure(title="嵌入示例", width=600, height=400)
p.circle([1, 2, 3, 4, 5], [2, 5, 8, 2, 7])
# 生成脚本和div标签
script, div = components(p)
print("JavaScript脚本:")
print(script[:200] + "...") # 脚本很长,只显示开头
print("\nHTML div:")
print(div)
# 返回的是元组 (script, div)
# script: 包含所有必要的JavaScript代码
# div: 包含图表的HTML容器
适用判断:
- 你已经有一个 Flask / Django / FastAPI 模板页面
- 你只想把一张或几张 Bokeh 图表嵌进去
- 这些图表不依赖 Python 回调持续运行
如果你需要嵌入的是 Bokeh Server 应用,请直接看后面的 8.6.4 节。
8.6.2 嵌入 Flask 应用
app.py:
from flask import Flask, render_template
from bokeh.plotting import figure
from bokeh.embed import components
app = Flask(__name__)
@app.route('/')
def index():
# 创建图表
p = figure(title="Flask中的Bokeh图表", width=800, height=400)
p.line([1, 2, 3, 4, 5], [2, 5, 8, 2, 7], line_width=2)
# 生成组件
script, div = components(p)
return render_template('index.html', script=script, div=div)
if __name__ == '__main__':
app.run(debug=True)
templates/index.html:
<!DOCTYPE html>
<html>
<head>
<title>Bokeh Flask示例</title>
<link rel="stylesheet" href="https://cdn.bokeh.org/bokeh/release/bokeh-3.x.x.min.css">
</head>
<body>
<h1>销售数据图表</h1>
<!-- 图表容器 -->
{{ div | safe }}
<!-- Bokeh脚本 -->
{{ script | safe }}
</body>
</html>
8.6.3 嵌入 Django 应用
views.py:
from django.shortcuts import render
from bokeh.plotting import figure
from bokeh.embed import components
def chart_view(request):
# 创建图表
p = figure(title="Django中的Bokeh图表", width=800, height=400)
p.circle([1, 2, 3, 4, 5], [2, 5, 8, 2, 7], size=10)
# 生成组件
script, div = components(p)
context = {
'script': script,
'div': div
}
return render(request, 'chart.html', context)
templates/chart.html:
{% load static %}
<!DOCTYPE html>
<html>
<head>
<title>Bokeh Django示例</title>
<link rel="stylesheet" href="https://cdn.bokeh.org/bokeh/release/bokeh-3.x.x.min.css">
</head>
<body>
{{ div|safe }}
{{ script|safe }}
</body>
</html>
8.6.4 嵌入 Bokeh Server 应用
当你需要把 支持 Python 回调 的 Bokeh 应用嵌入现有网站时,就不能再用 components()。这时应该先运行 Bokeh Server,再通过 server_document() 获取嵌入脚本。
from flask import Flask, render_template
from bokeh.embed import server_document
app = Flask(__name__)
@app.route('/')
def index():
# 获取 Bokeh Server 应用的嵌入脚本
# 假设 Bokeh Server 运行在 http://localhost:5006/myapp
script = server_document('http://localhost:5006/myapp')
return render_template('index.html', script=script)
这里要注意两点:
server_document()嵌入的是一个 正在运行的 Bokeh Server 应用- 页面里的交互会通过 WebSocket 与服务器上的 Python 代码通信
因此,components() 与 server_document() 的根本区别不是“返回值长什么样”,而是:
components()嵌入的是 静态生成的 standalone 文档server_document()嵌入的是 动态运行的 Server 应用
如果你对 Bokeh Server 还不熟,建议先回看第七章。
8.7 批量导出与自动化
自动化导出脚本
"""
批量导出图表脚本
用于生成报告所需的图表图片
"""
import os
from datetime import datetime
from bokeh.plotting import figure
from bokeh.io import export_png, export_svg
from bokeh.layouts import column, row
def create_sales_chart(data):
"""创建销售图表"""
p = figure(title="销售趋势", width=800, height=400)
p.line(range(len(data)), data, line_width=2, color="navy")
p.circle(range(len(data)), data, size=8, color="navy")
p.xaxis.axis_label = "月份"
p.yaxis.axis_label = "销售额"
return p
def create_comparison_chart(data1, data2):
"""创建对比图表"""
p = figure(title="销售对比", width=800, height=400)
p.line(range(len(data1)), data1, line_width=2, color="blue", legend_label="产品A")
p.line(range(len(data2)), data2, line_width=2, color="red", legend_label="产品B")
p.legend.location = "top_left"
return p
def export_charts(output_dir="exported_charts", formats=["png", "svg"]):
"""导出所有图表"""
# 创建输出目录
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
output_path = os.path.join(output_dir, timestamp)
os.makedirs(output_path, exist_ok=True)
# 示例数据
sales_data = [100, 150, 120, 180, 200, 175, 220]
product_a = [100, 120, 140, 160, 180]
product_b = [80, 100, 130, 150, 170]
# 创建图表
charts = {
"sales_trend": create_sales_chart(sales_data),
"comparison": create_comparison_chart(product_a, product_b)
}
# 导出图表
for name, chart in charts.items():
if "png" in formats:
png_path = os.path.join(output_path, f"{name}.png")
export_png(chart, filename=png_path)
print(f"已导出PNG: {png_path}")
if "svg" in formats:
chart.output_backend = "svg" # 设置SVG渲染
svg_path = os.path.join(output_path, f"{name}.svg")
export_svg(chart, filename=svg_path)
print(f"已导出SVG: {svg_path}")
print(f"\n所有图表已导出到: {output_path}")
return output_path
if __name__ == "__main__":
export_charts()
定时导出任务
"""
定时导出图表任务
可以配合cron或Windows任务计划使用
"""
import schedule
import time
from datetime import datetime
from export_script import export_charts
def scheduled_export():
"""定时导出任务"""
print(f"开始定时导出任务: {datetime.now()}")
try:
output_path = export_charts()
print(f"导出成功: {output_path}")
except Exception as e:
print(f"导出失败: {e}")
# 每天早上8点执行
schedule.every().day.at("08:00").do(scheduled_export)
# 或者每小时执行
schedule.every().hour.do(scheduled_export)
print("定时任务已启动...")
while True:
schedule.run_pending()
time.sleep(60)
8.8 版本兼容性说明
Bokeh 版本兼容性
| Bokeh版本 | HTML | PNG | SVG | JupyterLab | 备注 |
|---|---|---|---|---|---|
| 3.x | ✅ | ✅ | ✅ | ✅ | 当前稳定版本 |
| 2.x | ✅ | ✅ | ✅ | ⚠️ | 需要旧版扩展 |
| 1.x | ✅ | ✅ | ⚠️ | ❌ | 部分功能有限 |
依赖库版本要求
# requirements.txt 示例
bokeh>=3.0.0
selenium>=4.0.0
jupyter_bokeh>=3.0.0 # JupyterLab需要
# WebDriver版本需要与浏览器匹配
# Chrome: 查看 https://chromedriver.chromium.org/
# Firefox: 查看 https://github.com/mozilla/geckodriver/releases
常见兼容性问题
问题1:导出 PNG 时 WebDriver 不可用或版本不匹配
常见错误信息:
RuntimeError: Neither firefox and geckodriver nor chrome and chromedriver are available
WebDriverException: Message: 'chromedriver' executable needs to be in PATH
推荐优先使用 conda-forge 安装一组匹配的浏览器与驱动:
# Firefox 路线
conda install selenium firefox geckodriver -c conda-forge
# Chrome 路线
conda install selenium python-chromedriver-binary -c conda-forge
如果你手动安装 ChromeDriver,需要确保 ChromeDriver 与 Chrome/Chromium 的主版本匹配,并把驱动放到 PATH 中。使用 Selenium 4 手动指定驱动路径时,应使用 Service:
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
driver = webdriver.Chrome(service=Service("/path/to/chromedriver"))
Bokeh 的
export_png()/export_svg()通常不需要你自己创建webdriver对象;上面的 Selenium 代码只用于解释现代 Selenium 4 的驱动路径写法。
问题2:JupyterLab 中图表不显示
# 安装或升级 jupyter_bokeh
pip install --upgrade jupyter_bokeh
# JupyterLab 4.x 建议使用 jupyter_bokeh 4.x 或更新版本
pip install --upgrade "jupyter_bokeh>=4.0"
安装或升级后,重启 JupyterLab 再运行:
from bokeh.io import output_notebook, show
from bokeh.plotting import figure
output_notebook()
p = figure()
p.scatter([1, 2, 3], [4, 5, 6])
show(p)
JupyterLab 3/4 通常不需要手动执行
jupyter lab build。只有在旧版 JupyterLab 或本地扩展构建失败时,才考虑清理并重建。
8.9 本章小结
本章你应该掌握四件事:
- 先分运行模式,再选输出接口
- standalone:
output_file()、save()、file_html()、components() - Notebook:
output_notebook()+show() - Server:
server_document()/server_session()+bokeh serve
- standalone:
components()、file_html()、server_document()并不是同一类东西file_html()生成完整页面components()返回可嵌入片段server_document()嵌入的是活的 Server 应用
- PNG / SVG 导出是额外能力,不是默认内置能力
- 需要 Selenium
- 需要浏览器和对应驱动
- 是否需要 Python 回调,是决定输出方案的关键分界线
8.10 常见坑
- 把 standalone 文档和 Server 应用混为一谈:
save()/show()生成的 HTML 不能执行 Python 回调。 - 误把
components()当成 Server 嵌入方案:它只能嵌入 standalone 文档。 - 导出 PNG/SVG 时忽略浏览器驱动依赖:先确认 Selenium、浏览器、驱动三者都可用。
- 在 Jupyter 中只执行
show(),没先output_notebook():这会导致图表不显示或行为异常。 - 一上来就追求“所有格式都支持”:先明确你的交付目标,是分享交互图、嵌入网页,还是导出静态图片。
下一步:了解了输出选项后,下一章我们将学习性能优化,重点是如何判断瓶颈、选择降采样/聚合/WebGL 等策略,而不是把所有性能问题都归结为渲染后端。
延伸阅读: