在数据可视化这件事上,我用Python写过不少东西,从最开始老老实实画静态图的matplotlib,到后来追求快速出图的seaborn和plotly,工具换了好几轮。但如果你问我,现在要在Python生态里挑一个既能应付探索性分析、又能体面地交付给非技术同事做演示的交互式绘图库,我会首推Bokeh。
Pyecharts够炫,但遇到复杂联动逻辑时容易束手束脚;Plotly交互开箱即用,但大型数据集下那个重绘延迟确实让人头大;Matplotlib的静态图在报告里依旧能打,但“交互”两个字跟它基本不沾边。而Bokeh给我的感觉一直很特别——它的设计哲学从一开始就是奔着“浏览器”和“交互”去的,数据变了,图表可以通过服务器自动推送更新,这种流式刷新的能力,在别的库里很少见到这么干净的实现。
这篇文章我不会去复读官方文档的API列表,而是想从一个实际干过活的人的角度,讲清楚Bokeh是什么、它的核心组件怎么用、能做出什么样的交互效果,以及我在项目里踩过的一些坑。如果你正打算入门Bokeh,或者在纠结要不要把某个可视化需求迁移到Bokeh上,这篇东西应该能帮你省下不少时间。
1. 为什么要用Bokeh:它解决的是静态图解决不了的问题
1.1 Bokeh的核心定位与设计理念
Bokeh在Python可视化库里的定位,用一句话概括就是:面向浏览器的交互式可视化库。它把你的数据转换成可以在现代浏览器里直接运行的HTML和JavaScript代码,你不需要写一行前端代码,就能得到一个带缩放、平移、悬停提示、点击联动、甚至实时数据推送的图表。
这里有个很有意思的设计选择:Bokeh的API是纯Python的,但它生成的底层结构是一套基于JSON的协议,叫做BokehJS。也就是说,你写的Python代码本质上是在描述“这个图应该有哪些元素、哪些交互”,然后Bokeh会把这套描述翻译成浏览器能识别的脚本。这种做法带来的好处很实在——你不必掉进JavaScript的坑里,同时图表依然是真正的Web原生组件,可以在任何网页、仪表盘里无缝嵌入。
我个人的体会是,Bokeh最打动我的地方在于它对“数据流”的支持。传统的绘图流程是“数据准备好了 → 画图 → 保存成图片 → 丢给别人看”,一旦数据更新,整套流程得重来。Bokeh则不一样,它允许你建立一个与Python服务端相连的图,数据一变,图自动变。这在做实时监控面板、交易数据看板这类场景时,简直是一种享受。
1.2 它和Matplotlib、Plotly的关键差异
很多人入门Python可视化时都从Matplotlib开始,然后慢慢发现它画静态图确实很强,还专门对比过Matplotlib和Bokeh:Matplotlib的强项是出版级别的静态图,它把每个轴、每个线条的细节都控制得死死的;Bokeh的强项则完全在另一个维度,它关心的是“用户在看这张图的时候,能不能自己探索数据”。比如鼠标滚轮缩放、拖拽平移、框选放大,这些在Bokeh里是默认就有的能力,Matplotlib里你得写一堆事件绑定的代码才能勉强实现。
和Plotly相比,Bokeh的差异更微妙。两者都做交互式可视化,但底层思路完全不同。Plotly的渲染主要靠WebGL和SVG,图表一多、数据一大,页面会出现明显的卡顿感。Bokeh则有一套自己的Canvas渲染引擎,处理千万级数据点的能力要从容得多。另外,Bokeh的生态里包含了一个叫Bokeh Server的组件,它能让你在Python和浏览器之间保持双向通信。这个能力在构建复杂业务系统时非常关键,因为你可以直接在Python回调函数里处理用户在前端的操作,比如用户点了一下图里的某个点,Python端立刻就能收到信号并做出响应。Plotly也有Dash框架,但那是另一套更重型的全家桶方案。
1.3 适合用Bokeh的场景清单
根据我这几年在真实项目里的经验,这几个场景用Bokeh是特别舒服的:
- 数据探索看板:你自己需要在大量数据里找规律,拖拽缩放、框选局部数据这些操作能大大提升效率。
- 给非技术同事交付分析结果:做一个带交互的网页,丢给对方点开就能用,比发一堆静态PNG截图体面得多。
- 实时数据监控:比如监控服务器指标、爬虫运行状态、股票价格变化,Bokeh Server能直接把数据推送到前端。
- 需要嵌入自己Web应用的图表:Bokeh提供了多种嵌入方式,既能生成完整页面,也能作为一个组件嵌进现成的HTML里。
- 地理空间数据可视化:Bokeh对经纬度坐标有原生支持,配合底图插件可以做出来不错的地图效果。
不太适合的场景也有:如果你只是要一张放在论文里的静态图,说实话Matplotlib或者Seaborn更合适,Bokeh虽然也能导出静态图,但杀鸡用牛刀了;如果你需要极其复杂的自定义前端交互效果,那还是老老实实写前端吧。
2. 核心概念拆解:figure、glyph和ColumnDataSource
2.1 Figure:Bokeh的画布入口
Bokeh的绘图起点是figure函数,它跟Matplotlib的plt.subplots在概念上有相似之处,但返回的对象能力要强大得多。你创建了一个figure,实际上就创建了一个带默认工具栏、坐标轴、网格线的绘图区域。
from bokeh.plotting import figure p = figure( title="某电商平台月度销售趋势", width=900, height=500, x_axis_label="月份", y_axis_label="销售额(万元)", x_range=("1月", "2月", "3月", "4月", "5月", "6月"), tools="pan,wheel_zoom,box_zoom,reset,save" )这里有个细节值得注意:x_range我传的是一个字符串列表,Bokeh会自动把x轴识别为类别型(Categorical)坐标轴。如果你不传这个参数,Bokeh则会根据数据类型推断坐标轴类型——数值型自动用线性轴,时间型自动用时间轴。这个自动推断机制大部分时候很聪明,但一旦数据里混进了奇怪的类型,它也会给你弄出点莫名其妙的结果,建议在关键图表里显式指定坐标轴类型。
tools参数指定了图表自带的交互工具。pan是拖拽平移、wheel_zoom是滚轮缩放、box_zoom是框选缩放、reset是回到初始视图、save是保存图片。这些工具会以图标的形式显示在图表旁边的工具栏里,用户自己就能操作,完全不用你额外写代码。
2.2 Glyph:图表的视觉元素
Glyph这个概念是Bokeh的核心抽象,理解它,你就理解了Bokeh的百分之六十。简单来说,glyph就是你画在图上的一种图形元素,比如圆圈、方块、线条、柱子、弧形、扇形等等。Bokeh内置了几十种glyph,涵盖散点图、折线图、柱状图、面积图、热力图、饼图等常见图表类型。
from bokeh.plotting import figure, show from bokeh.models import ColumnDataSource import pandas as pd # 准备数据 data = pd.DataFrame({ "month": ["1月", "2月", "3月", "4月", "5月", "6月"], "sales": [320, 410, 385, 520, 600, 780], "cost": [200, 250, 230, 280, 330, 400] }) source = ColumnDataSource(data) p = figure( title="销售额与成本对比", width=800, height=400, x_range=data["month"].tolist(), tools="pan,wheel_zoom,box_zoom,reset" ) # 添加柱状图glyph p.vbar( x="month", top="sales", source=source, width=0.4, color="#4A90D9", legend_label="销售额", alpha=0.8 ) # 添加另一个柱状图glyph,错开位置 p.vbar( x="month", top="cost", source=source, width=0.4, color="#E8836B", legend_label="成本", alpha=0.8, x_offset=0.25 ) show(p)这个例子很有意思的地方在于,里面加了legend_label参数,Bokeh会自动生成图例;alpha参数控制了透明度,当两个柱子靠得很近的时候,透明度能避免后面的柱子完全被挡住。我还用了x_offset来把第二个柱子向右错开,这个参数是新版本才有的,旧版本你得自己计算偏移量。
2.3 ColumnDataSource:Bokeh的数据引擎
这是我认为Bokeh最值得花时间理解的概念。
ColumnDataSource,简称CDS,是Bokeh的数据结构核心。你可以把它理解成一个“字典的升级版”:它把每一列数据以数组的形式存储,并且要求所有列的长度必须一致。为什么需要这个东西?因为Bokeh需要把数据从Python传递到JavaScript端,而CDS就是两者之间的标准协议格式。
source = ColumnDataSource(data={ "x": [1, 2, 3, 4, 5], "y": [6, 7, 2, 4, 5], "label": ["A", "B", "C", "D", "E"], "color": ["#4A90D9", "#E8836B", "#50B83C", "#F5A623", "#8B5CF6"] }) p.circle(x="x", y="y", radius=0.2, fill_color="color", legend_label="label", source=source)注意在p.circle里,x和y传的都是字符串"x"和"y",而不是实际的列表数据。这个语法看起来奇怪,但它的含义是:“从source里取名为x的列和名为y的列来画图”。一旦你的图绑定到了CDS上,后面所有交互事件和动态更新,都基于这个CDS来进行。
CDS的价值在交互场景里体现得最淋漓尽致。举个例子,你想做一个图,用户点击某个数据点后,另一个图同步高亮这个点在另一个维度上的表现。如果你把两组数据都放到同一个CDS里,把两个图绑定到同一个CDS上,这个联动效果几乎是零成本实现的。因为Bokeh里所有的选择、高亮、回调机制,都是围绕CDS的索引来工作的。
# 同一个CDS可以被多个图共享 source = ColumnDataSource(df) p1 = figure(width=400, height=400, title="视图1") p1.circle("x", "y", source=source, size=10) p2 = figure(width=400, height=400, title="视图2") p2.line("x", "z", source=source, line_width=2)这样一来,你在p1上选择了一个数据点,p2能感知到你的选择状态,因为它在看同一个CDS。这种“多个图共享一份数据”的模式,在搭建仪表盘时非常实用。
3. 从零开始:5分钟跑通一个Bokeh实战Demo
3.1 安装与环境准备
Bokeh的安装非常省心,用pip一行命令就完事:
pip install bokeh如果想升级到最新的开发版,也可以从GitHub仓库拉下来安装,但日常使用完全没有必要,稳定版就够了。装完之后,建议在Jupyter Notebook里测试一下:
from bokeh.io import output_notebook output_notebook()这个output_notebook函数告诉你用的是哪个输出环境。在Notebook里调用它,图表就会直接渲染在单元格下方;如果你写的是普通脚本,则需要用output_file指定输出HTML文件的路径:
from bokeh.io import output_file, show output_file("sales_dashboard.html") show(p)show函数会启动一个本地HTTP服务器,然后在浏览器里打开渲染好的页面。这个过程是即时的,你每次运行脚本,浏览器都会自动刷新加载最新图表。
3.2 一个完整的销售数据图表实战
下面我用一个相对完整的例子,把前面讲到的概念串起来。假设你现在拿到了一份某电商平台各门店的销售数据,想知道每个门店的销售额和利润情况,还要看看目标完成率。
import pandas as pd import numpy as np from bokeh.plotting import figure, show, output_file from bokeh.models import ColumnDataSource, HoverTool, RangeSlider # ========== 1. 准备数据 ========== np.random.seed(42) stores = [f"门店{i}" for i in range(1, 21)] sales = np.random.randint(800, 20000, size=20) profit = sales * np.random.uniform(0.1, 0.35, size=20) target = np.random.randint(10000, 22000, size=20) df = pd.DataFrame({ "store": stores, "sales": sales, "profit": profit.round(), "target": target, "rate": (sales / target * 100).round(1) }) # ========== 2. 创建数据源 ========== source = ColumnDataSource(df) # ========== 3. 创建图表 ========== p = figure( title="2024年Q4各门店销售业绩分析", width=1000, height=500, x_range=stores, tools="pan,wheel_zoom,box_zoom,reset,save", tooltips=[("门店", "@store"), ("销售额", "@sales{0,0}"), ("利润率", "@profit{0,0}"), ("完成率", "@rate%")] ) # ========== 4. 添加图形元素 ========== p.vbar(x="store", top="sales", source=source, width=0.6, color="#4A90D9", legend_label="销售额", alpha=0.85) p.circle(x="store", y="target", source=source, size=8, color="#E8836B", legend_label="目标值") # ========== 5. 设置细节 ========== p.xaxis.major_label_orientation = 45 # 横轴标签旋转45度,避免文字重叠 p.yaxis.axis_label = "金额(元)" p.legend.location = "top_left" p.legend.click_policy = "hide" # 图例可点击隐藏对应元素 # ========== 6. 输出图表 ========== output_file("store_sales_analysis.html") show(p)这里面有几个细节值得说一说。
首先是tooltips参数。我在figure里直接传了这个参数,它会在用户鼠标悬停在数据点上时显示一个信息框。字符串里@store的意思是“取CDS里store列的值”,@{0,0}则是一个数字格式模板,0,0表示千位分隔符格式。这个语法是Bokeh的定制模板语言,熟悉它之后制作信息框会很灵活。
其次是legend_location和legend.click_policy。前者控制图例位置,后者是个非常实用的交互功能:用户点击图例里的某项,对应的图形元素就会隐藏起来,这在数据项太多需要临时聚焦时特别好用。
最后是xaxis.major_label_orientation = 45。当门店数量多起来,横轴的文字标签会打架,旋转45度之后清晰多了。这种细节处理,往往决定了图表是否“专业”。
3.3 保存与分享的几种姿势
Bokeh图表做好之后,分享方式很灵活:
show(p):直接在浏览器里打开,适合自己查看和调试。output_file("xxx.html")+show(p):保存成HTML文件,可以直接发给别人,对方双击就能在浏览器里打开看,不需要安装Python环境。p.save("xxx.html"):有时候别人不需要交互功能,只想快速看个图片,可以用这种方式。- 在Jupyter Notebook里直接用:图表会作为一个富文本HTML对象嵌入在单元格里。
有一种情况需要注意:如果你把HTML文件发给别人,对方打开时发现图表不显示,很可能是文件路径里带有中文字符或者浏览器安全策略拦截了本地JS。解决办法是用p.open_browser()配合一个本地静态服务器来访问,或者直接把Bokeh的JS库CDN链接嵌入到HTML里。
不过我自己最常用的方式,其实是下面要讲的Bokeh Server模式——直接把图表做成一个实时运行的Web服务,数据更新的时候页面自动刷新,这才是Bokeh真正的完全体。
4. 交互进阶:从静态展示到动态联动
4.1 用HoverTool增强用户体验
HoverTool是Bokeh里最常用的交互工具之一,它的作用是在鼠标悬停时展示详细信息。你可以把它添加到figure的tools列表里,也可以实例化后单独通过add_tools方法挂载:
from bokeh.models import HoverTool hover = HoverTool( tooltips=[ ("门店", "@store"), ("销售额", "@sales{0,0}"), ("利润率", "@profit{0.0%}"), ("完成率", "@rate{0.0}%") ], mode="mouse" # 鼠标悬停触发;还有 "vline" 和 "hline" 模式 ) p.add_tools(hover)mode参数值得展开讲一下。默认的mouse模式是鼠标指到哪个点就显示哪个点的信息;vline模式则是鼠标在同一竖直位置时,会同时显示这个位置所有数据点的信息,适合折线图里多个序列对比的场景;hline模式类似,按水平位置触发。在折线图里做多序列对比时,用vline模式体验会好很多。
4.2 滑杆、下拉框与按钮:让图表听你的话
光有悬停提示还不够,真正的交互需要用户能主动改变图表的呈现内容。Bokeh的Panel、Slider、Select、Button这些控件,配合JavaScript回调,可以让你做出“用户拖一拖滑杆,图表数据就跟着变”的效果。
这里我分享一个我常用的模式:用JavaScript回调更新数据。Bokeh支持用一种叫CustomJS的机制,直接在Python代码里写JavaScript代码,然后绑定到控件事件上。
from bokeh.models import Slider, CustomJS slider = Slider(start=500, end=20000, step=500, value=5000, title="最低销售额阈值") callback = CustomJS(args={"source": source}, code=""" const data = source.data; const threshold = cb_obj.value; // 遍历所有数据点,根据阈值调整柱子颜色 for (let i = 0; i < data['sales'].length; i++) { if (data['sales'][i] >= threshold) { data['color'][i] = '#4A90D9'; // 达标 - 蓝色 } else { data['color'][i] = '#E8836B'; // 未达标 - 红色 } } source.change.emit(); """) slider.js_on_change("value", callback)这段代码实现的效果是:用户拖动滑杆设定一个销售额阈值,低于阈值的门店柱子立刻变成红色,高于的保持蓝色。source.change.emit()是关键,它通知Bokeh“这个数据源变了,请重新渲染所有绑定它的图形”。
这种用CustomJS的做法的好处是:所有逻辑都在前端完成,不需要起Bokeh Server,性能极好。适合做数据筛选、颜色变化、文本更新这类交互。但它的局限也很明显——前端JavaScript里没法直接跑Python代码,你要是想在前端回调里做复杂的Pandas数据操作,那就别硬用CustomJS了,应该上Bokeh Server。
4.3 用Bokeh Server实现真正的Python联动
前面提到的CustomJS,本质上只是把JavaScript代码“交给浏览器执行”。但Bokeh最厉害的地方在于:它允许你在Python端运行回调函数,实现前后端双向通信。这一步靠的是Bokeh Server。
Bokeh Server的使用方式很简单,你不调用show(p),而是调用curdoc().add_root(p),然后通过bokeh serve --show my_app.py启动服务。在服务模式下,你可以用on_change绑定事件到Python函数上:
# app.py from bokeh.plotting import figure, curdoc from bokeh.models import Slider, ColumnDataSource import pandas as pd # 准备数据 df = pd.DataFrame({"month": range(1, 13), "sales": [120, 150, 130, 200, 180, 210, 260, 240, 280, 300, 320, 350]}) source = ColumnDataSource(df) p = figure(title="月度销售趋势", width=800, height=400) p.line(x="month", y="sales", source=source, line_width=3) slider = Slider(start=0, end=2, step=0.1, value=1, title="增长率") # 这个函数是Python代码,会在滑杆变化时被调用 def update(attr, old, new): rate = slider.value new_sales = df["sales"] * rate source.data = {"month": df["month"], "sales": new_sales.tolist()} slider.on_change("value", update) curdoc().add_root(p) curdoc().add_root(slider)然后命令行执行:
bokeh serve --show app.py浏览器会自动打开本地地址,你会看到一个带滑杆的交互图表。每次拖动滑杆,Python代码都会在服务端被触发执行,更新数据源,图表自动刷新。
这个模式的能力上限,说实话非常高。因为你在服务端能做任何事情——查数据库、调API、做机器学习推理、跑复杂的统计计算——然后把结果推送到前端。我在实际项目里做过一个数据异常检测的看板,用户在前端选择时间范围,后端Python立刻对这段数据跑一遍异常检测算法,把异常点标红推送到图上,整个过程非常流畅,体验接近原生桌面应用。
5. 布局与集成:把多个图拼成一个完整的看板
5.1 用Column、Row和Tabs组织图表
一个正经的数据看板,往往不只有一张图。Bokeh提供了非常方便的布局组件,能让你把多个图拼装成不同的版面结构。
column是垂直排列,row是水平排列,gridplot可以看成行列组合的通用网格。更进阶一点的Tabs组件,能把多个图放到不同的标签页里,适合组织多个维度的分析内容。
from bokeh.layouts import column, row, gridplot from bokeh.models import Tabs, Panel # 假设已经创建了 p1, p2, p3, p4 四个图 # 1. 简单垂直排列 layout_v = column(p1, p2) # 2. 网格布局:两行两列 layout_grid = gridplot([[p1, p2], [p3, p4]], width=500, height=300) # 3. 标签页布局 tab1 = Panel(child=p1, title="销售趋势") tab2 = Panel(child=p2, title="利润分析") tabs = Tabs(tabs=[tab1, tab2])我个人的经验是:网格布局gridplot在做季度/月度对比报告时特别方便,因为你可以在同一个图框里塞入多个散点图,快速对比不同维度的数据分布。Tabs则适合做一个“总览 + 明细”的结构,总览页放核心指标,明细页放详细数据表或者更多维度的拆分图表。
5.2 嵌入到现有Web应用的几种方式
Bokeh图表做好了,要不要嵌入到你自己的Web应用里?这取决于你的Web应用是什么技术栈。
- 纯HTML页面:用
script, div = components(p)拿到一段JS和一段HTML,直接粘到你的页面里,引一下Bokeh的CDN文件即可。 - Flask服务:在Flask路由里渲染Bokeh图表,可以用
components生成片段,然后交给Jinja2模板渲染。 - Django服务:同样可以用
components方式,也可以使用Bokeh Server做更复杂的实时交互。
# Flask集成示例 from flask import Flask, render_template from bokeh.embed import components from bokeh.plotting import figure app = Flask(__name__) @app.route("/") def index(): p = figure(width=600, height=400) p.line([1, 2, 3], [4, 5, 6]) script, div = components(p) return render_template("index.html", script=script, div=div) if __name__ == "__main__": app.run(debug=True)在index.html模板里,你需要引入Bokeh的JS和CSS文件,然后把{{ script | safe }}和{{ div | safe }}放到合适的位置。这种方式特别适合给公司的内部系统快速加一个可视化页面,不用起独立的Bokeh服务,开销也小。
5.3 导出静态图片:什么时候需要、怎么操作
交互式图表确实酷炫,但有些场合你依然需要一张静态图片——比如放进PPT、写进周报邮件。Bokeh的导出功能有点特殊,它依赖Selenium和浏览器驱动,因为它是用“打开网页然后截图”的方式来实现导出的。
from bokeh.io import export_png, export_svg export_png(p, filename="chart.png")在没装浏览器驱动之前,这个函数会报错找不到Chrome驱动。装好之后倒是简单,但是导出的PNG清晰度一般,缩放比例也有限制。所以我的建议是:如果只是偶尔需要静态图片,直接在浏览器里打开图表,用截图工具截一张,效果更好,不用折腾环境。真要大批量生成静态图走流程,再考虑配置Selenium。
6. 几个我实际踩过的坑和避坑心得
6.1 中文字体显示问题
这是一个国产环境特有的坑。Bokeh默认的字体配置里,中文字符经常会渲染成方框或者乱码。我的解决方案是显式设置字体家族:
p.xaxis.axis_label_text_font = "Microsoft YaHei" p.xaxis.major_label_text_font = "Microsoft YaHei" p.title.text_font = "Microsoft YaHei"或者统一设置主题:
from bokeh.themes import Theme theme = Theme(json={ "attrs": { "Axis": {"axis_label_text_font": "Microsoft YaHei", "major_label_text_font": "Microsoft YaHei"}, "Title": {"text_font": "Microsoft YaHei"} } }) p.theme = theme注意,这套配置在不同的操作系统上要改字体名称。Windows上是Microsoft YaHei,macOS上是PingFang SC,Linux服务器上就得看装了什么中文字体,比如WenQuanYi Zen Hei或Noto Sans CJK SC。
6.2 数据集较大时的性能优化
Bokeh处理百万级数据点比大多数Python绘图库都强,但也不是没有上限。我在处理千万级点位数据时,明显感觉到交互变得卡顿。性能优化的核心思路有三个:
第一,抽样。在数据传递到Bokeh之前,先用Pandas的sample或resample降低数据密度。对于时间序列,可以先做降采样;对于散点图,可以用scatter的radius大小来直观控制点的显示数量。
第二,用WebGL渲染。Bokeh的散点图支持WebGL后端,开启方式很简单:
p = figure(output_backend="webgl")开启之后,大量点的渲染效率会提升很多,尤其在硬件加速的浏览器上,体验差别非常明显。
第三,用图像的传递方式处理超大栅格数据。如果你在做热力图这类数据,Bokeh提供了Image和ImageRGBA两个glyph,它们把数据以图形像素的形式传递,速度极快,但会丢失交互式点选信息。取舍取决于业务需求。
6.3 图表联动的经典困境
我碰到过好几次这样的需求:两个图绑定了同一个CDS,看起来链路很顺,但实际运行时发现,一个图的选择状态没法同步到另一个图的高亮效果。排查下来的原因通常是:两个图虽然共享了CDS,但没有共享Selection状态。Bokeh的ColumnDataSource里有一个叫selected的属性和indices属性,当你在图1上选择了一个点,Bokeh默认只更新图1的内部状态。要让图2感知,需要你手动触发事件监听:
source.selected.on_change("indices", callback)或者更简单一点,给多个图绑定同一个数据源时,确保你用的是同一个ColumnDataSource实例,并且在图属性配置里显式声明selection_policy为render等需要的模式。这个问题比较隐蔽,新手很容易被表面的共享数据源迷惑,实际调试时才发现状态没有同步。
6.4 小技巧:颜色映射与色带应用
最后分享一个我非常喜欢的小功能——颜色映射。当你的数据点要按照数值大小着色时,不需要手动生成颜色列表,Bokeh有专门的LinearColorMapper:
from bokeh.models import LinearColorMapper, ColorBar color_mapper = LinearColorMapper(palette="Viridis256", low=min(df["sales"]), high=max(df["sales"])) p.scatter( x="sales", y="profit", source=source, fill_color={"field": "rate", "transform": color_mapper}, size=12, color="black" ) color_bar = ColorBar(color_mapper=color_mapper, location=(0, 0)) p.add_layout(color_bar, "right")这里fill_color的语法很有意思:{"field": "rate", "transform": color_mapper}表示“从CDS的rate列取值,然后用color_mapper把值映射成颜色”。配上一个ColorBar,你的散点图瞬间能多表达一个维度,信息密度高很多,而且视觉上也非常专业。
7. 写在最后:Bokeh的取舍与进阶方向
做技术选型的时候,我见过太多人一上来就问“哪个库最厉害”,但实际编码的时候才发现,每个库都有自己的脾气。Bokeh的脾气是:它需要你稍微花点时间理解ColumnDataSource这个核心概念,一旦跨过这个门槛,后面的事情基本水到渠成;它不擅长做超大规模数据集的静态高保真输出,但在交互探索和实时推送这个赛道上,它是Python生态里少有的能打选手。
如果接下来你想深入学Bokeh,我的建议是按这个顺序来:先玩玩figure和常用glyph,把柱状图、散点图、折线图画溜;然后啃ColumnDataSource,试着用同一个数据源驱动多个图;接着学CustomJS,做几个能响应滑杆、下拉框的小交互;最后上Bokeh Server,把它跑成一个真正的Web应用。每个阶段都有大量的官方示例可以直接抄,官网的Gallery是我见过最好的可视化库案例库,几乎每个示例都能一键查看源码。
我在好几个项目里都用Bokeh做过最终交付的仪表盘,同事们的反馈出奇一致:“这个页面点起来很爽,比我之前看的Excel截图强多了。”也许对一个数据人来说,最好的夸奖莫过于此。工具会变,但“让数据自己说话”这件事,值得反复打磨。