Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

常见问题与故障排查

本节汇总了使用 Bokeh 过程中最常遇到的问题及其解决方案,并提供了系统化的调试方法。


安装与环境问题

问题1:安装失败

症状pip install bokeh 报错或安装后无法导入

可能原因

  1. Python 版本不兼容
  2. 依赖库冲突
  3. 网络问题导致下载中断
  4. 虚拟环境配置问题

解决方案

# 检查 Python 版本(Bokeh 3.x 需要 Python 3.9+)
python --version

# 升级 pip 并安装
pip install --upgrade pip
pip install bokeh -i https://pypi.tuna.tsinghua.edu.cn/simple

# 如果有依赖冲突,尝试全新安装
pip install --force-reinstall bokeh

# 使用 conda 安装(推荐,自动处理依赖)
conda install bokeh -c conda-forge

# 验证安装
python -c "import bokeh; print(bokeh.__version__)"

问题2:JupyterLab 扩展问题

症状:JupyterLab 中图表不显示或显示空白

可能原因

  1. jupyter_bokeh 未安装或版本过旧
  2. JupyterLab 与 jupyter_bokeh 版本不兼容
  3. Notebook 环境未正确初始化
  4. 旧版 JupyterLab 扩展缓存异常

解决方案

# 安装或升级 jupyter_bokeh
pip install --upgrade jupyter_bokeh

# 如果你使用 JupyterLab 4.x,建议搭配 jupyter_bokeh 4.x 或更高版本
pip install "jupyter_bokeh>=4.0"

然后重启 JupyterLab,并在执行 show() 之前确认已经运行:

from bokeh.io import output_notebook
output_notebook()

说明:JupyterLab 3/4 通常不需要手动执行 jupyter lab build。只有在较旧的 JupyterLab 环境或扩展缓存异常时,才考虑执行 jupyter lab clean && jupyter lab build

验证扩展是否正常

from bokeh.io import output_notebook, show
from bokeh.plotting import figure

output_notebook()
p = figure()
p.circle([1, 2, 3], [4, 5, 6])
show(p)

问题3:Selenium / WebDriver 配置问题

症状export_png()export_svg() 报错

常见错误信息

RuntimeError: Neither firefox and geckodriver nor chrome and chromedriver are available

解决方案

# 推荐:使用 conda 一键安装
conda install selenium geckodriver firefox -c conda-forge

# 或使用 pip + 手动安装 WebDriver
pip install selenium
brew install chromedriver   # macOS ChromeDriver
brew install geckodriver    # macOS geckodriver(Firefox)

手动指定 WebDriver 路径

from bokeh.io import export_png
from bokeh.plotting import figure
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

options = Options()
options.headless = True
driver = webdriver.Chrome(options=options)

p = figure()
p.circle([1, 2, 3], [4, 5, 6])
export_png(p, filename="chart.png", webdriver=driver)
driver.quit()

问题4:虚拟环境问题

解决方案:使用 python -m venv bokeh_envconda create -n bokeh_env python=3.11 创建虚拟环境后 pip install bokeh。确保 which bokeh 指向虚拟环境中的 bokeh。


显示与渲染问题

问题5:图表不显示

症状:运行代码后没有显示图表

可能原因

  1. 忘记调用 show(p)output_notebook()
  2. Jupyter 扩展未安装
  3. 输出模式冲突

解决方案

from bokeh.io import output_notebook, output_file, show, save

# 在 Jupyter 中
output_notebook()
show(p)

# 在脚本中
output_file("chart.html")
show(p)

# 如果 show() 不工作,用 save() 替代
save(p)
print("图表已保存到 chart.html")

注意output_file()output_notebook() 不要同时使用。在 VS Code 中如果仍不显示,可使用 save(p) 后用浏览器打开。

问题6:图表显示空白

症状:Figure 画布出现了,但看不到数据

检查清单

# 1. 确认数据不为空
source = ColumnDataSource(data={'x': [1, 2, 3], 'y': [4, 5, 6]})
print(f"数据行数: {len(source)}")

# 2. 检查坐标轴范围
p = figure()
p.circle([1, 2, 3], [4, 5, 6])
print(f"X 范围: {p.x_range.start} ~ {p.x_range.end}")
# 如果范围不对,手动设置
p.x_range.start = 0
p.x_range.end = 4

# 3. 检查 NaN 值
import numpy as np
data = [1, 2, np.nan, 4]
print(f"NaN 数量: {sum(np.isnan(data))}")

# 4. 确认 glyph 参数正确
p.circle('x', 'y', source=source)  # 需要 x, y 和 source 三个参数

问题7:图表尺寸异常

症状:图表太大、太小或响应式布局不工作

sizing_mode 选项一览

