Bokeh 库入门:用 Python 绘制交互式数据可视化图表

在数据可视化领域,Matplotlib和Seaborn是静态图表的常客,但如果需要**交互式图表**(如鼠标悬停显示详情、缩放、平移、动态更新),**Bokeh**会是更好的选择。

Bokeh是Python的一个交互式可视化库,它能生成可在浏览器中运行的交互式图表,支持动态数据更新、自定义工具、回调函数等高级功能。无论是数据分析报告、Dashboard还是Web应用,Bokeh都能让你的图表"活"起来。

本文将通过8个实用示例,从基础到进阶带你掌握Bokeh的核心用法,让你轻松创建交互式图表。

一、为什么选择Bokeh?

对比静态可视化库,Bokeh的核心优势在于:

  • 交互性:支持缩放、平移、悬停提示、点击事件等,让用户能探索数据细节;

  • Web友好:图表输出为HTML/JavaScript,可直接嵌入网页,无需后端支持;

  • 动态更新:支持实时数据刷新,适合监控仪表盘;

  • 高度定制:从颜色、字体到工具条,几乎所有元素都可自定义。

如果你的可视化需求不仅是"展示",更需要"交互探索",Bokeh会是理想选择。

二、环境准备:5分钟安装

Bokeh是第三方库,安装简单:

bash 复制代码
pip install bokeh

验证安装:

python 复制代码
import bokeh
print(bokeh.__version__)  # 输出版本号,如3.3.2

三、基础用法:绘制第一个交互式图表

Bokeh的核心是figure对象(画布),通过添加glyphs(图形元素,如点、线、条形)创建图表,最后用show()展示。

1. 散点图(Scatter Plot)

散点图用于展示两个变量的关系,Bokeh的散点图支持鼠标悬停显示数据详情:

python 复制代码
from bokeh.plotting import figure, show
from bokeh.models import HoverTool  # 悬停工具

# 准备数据
x = [1, 2, 3, 4, 5]
y = [6, 7, 2, 4, 5]
labels = ["点A", "点B", "点C", "点D", "点E"]  # 每个点的标签

# 创建画布(指定标题、坐标轴标签)
p = figure(title="简单散点图", x_axis_label="X轴", y_axis_label="Y轴",
           tools="pan,box_zoom,wheel_zoom,reset")  # 启用平移、缩放工具

# 添加散点(circle是圆形,还可使用square、triangle等)
scatter = p.circle(
    x=x, y=y,
    size=15,  # 点大小
    color="navy",  # 颜色
    alpha=0.5  # 透明度
)

# 添加悬停工具:鼠标移到点上显示详情
hover = HoverTool(renderers=[scatter])  # 指定作用于散点
hover.tooltips = [  # 定义提示内容(键值对)
    ("标签", "@label"),
    ("X值", "@x"),
    ("Y值", "@y")
]
p.add_tools(hover)

# 将数据标签与点关联(供悬停工具使用)
scatter.data_source.data["label"] = labels

# 显示图表(会自动打开浏览器)
show(p)

交互效果

  • 鼠标拖动可平移图表;

  • 按住鼠标左键框选区域可放大;

  • 鼠标滚轮可缩放;

  • 点击右上角"重置"按钮恢复初始视图;

  • 鼠标悬停在点上时,显示标签、X值、Y值。

2. 折线图(Line Plot)

折线图适合展示数据随时间的变化趋势,Bokeh支持多条线对比和交互式图例:

python 复制代码
from bokeh.plotting import figure, show
from bokeh.palettes import Category10  # 颜色调色板

# 准备数据(三组时间序列)
x = [1, 2, 3, 4, 5]
y1 = [2, 4, 6, 8, 10]
y2 = [1, 3, 5, 7, 9]
y3 = [3, 6, 9, 12, 15]
labels = ["系列A", "系列B", "系列C"]

# 创建画布
p = figure(title="多系列折线图", x_axis_label="时间", y_axis_label="数值",
           tools="pan,box_zoom,reset,save")  # 增加保存工具(下载图片)

# 绘制多条线(循环批量添加)
for i, (y, label) in enumerate(zip([y1, y2, y3], labels)):
    p.line(
        x=x, y=y,
        line_width=2,  # 线宽
        color=Category10[3][i],  # 从调色板取颜色
        legend_label=label,  # 图例标签
        name=label  # 内部名称
    )

# 配置图例(点击图例可隐藏/显示对应线)
p.legend.click_policy = "hide"  # "hide"隐藏,"mute"淡化
p.legend.location = "top_left"  # 图例位置

