把二维数值网格制作成可更新的 Bokeh 等值线图
原文:Bokeh Contributors,Contour plots。中文翻译与技术整理:未完纪。2026-10-08 核对 Bokeh 3.10.0 文档;等值线功能自 Bokeh 3.0 引入。

等值线图在二维四边形网格上计算数值相同的曲线,也可以填充相邻等级之间的区域。Bokeh 的 contour() 一次调用就能生成线和填色多边形,再用相同渲染器构造匹配的色条。数值轮廓由 Python 端的 ContourPy 计算,Bokeh 负责显示。
本文完整整理 Bokeh 官方 Contour plots 的三个例子及高级说明。所有输入由 NumPy 合成;本轮未执行代码或启动服务器,配图为技术示意。
从两个高斯函数开始
第一个例子在 x 方向取 40 个点,在 y 方向取 30 个点,再通过 meshgrid 形成形状为 (30, 40) 的网格。z 是一个正高斯峰与一个负高斯峰相加得到的场。levels 从 -1 到 1 均匀选取 9 个等值等级,Sunset8 为相邻等级间的 8 个区域提供颜色。
import numpy as np
from bokeh.palettes import Sunset8
from bokeh.plotting import figure, show
# 两个高斯函数相加形成待绘制的数值场。
x, y = np.meshgrid(np.linspace(0, 3, 40), np.linspace(0, 2, 30))
z = (
1.3*np.exp(-2.5*((x-1.3)**2 + (y-0.8)**2))
- 1.2*np.exp(-2*((x-1.8)**2 + (y-1.3)**2))
)
p = figure(width=550, height=300, x_range=(0, 3), y_range=(0, 2))
levels = np.linspace(-1, 1, 9)
contour_renderer = p.contour(
x, y, z, levels, fill_color=Sunset8, line_color="black",
)
colorbar = contour_renderer.construct_color_bar()
p.add_layout(colorbar, "right")
show(p)
按惯例,z 是定义在 x、y 网格上的二维数值数组。这个例子用的是等距笛卡尔网格;levels 指定要计算哪些数值等级。要画线,必须提供 line_color;要画填色区间,必须提供 fill_color。两者都可以是一个标量属性,也可以是按等级分配的向量属性。
数量关系需要特别留意:等值线的向量属性长度应为 len(levels),而填色多边形的向量属性长度应为 len(levels)-1。这里所有线用同一种黑色,填色使用 8 种不同颜色。construct_color_bar() 返回的色条自动沿用轮廓图的线与填色属性,避免手动维护两套不一致的颜色规则。
极坐标网格也可以使用
网格不一定必须是直角坐标下的等距网格。第二个例子以半径和角度建立二维网格,将它们映射成 x、y,再在圆形区域上定义一个二维正弦场。网格在角度方向绕回自身。
import numpy as np
from bokeh.palettes import Cividis
from bokeh.plotting import figure, show
radius, angle = np.meshgrid(
np.linspace(0, 1, 20), np.linspace(0, 2*np.pi, 120),
)
x = radius*np.cos(angle)
y = radius*np.sin(angle)
z = 1 + np.sin(3*angle)*np.sin(np.pi*radius)
p = figure(width=550, height=400)
levels = np.linspace(0, 2, 11)
contour_renderer = p.contour(
x=x, y=y, z=z, levels=levels,
fill_color=Cividis,
hatch_pattern=["x"]*5 + [" "]*5,
hatch_color="white",
hatch_alpha=0.5,
line_color=["white"]*5 + ["black"] + ["red"]*5,
line_dash=["solid"]*6 + ["dashed"]*5,
line_width=[1]*6 + [2]*5,
)
colorbar = contour_renderer.construct_color_bar(title="Colorbar title")
p.add_layout(colorbar, "right")
show(p)
此处共有 11 个等级、10 个填色区间。hatch_pattern 的两组长度相加为 10;line_color、line_dash 与 line_width 的两组或三组长度相加为 11。借助这些向量,可以突出某些等级,例如用不同线型与线宽强调区域边界;不应把线属性与区间属性按同一长度随意拼接。
一般视觉属性可以是标量或长度正确的向量。line_color、fill_color 与 hatch_color 额外接受调色板规则:颜色序列过长或过短时会通过 interp_palette() 重采样,像 Cividis256 这样的长调色板适合这种用法。也可以传入 Cividis 调色板集合,它是从颜色数量映射到具体调色板的字典;有准确长度就直接使用,没有时会选取相近长度并线性插值。这个自动处理仅指颜色属性,不能推而广之地认为所有线型、线宽或纹理向量都能随意长度。
construct_color_bar() 的附加关键字参数会传给 ContourColorBar 构造器,所以上例可以设置色条标题。
在服务器中更新同一个等值线图层
轮廓计算发生在 Python,因此原文的动画使用 Bokeh server,而不是仅导出一个静态 HTML 就能执行 Python 回调。下面的场由四个正负高斯峰相加组成,随时间改变它们的半径;网格与等级在整个动画中保持不变。
import numpy as np
from bokeh.driving import count
from bokeh.palettes import PiYG
from bokeh.plotting import curdoc, figure
from bokeh.plotting.contour import contour_data
x, y = np.meshgrid(np.linspace(-1, 1, 41), np.linspace(-1, 1, 41))
levels = np.linspace(-1.0, 1.0, 11)
def get_z(timestep):
delta = 0.08*np.cos(timestep*0.15)
amps = [0.95, 0.95, -0.95, -0.95]
xmids = [-0.4, 0.4, -0.4, 0.4]
ymids = [-0.4, 0.4, 0.4, -0.4]
rads = [0.4 + delta, 0.4 + delta, 0.4 - delta, 0.4 - delta]
z = np.zeros_like(x)
for amp, xmid, ymid, rad in zip(amps, xmids, ymids, rads):
z += amp*np.exp(-((x-xmid)**2 + (y-ymid)**2)/rad**2)
return z
@count()
def callback(timestep):
z = get_z(timestep)
new_contour_data = contour_data(x, y, z, levels)
contour_renderer.set_data(new_contour_data)
fig = figure(width=600, height=450)
contour_renderer = fig.contour(
x, y, get_z(0), levels,
fill_color=PiYG, line_color="black",
line_width=[1]*5 + [3] + [1]*5,
)
colorbar = contour_renderer.construct_color_bar()
fig.add_layout(colorbar, "right")
curdoc().add_periodic_callback(callback, 40)
curdoc().add_root(fig)
把这一段保存为 contour_animated.py 后,源文的启动方式如下。命令会启动服务并尝试打开浏览器;本轮没有执行。用于远程访问时还需要独立设计身份验证、监听地址、网络边界和部署方式,不能把本地演示命令等同于完整生产配置。
bokeh serve --show contour_animated.py
更新过程是:先按通常方式调用 contour() 并保留 ContourRenderer;计算新的 z;把新 z 与不变的 x、y、levels 传给 contour_data();再把返回的数据交给 set_data()。之后反复执行后三步。40 毫秒是回调调度间隔,不是已经测得每秒能完成 25 帧的性能保证。
原例假设网格、等级和视觉属性不变。技术上可以修改它们,但必须同时处理图形边界和视觉属性到等级的分配关系。遇到这些变化时,通常删除旧的、不再需要的轮廓图层,再创建新图层更简单。不要只替换 z 却遗忘对应网格、色条和属性长度的改变。
缺失值与底层实现
调用 contour() 必须提供 z、levels,以及 fill_color 或 line_color 中至少一个。x、y 可省略,此时默认用两个方向网格间距均为 1 的笛卡尔网格。需要从计算中排除网格点时,可把 z 设成 NumPy 掩码数组并遮罩对应位置,或者把对应 z 值设为 np.nan。
等值线使用 MultiLine glyph 实现,填色轮廓使用 line_width 为 0 的 MultiPolygons glyph 实现。数值计算交给 ContourPy;更深入的算法与网格约束应查阅其文档。Bokeh 等值线功能从 3.0 加入,本文核对页面为 3.10.0,不把未来计划中的改进当成已交付能力。
代码审核与可用范围
三个例子只创建合成数组、图形和本地回调,没有硬编码秘密、下载第三方样本或破坏性命令。静态检查确认例中等级数与线/填色属性长度对应;高斯半径在本例变化范围内保持正值。未实测安装兼容性、动画帧率、浏览器显示或服务器行为,也没有用真实测量场验证插值精度。图形平滑和颜色美观不等于数据准确。
原文与参考:Bokeh Contour plots;ContourPy 文档。©2026 Bokeh Contributors。源网站内容按其页脚所链接的 BSD License 发布;完整许可随稿(BSD 许可文本),翻译整理、注释和原创示意图由未完纪补充。











暂无评论内容