模式说明
"fixed"固定尺寸(默认)
"stretch_width"宽度随容器拉伸
"stretch_height"高度随容器拉伸
"stretch_both"宽高都拉伸
"scale_width"等比缩放,宽度填满容器
"scale_height"等比缩放,高度填满容器
"scale_both"等比缩放,宽高都填满
# 固定尺寸
p = figure(width=800, height=400)

# 响式(推荐用于 Web 嵌入)
p = figure(sizing_mode="stretch_width", height=400)

# 布局也支持 sizing_mode
from bokeh.layouts import row
layout = row(p1, p2, sizing_mode="stretch_width")

问题8:工具栏不显示

# toolbar_location: "above", "below", "left", "right", None
p = figure(toolbar_location="above", tools="pan,wheel_zoom,box_zoom,reset,save")
p.toolbar.logo = None       # 隐藏 Bokeh logo
p.toolbar.autohide = True   # 鼠标不在图表上时自动隐藏

问题9:颜色显示错误

Bokeh 支持的颜色格式

格式示例
CSS 颜色名称"red"
十六进制"#FF5733"
RGB 元组(0~255)(255, 87, 51)
RGBA 元组(255, 87, 51, 0.5)

常见错误:Bokeh 不支持 matplotlib 的 (0.5, 0.3, 0.1) 格式(0~1 的 RGB),请使用 0~255 的 RGB 或十六进制。

from bokeh.transform import linear_cmap
from bokeh.palettes import Viridis256, Category20

# 确保 low 和 high 覆盖数据范围
color_mapper = linear_cmap('value', Viridis256,
    low=min(data['value']), high=max(data['value']))

# 超过 10 种颜色时用 Category20
colors = Category20[20][:15]

问题10:图例重叠

# 方式1:调整位置
p.legend.location = "top_left"

# 方式2:移到图表外部
p.add_layout(p.legend[0], 'right')

# 方式3:点击图例隐藏/显示数据
p.legend.click_policy = "hide"  # 或 "mute"(变淡)

# 附加:设置图例样式
p.legend.background_fill_alpha = 0.5
p.legend.border_line_color = None
p.legend.label_text_font_size = "10pt"

数据相关问题

问题11:中文显示问题

症状:图表中的中文显示为方框或乱码

解决方案

# 方法1:设置具体字体
p.title.text_font = "SimHei"
p.xaxis.axis_label_text_font = "SimHei"
p.yaxis.axis_label_text_font = "SimHei"

# 常用中文字体:
# Windows: "SimHei"(黑体), "Microsoft YaHei"(微软雅黑)
# macOS: "PingFang SC", "Heiti SC"
# Linux: "WenQuanYi Micro Hei"

# 方法2:使用主题全局设置
from bokeh.themes import Theme

theme = Theme(json={
    'attrs': {
        'Figure': {
            'title_text_font': 'PingFang SC'
        },
        'Axis': {
            'axis_label_text_font': 'PingFang SC',
            'major_label_text_font': 'PingFang SC'
        },
        'Legend': {
            'label_text_font': 'PingFang SC'
        }
    }
})

from bokeh.io import curdoc
curdoc().theme = theme

# 方法3:查看系统可用字体
import matplotlib.font_manager as fm
fonts = [f.name for f in fm.fontManager.ttflist]
chinese_fonts = [f for f in fonts if any(
    keyword in f for keyword in ['Hei', 'Song', 'Ming', 'Ya', 'Ping', 'Yuan', 'Fang']
)]
print("可用中文字体:", chinese_fonts)

问题12:ColumnDataSource 错误

症状ValueError: expected an element of either String or Dict(String, Either(String, Seq(String))) 等数据相关报错

常见错误与解决

from bokeh.models import ColumnDataSource
import pandas as pd

# 错误1:列长度不一致
source = ColumnDataSource(data={
    'x': [1, 2, 3],
    'y': [4, 5, 6],   # ✅ 所有列长度必须相同
})

# 错误2:引用不存在的列
print(source.column_names)  # 先确认列名
p.circle('x', 'y', source=source)

# 错误3:DataFrame 中有 NaN
df = pd.DataFrame({'x': [1, 2, None], 'y': [4, 5, 6]})
df = df.dropna()  # ✅ 填充或删除 NaN

# 错误4:更新数据方式不对
source.data['x'] = [10, 20]       # ❌ 直接修改不会触发更新
source.data = {'x': [10, 20]}     # ✅ 整体赋值触发更新
source.stream({'x': [4], 'y': [7]})  # ✅ 追加数据
source.patch({'x': [(0, 100)]})      # ✅ 局部更新(索引0改为100)

问题13:日期轴格式问题

症状:日期显示为时间戳数字,或格式不正确

解决方案