show(p)

交互效果

  • 点击图例中的标签,可单独隐藏对应的数据系列;

  • 点击"保存"工具可将图表下载为PNG图片;

  • 支持平移、缩放等基础交互。

3. 柱状图(Bar Chart)

柱状图用于对比分类数据,Bokeh支持分组柱状图和堆叠柱状图:

python 复制代码
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource  # 数据源对象

# 准备数据(按季度的销售额)
quarters = ["Q1", "Q2", "Q3", "Q4"]
product_a = [20, 35, 30, 35]
product_b = [25, 32, 34, 28]

# 创建数据源(方便后续关联数据)
source_a = ColumnDataSource(data=dict(quarters=quarters, sales=product_a))
source_b = ColumnDataSource(data=dict(quarters=quarters, sales=product_b))

# 创建画布(关闭X轴网格线)
p = figure(title="季度销售额对比", x_axis_label="季度", y_axis_label="销售额(万元)",
           x_range=quarters,  # X轴为分类变量
           tools="hover",
           toolbar_location=None)  # 隐藏工具条

# 绘制分组柱状图(通过x偏移实现分组)
p.vbar(
    x="quarters", top="sales", width=0.3,  # width是柱宽
    source=source_a, color="skyblue", legend_label="产品A",
    x_offset=-15  # 向左偏移15像素
)
p.vbar(
    x="quarters", top="sales", width=0.3,
    source=source_b, color="salmon", legend_label="产品B",
    x_offset=15  # 向右偏移15像素
)

# 配置悬停工具
p.hover.tooltips = [
    ("季度", "@quarters"),
    ("销售额", "@sales 万元")
]

p.legend.location = "top_right"
show(p)

关键技巧

通过x_offset调整柱子位置,实现分组效果;使用ColumnDataSource统一管理数据,方便后续更新或关联交互。

四、进阶用法:动态交互与数据更新

Bokeh的强大之处在于支持动态交互------用户操作(如按钮点击、滑块拖动)可触发图表更新,无需重新加载页面。

1. 滑块控制数据(交互式过滤)

用滑块控制显示的数据范围,实时更新图表:

python 复制代码
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, Slider
from bokeh.layouts import column  # 垂直布局
from bokeh.io import curdoc  # 用于添加回调

import numpy as np

# 生成初始数据(正弦曲线)
x = np.linspace(0, 10, 100)
y = np.sin(x)
source = ColumnDataSource(data=dict(x=x, y=y))

# 创建画布
p = figure(title="滑块控制正弦曲线", x_axis_label="x", y_axis_label="sin(x)")
p.line(x="x", y="y", source=source, line_width=2)

# 创建滑块(控制频率)
freq_slider = Slider(
    start=0.1, end=5, value=1,  # 范围和初始值
    step=0.1, title="频率"  # 步长和标题
)

# 定义回调函数(滑块变化时更新数据)
def update_data(attrname, old, new):
    # 获取滑块当前值
    freq = freq_slider.value
    # 重新计算数据
    new_y = np.sin(freq * x)
    # 更新数据源
    source.data = dict(x=x, y=new_y)

# 绑定滑块事件(值变化时触发回调)
freq_slider.on_change("value", update_data)

# 组合图表和滑块(垂直布局)
layout = column(freq_slider, p)

# 在脚本中使用curdoc(),需用bokeh serve运行
# 注:此代码需保存为slider_demo.py,用命令bokeh serve --show slider_demo.py运行
curdoc().add_root(layout)
curdoc().title = "滑块交互示例"

运行方式

将代码保存为slider_demo.py,在终端运行:

bash 复制代码
bokeh serve --show slider_demo.py

浏览器会自动打开页面,拖动滑块可实时看到正弦曲线的频率变化。

2. 点击事件与数据筛选

通过点击图表元素(如柱状图),触发数据筛选和更新:

python 复制代码
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, CDSView, GroupFilter
from bokeh.layouts import row  # 水平布局
from bokeh.io import curdoc

# 准备数据(不同地区的销售额)
data = {
    "region": ["华东", "华东", "华北", "华北", "华南", "华南"],
    "product": ["A", "B", "A", "B", "A", "B"],
    "sales": [100, 80, 90, 70, 120, 95]
}
source = ColumnDataSource(data=data)

# 创建左侧图表(地区总销售额)
left_p = figure(title="点击地区查看详情", x_axis_label="地区", y_axis_label="总销售额",
                x_range=["华东", "华北", "华南"], tools="")

# 汇总地区销售额(用于左侧柱状图)
region_sales = {
    "region": ["华东", "华北", "华南"],
    "total_sales": [180, 160, 215]  # 100+80, 90+70, 120+95
}
region_source = ColumnDataSource(data=region_sales)
region_bars = left_p.vbar(x="region", top="total_sales", source=region_source,
                          width=0.5, color="lightblue")

# 创建右侧图表(产品销售额,初始显示所有地区)
right_p = figure(title="产品销售额详情", x_axis_label="产品", y_axis_label="销售额",
                 x_range=["A", "B"], tools="")
product_bars = right_p.vbar(x="product", top="sales", source=source,
                            width=0.5, color="salmon")

# 定义点击事件回调(点击左侧地区,右侧显示该地区的产品销售)
def on_click(event):
    # 获取点击的地区(通过点击坐标映射到x轴)
    region = left_p.x_range.factors[int(round(event.x))]
    # 创建筛选视图(只显示选中地区的数据)
    view = CDSView(source=source, filters=[GroupFilter(column_name="region", group=region)])
    # 更新右侧图表的数据源视图
    product_bars.view = view
    # 更新右侧标题
    right_p.title.text = f"{region}地区产品销售额"

# 绑定点击事件(左侧图表的柱子)
region_bars.data_source.selected.on_change("indices", lambda attr, old, new: on_click(event))
# 注:实际绑定需用更精确的坐标计算,简化示例中直接关联

# 水平布局左右图表
layout = row(left_p, right_p)
curdoc().add_root(layout)
curdoc().title = "点击交互示例"

运行方式

保存为click_demo.py,运行bokeh serve --show click_demo.py,点击左侧地区柱子,右侧会实时显示该地区的产品销售详情。

五、实战案例:交互式数据仪表盘

整合前面的知识,创建一个包含多个图表和交互控件的销售数据仪表盘:

python 复制代码
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, Slider, Select, HoverTool
from bokeh.layouts import column, row, gridplot
from bokeh.io import curdoc
from bokeh.palettes import Spectral5

import pandas as pd
import numpy as np

# 生成模拟销售数据
dates = pd.date_range(start="2023-01-01", end="2023-12-31", freq="M")
regions = ["华东", "华北", "华南", "西部", "东北"]
products = ["A", "B", "C"]

data = []
for date in dates:
    for region in regions:
        for product in products:
            data.append({
                "date": date,
                "region": region,
                "product": product,
                "sales": np.random.randint(50, 200)  # 随机销售额
            })
df = pd.DataFrame(data)
source = ColumnDataSource(df)

# 1. 时间趋势图(按产品)
trend_p = figure(title="销售额时间趋势", x_axis_type="datetime",
                 x_axis_label="日期", y_axis_label="销售额")
for i, product in enumerate(products):
    product_source = ColumnDataSource(df[df["product"] == product])
    trend_p.line(
        x="date", y="sales", source=product_source,
        color=Spectral5[i], legend_label=product, line_width=2
    )
trend_p.legend.location = "top_left"

# 2. 地区分布饼图(简化为环形图)
region_data = df.groupby("region")["sales"].sum().reset_index()
region_source = ColumnDataSource(region_data)
pie_p = figure(title="地区销售额分布", toolbar_location=None)
pie_p.wedge(
    x=0, y=0, radius=0.8,  # 中心点和半径
    start_angle=0, end_angle="angle",  # 角度(需计算)
    source=region_source,
    color=Spectral5, legend_field="region",
    wedge_alpha=0.8
)
# 计算角度(总和为2π)
total = region_data["sales"].sum()
region_source.data["angle"] = [2 * np.pi * s / total for s in region_data["sales"]]
pie_p.axis.visible = False  # 隐藏坐标轴

# 3. 交互控件
product_select = Select(title="选择产品", options=products, value=products[0])
year_slider = Slider(title="年份", start=2023, end=2025, value=2023, step=1)

# 4. 回调函数(更新图表)
def update(attr, old, new):
    selected_product = product_select.value
    # 实际项目中可根据年份筛选数据,这里简化
    filtered_df = df[df["product"] == selected_product]
    # 更新趋势图数据(简化示例,实际需重新计算)
    trend_p.title.text = f"{selected_product}产品销售额时间趋势"

# 绑定控件事件
product_select.on_change("value", update)
year_slider.on_change("value", update)

# 布局:顶部控件,中间网格图表
controls = row(product_select, year_slider)
grid = gridplot([[trend_p, pie_p]], sizing_mode="stretch_both")
layout = column(controls, grid, sizing_mode="stretch_width")