from bokeh.plotting import figure
from bokeh.models import DatetimeTickFormatter, HoverTool
import pandas as pd

# 方法1:使用 x_axis_type="datetime"
dates = pd.date_range('2024-01-01', periods=12, freq='M')
values = np.random.randn(12).cumsum()
p = figure(x_axis_type="datetime", width=800, height=400)
p.line(dates, values, line_width=2)

# 方法2:自定义日期格式
p.xaxis.formatter = DatetimeTickFormatter(
    days=["%Y-%m-%d"], months=["%Y-%m"],
    hours=["%H:%M"], minutes=["%H:%M"],
)

# 方法3:HoverTool 中的日期格式
hover = HoverTool(tooltips=[
    ("日期", "@date{%F}"),
    ("值", "@value{0.00}"),
], formatters={'@date': 'datetime'})

# 方法4:确保传入 datetime 类型
dates = pd.to_datetime(['2024-01-01', '2024-02-01', '2024-03-01'])  # ✅

问题14:分类轴问题

症状:类别顺序错误、标签重叠

解决方案:使用 x_range 参数控制类别顺序:p = figure(x_range=['C', 'A', 'B'])。标签重叠时设置 p.xaxis.major_label_orientation = "vertical"math.pi / 4。注意分类数据必须配合 x_range 使用,不能直接当数值轴用。

问题15:NaN / None 值处理

症状:图表部分数据缺失或渲染异常

解决方案:使用 df.dropna() 删除或 df.fillna(0) / df.interpolate() 填充。注意 Bokeh 折线遇到 NaN 会断开,这是预期行为;如需连续可先过滤:mask = ~np.isnan(y); p.line(x[mask], y[mask])


Bokeh Server 问题

问题16:Bokeh Server 无法启动

症状:运行 bokeh serve app.py 报错

可能原因

  1. 端口被占用
  2. 文件路径错误
  3. 代码语法错误

解决方案

# 检查并释放端口
lsof -i :5006                      # macOS/Linux
kill -9 $(lsof -t -i:5006)

# 先用最直接的方式验证应用能否启动
bokeh serve app.py --show

# 使用其他端口
bokeh serve app.py --port 5007 --show

# 查看详细错误信息
bokeh serve app.py --log-level debug

# 以目录方式运行(推荐)
mkdir myapp && mv app.py myapp/main.py
bokeh serve myapp --show

注意:如果你的示例里使用了 curdoc()on_change()add_periodic_callback() 等 Python 回调,必须通过 bokeh serve 启动;直接运行 python app.py 或在静态 show() / save() 模式下,这些回调都不会生效。

问题17:回调不触发

症状:滑块、按钮等控件的回调函数没有被执行

可能原因

  1. 回调函数签名错误
  2. on_change 监听的属性名不对
  3. 在静态 HTML 中使用了 Python 回调
  4. Bokeh Server 未正确启动

解决方案

from bokeh.io import curdoc
from bokeh.models import Slider, Button, CustomJS
from bokeh.plotting import figure

# 问题1:on_change 回调签名必须接受三个参数
# ❌ 错误
def update(new_value):
    pass

# ✅ 正确
def update(attr, old, new):
    print(f"属性 {attr} 从 {old} 变为 {new}")

slider = Slider(start=0, end=10, value=1)
slider.on_change('value', update)

# 问题2:on_click 回调签名
def on_button_click():
    print("clicked")

button = Button(label="Click")
button.on_click(on_button_click)

# 问题3:静态 HTML 不支持 Python 回调
# ❌ 在 show() / save() 中使用 on_change → 不会执行
# ✅ 静态 HTML 只能用 CustomJS
slider.js_on_change('value', CustomJS(code="""
    console.log('新值: ' + cb_obj.value);
"""))

# 问题4:确保 curdoc() 正确使用
# 下面这类代码需要在 `bokeh serve app.py --show` 启动的应用里运行
curdoc().add_root(layout)  # 添加到文档
# 不要再调用 show()

问题18:WebSocket 连接失败

症状:浏览器控制台报 WebSocket 错误,图表无法交互

常见错误信息

WebSocket connection to 'ws://localhost:5006/app/ws' failed

解决方案

# 原因1:WebSocket 源未允许
bokeh serve app.py --allow-websocket-origin=localhost:5006
bokeh serve app.py --allow-websocket-origin=example.com
# 允许所有源(仅开发环境)
bokeh serve app.py --allow-websocket-origin=*
# 原因2:Nginx 反向代理需要 WebSocket 配置
location / {
    proxy_pass http://127.0.0.1:5006;
    proxy_set_header Host $host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_http_version 1.1;
    proxy_read_timeout 86400;
}
# 原因3:SSL/HTTPS 混合内容 → Bokeh Server 也需要 HTTPS 或用反向代理
# 原因4:防火墙阻断 → sudo ufw allow 5006