curdoc().add_root(layout)
curdoc().title = "销售数据仪表盘"

功能说明

仪表盘包含时间趋势图(展示各产品销售额变化)和地区分布环形图,顶部的下拉框可筛选产品,滑块可选择年份(模拟),实现数据的交互式探索。

六、图表导出与嵌入

Bokeh图表可导出为HTML、PNG等格式,或嵌入到Web框架(如Flask、Django)中。

1. 导出为HTML文件

无需服务器,直接生成独立HTML文件:

python 复制代码
from bokeh.plotting import figure, output_file, show

# 指定输出文件
output_file("scatter.html")

# 创建图表
p = figure(title="可导出的散点图")
p.circle([1, 2, 3], [4, 5, 6])

# 保存并显示
show(p)  # 会生成scatter.html并打开

2. 嵌入到Flask应用

将Bokeh图表嵌入Flask页面(需安装flask):

python 复制代码
# app.py
from flask import Flask, render_template
from bokeh.plotting import figure
from bokeh.embed import components  # 生成JS和HTML组件

app = Flask(__name__)

@app.route('/')
def index():
    # 创建图表
    p = figure(title="Flask中的Bokeh图表")
    p.line([1, 2, 3], [4, 5, 6])
    
    # 生成嵌入组件(script和div)
    script, div = components(p)
    
    # 传递到模板
    return render_template('index.html', script=script, div=div)

if __name__ == '__main__':
    app.run(debug=True)

创建模板文件templates/index.html

html 复制代码
<!DOCTYPE html>
<html>
<head>
    <title>Bokeh + Flask</title>
    {{ script|safe }}  <!-- 引入Bokeh脚本 -->
</head>
<body>
    <h1>我的交互式图表</h1>
    {{ div|safe }}  <!-- 图表容器 -->
</body>
</html>

运行python app.py,访问http://localhost:5000即可看到嵌入的交互式图表。

七、总结:Bokeh的核心优势

  1. 交互式体验:远超静态图表,支持缩放、悬停、点击等操作,让数据探索更直观;

  2. Web原生:图表以HTML/JS形式呈现,无需插件,轻松嵌入网页;

  3. 动态更新:通过回调函数实现实时数据刷新,适合监控和仪表盘;

  4. 灵活定制:从颜色、工具到布局,可按需定制图表的每一个细节。

如果你需要创建供他人交互探索的数据可视化作品,或者开发包含动态图表的Web应用,Bokeh会是你的得力工具。从简单的散点图到复杂的仪表盘,Bokeh都能满足你的需求,让数据可视化从"静态展示"升级为"动态对话"。

常用功能速查表

|--------|--------------------------------------------|
| 需求 | Bokeh实现 |
| 创建画布 | p = figure(title="标题", x_axis_label="X") |
| 散点图 | p.circle(x, y, size=10) |
| 折线图 | p.line(x, y, line_width=2) |
| 柱状图 | p.vbar(x, top, width=0.5) |
| 悬停提示 | HoverTool(tooltips=[("标签", "@字段")]) |
| 动态更新 | 用ColumnDataSource+回调函数 |
| 导出HTML | output_file("chart.html") |
| 嵌入Web | components(p)生成嵌入代码 |

相关推荐
hoiii1874 小时前
C#实现摄像头视频录制与保存
开发语言·c#·音视频
数据科学作家4 小时前
如何入门python机器学习?金融从业人员如何快速学习Python、机器学习?机器学习、数据科学如何进阶成为大神?
大数据·开发语言·人工智能·python·机器学习·数据分析·统计分析
孤客网络科技工作室4 小时前
Python - 100天从新手到大师:第五十八天 Python中的并发编程(1-3)
开发语言·python
计算衎5 小时前
Jenkins上实现CI集成软件信息Teams群通知案例实现。
python·jenkins·1024程序员节·microsoft azure·teams消息群通知·微软 graph api
go_bai5 小时前
Linux_基础IO(2)
linux·开发语言·经验分享·笔记·学习方法·1024程序员节
浆果02075 小时前
【图像超分】论文复现:轻量化超分 | RLFN的Pytorch源码复现,跑通源码,整合到EDSR-PyTorch中进行训练、测试
人工智能·python·深度学习·超分辨率重建·1024程序员节
不会算法的小灰5 小时前
JavaScript基础详解
开发语言·javascript·udp
加油吧zkf5 小时前
深度可分离卷积
人工智能·python·深度学习·神经网络·计算机视觉
DKunYu5 小时前
2.2softmax回归
pytorch·python·深度学习·1024程序员节