问题19:多用户会话冲突

症状:多个用户同时访问时数据互相干扰

解决方案

# ❌ 错误:使用全局变量(所有会话共享)
global_source = ColumnDataSource(data={'x': [1, 2, 3]})

def update(attr, old, new):
    global_source.data = ...  # 会影响所有用户!

# ✅ 正确:在 make_document 函数中创建局部对象
def make_document(doc):
    source = ColumnDataSource(data={'x': [1, 2, 3]})

    p = figure()
    p.circle('x', 'y', source=source)

    def update(attr, old, new):
        source.data = ...  # 只影响当前会话

    slider = Slider(start=0, end=10, value=1)
    slider.on_change('value', update)
    doc.add_root(column(slider, p))

# 启动方式
from bokeh.server.server import Server
server = Server({'/': make_document})
server.start()

问题20:部署后样式丢失

症状:部署到生产环境后图表样式异常或 BokehJS 加载失败

解决方案:CDN 不可访问时,使用内联资源模式:output_file("chart.html", resources=INLINE)。同时确保静态文件目录权限正确(chmod -R 755),并在 Nginx 中配置 CORS:add_header Access-Control-Allow-Origin *;


导出与输出问题

问题21:大数据集性能问题

症状:图表渲染缓慢或浏览器崩溃

快速修复:启用 WebGL 渲染:p = figure(output_backend="webgl")

关于性能优化的完整讨论(WebGL、数据采样、降采样算法、Datashader 等),详见第九章

问题22:导出 PNG 失败

症状export_png() 报错或超时

解决方案

from bokeh.io import export_png
from bokeh.plotting import figure
import selenium

driver = selenium.webdriver.Chrome()
driver.set_page_load_timeout(30)  # 增加超时时间

p = figure()
p.circle([1, 2, 3], [4, 5, 6])

try:
    export_png(p, filename="chart.png", webdriver=driver, timeout=30)
except Exception as e:
    print(f"导出失败: {e}")
finally:
    driver.quit()

# 高分辨率导出:使用 2 倍尺寸
p = figure(width=1600, height=800)
export_png(p, filename="chart_hires.png")

问题23:SVG 导出限制

  • 需要先设置 p = figure(output_backend="svg"),然后调用 export_svg(p, filename="chart.svg")
  • 不支持 WebGL 渲染的内容
  • 部分交互工具(如 lasso_select)在 SVG 中不工作
  • 数据量大时 SVG 文件可能很大,建议使用高分辨率 PNG 替代

问题24:HTML 文件过大

症状:生成的 HTML 文件非常大(>10MB)

解决方案:使用 CDN 模式(推荐):output_file("chart.html", mode="cdn"),文件仅几 KB(查看时需要网络)。如需离线使用可改用 mode="inline"(2~5 MB)。对于大数据量场景,建议使用 Bokeh Server 或 AjaxDataSource 按需加载。


调试技巧

通用调试策略

1. 使用 --log-level debug 启动 Bokeh Server

bokeh serve app.py --log-level debug

2. 检查 Bokeh 对象

p = figure()
source = ColumnDataSource(data={'x': [1, 2, 3], 'y': [4, 5, 6]})
r = p.circle('x', 'y', source=source)

print(type(r))               # GlyphRenderer
print(source.column_names)    # ['x', 'y']
print(len(source))            # 3

3. 使用浏览器开发者工具(F12 → Console 标签查看 BokehJS 错误)

错误信息速查

错误信息原因解决方案
ValueError: expected an element of ...参数类型错误检查参数类型是否匹配 API 文档
RuntimeError: Neither firefox/geckodriver nor chrome/chromedriver available缺少 WebDriver安装 Selenium 和浏览器驱动
WebSocket connection to 'ws://...' failedWebSocket 连接被拒添加 --allow-websocket-origin
ValueError: ColumnDataSource's columns must be of the same length列长度不一致确保所有数据列长度相同
RuntimeError: Models must be owned by only a single document对象被多个文档引用每个文档创建独立的对象
ERROR:tornado.application:Uncaught exception GET /ws服务端代码异常检查回调函数中的错误

常见调试场景

回调函数中报错但不显示详细信息

import traceback

def update(attr, old, new):
    try:
        source.data = compute_data(new)
    except Exception as e:
        print(f"回调错误: {e}")
        traceback.print_exc()

图表显示但交互无反应:检查是否在 Bokeh Server 中运行(Python 回调需要)、回调是否正确绑定、浏览器控制台是否有 JS 错误。

布局不生效:给组件添加背景色辅助调试,确认 layout.children 数量正确。