☰
kegg-network-viewer:快速构建KEGG通路网络图的HTML工具
2026/10/17 7:16:29 网站建设 项目流程

简介:KPV-KEGG PATHWAY VIEWER 是一套面向生物信息学与代谢通路研究者的在线可视化工具,用于直观呈现 KEGG 代谢途径并支持交互操作。它依据 AP 检测算法对蛋白质进行分类,帮助使用者快速识别网络中连接度最高的关键蛋白节点,同时提供 KEGG 路径的动态 HTML 可视化,可模拟节点去除、边缘添加等基本操作,适合需要分析通路瓶颈与蛋白互作关系的中高级科研人员。资源包共 956 个文件,以 211 个 js 脚本、167 个 html 页面、61 个 css 与 42 个 less 样式文件构成前端主体,另含 177 个 png、29 个 gif 等图像素材及 131 个 md 说明文档,并附带 php、sql、sqlite 等后端与数据文件,整体约 62.01MB,目录结构完整。目前已有 1086 人学习下载。借助该工具,读者可搭建本地可视化环境,复现蛋白分类与通路交互效果,并参考文档理解网络构建逻辑,为代谢通路研究提供可运行的实践基础。

1. 从一张画不出的通路图说起:kegg-network-viewer 能替你省掉什么

做转录组或代谢组分析的人,大概都经历过这个场景:差异基因列表跑出来了,KEGG 富集结果也拿到了,可当导师或甲方说“把这条通路的调控关系画成网络图”时,你打开绘图工具,面对几十个基因、代谢物和它们之间的调控箭头,鼠标悬在半空,不知道从哪一根线开始连。手工在 Cytoscape 里一个个拖节点、连边、调布局,一张图能耗掉一整个下午,改一版数据还得从头再来。kegg-network-viewer 就是冲着这个痛点来的——它把 KEGG 通路里的基因、化合物、酶之间的关系抽成网络结构,用 HTML 做前端渲染,让你在浏览器里直接看、直接筛、直接导出。适合三类人:做组学数据分析、需要快速出通路网络图的生信从业者;带学生做课题、想让学生自己动手探索通路关系的导师;以及不想装一堆桌面软件、只想在浏览器里完成可视化的开发者。它不替代 Cytoscape 的精细排版能力,但胜在轻、快、可复现——数据一换,图跟着变,不用重新拖一遍。

2. 拆开这个 HTML 工具:它到底怎么把 KEGG 关系画成网络

2.1 核心思路:从通路条目到节点-边结构

KEGG 本身提供的是通路图(pathway map),图里画的是代谢物、基因产物和反应关系,但它是按通路组织的,不是按网络组织的。kegg-network-viewer 做的事,本质上是把 KEGG 的条目关系重新映射成一张有向或无向图。常见做法是:先从 KEGG 的 REST 接口或本地 KGML 文件里拿到某条通路的条目列表,每个条目有 entry id、类型(gene / compound / enzyme)、名称和它参与的反应或调控关系。然后把这些条目当作节点,把反应或调控关系当作边,构建一个节点-边集合。最后用前端图布局算法(常见的是力导向布局)把节点撒在画布上,边连起来,形成一个可交互的网络。

这里有个关键选择:你是用 KEGG 在线接口实时拉数据,还是先把 KGML 下载到本地再解析。实时拉数据的好处是数据新,但受网络和接口稳定性影响;本地解析的好处是可复现、可离线,适合批量处理。我一般会建议先把目标通路的 KGML 文件下载下来,用脚本解析成 JSON,再喂给前端。这样前端只负责渲染,不负责数据获取,职责清晰,调试也方便。

2.2 前端渲染:HTML + 图布局库的选型理由

项目标题里明确写了 HTML,说明它的前端是纯 Web 技术栈。常见做法是用 D3.js 做力导向图,或者用 Cytoscape.js 做图渲染。D3 灵活,但需要自己处理很多细节;Cytoscape.js 封装好了图操作,适合快速搭建。如果这个工具的目标是“轻量、开箱即用”,那大概率是选了一个图布局库,把节点和边数据传进去,配置一下样式就出图。

为什么不用 Canvas 手绘?因为力导向布局需要反复迭代计算节点位置,用 SVG 或 Canvas 配合布局库更省事。而且 HTML 的好处是,你可以在图旁边加筛选面板、搜索框、导出按钮,交互逻辑用 JavaScript 写,不需要后端参与。这意味着你只要有一个静态文件服务器,甚至直接双击 HTML 文件,就能在浏览器里跑起来。

2.3 数据准备:从 KEGG 到可视化输入的转换步骤

假设你已经拿到了某条通路的 KGML 文件,接下来要把它转成前端能吃的 JSON。下面是一个常见的转换脚本骨架,用 Python 写,解析 KGML 里的 entry 和 relation 标签。

import xml.etree.ElementTree as ET import json def parse_kgml(kgml_path): tree = ET.parse(kgml_path) root = tree.getroot() nodes = [] edges = [] # 解析 entry 节点 for entry in root.findall('entry'): node = { 'id': entry.get('id'), 'name': entry.get('name'), 'type': entry.get('type'), 'label': entry.find('graphics').get('name') if entry.find('graphics') is not None else '' } nodes.append(node) # 解析 relation 边 for relation in root.findall('relation'): edge = { 'source': relation.get('entry1'), 'target': relation.get('entry2'), 'type': relation.get('type') } edges.append(edge) return {'nodes': nodes, 'edges': edges} if __name__ == '__main__': data = parse_kgml('pathway.kgml') with open('network.json', 'w') as f: json.dump(data, f, indent=2)

这段代码的逻辑很直接:用 ElementTree 解析 KGML,遍历所有 entry 标签提取节点信息,遍历所有 relation 标签提取边信息,最后输出一个包含 nodes 和 edges 的 JSON。参数方面,entry 的 type 可能是 gene、compound、enzyme 等,前端可以根据 type 给节点上不同颜色;relation 的 type 表示关系类型,比如 activation、inhibition、binding 等,前端可以用不同线型表示。注意,KGML 里的 entry id 是字符串,前端引用时不要当成数字处理,否则可能匹配不上。

2.4 前端加载与交互:把 JSON 喂给图库

前端部分,假设你用 Cytoscape.js,核心代码大概长这样:

// 加载转换好的网络数据 fetch('network.json') .then(res => res.json()) .then(data => { // 把节点和边转成 Cytoscape 需要的格式 const elements = []; data.nodes.forEach(n => { elements.push({ data: { id: n.id, label: n.label || n.name, type: n.type } }); }); data.edges.forEach(e => { elements.push({ data: { source: e.source, target: e.target, type: e.type } }); }); // 初始化图实例 const cy = cytoscape({ container: document.getElementById('cy'), elements: elements, style: [ { selector: 'node', style: { 'label': 'data(label)', 'background-color': ele => { // 根据节点类型上色 const t = ele.data('type'); if (t === 'gene') return '#4e79a7'; if (t === 'compound') return '#f28e2b'; return '#59a14f'; } } }, { selector: 'edge', style: { 'width': 2, 'line-color': '#ccc', 'target-arrow-shape': 'triangle', 'curve-style': 'bezier' } } ], layout: { name: 'cose', // 力导向布局 animate: true } }); });

这段代码做了几件事:从 network.json 拉数据,把节点和边转成 Cytoscape 的 elements 格式,然后初始化图实例。样式部分根据节点 type 设置不同背景色,边用贝塞尔曲线加箭头。布局用 cose(一种力导向布局),让节点自动散开。参数上,layout 的 name 可以换成 'circle'、'grid' 等,取决于你想怎么排;animate 控制是否动画过渡,数据量大时建议关掉,否则会卡。注意,如果节点数量超过几百个,力导向布局的计算时间会明显变长,这时候可以考虑先用后端算好坐标,前端只负责渲染。

3. 跑起来:从零复现一份可交互的 KEGG 网络图

3.1 环境准备与文件结构

这个工具是纯前端项目,理论上你只需要一个浏览器和一个静态文件服务器。但为了开发方便,我一般会这样组织目录:

kegg-network-viewer/ ├── index.html # 主页面 ├── js/ │ ├── main.js # 图初始化逻辑 │ └── lib/ │ └── cytoscape.min.js ├── data/ │ └── network.json # 转换后的网络数据 └── css/ └── style.css

如果你不想装 Node.js 或 Python 服务器,直接用浏览器打开 index.html 也能跑,但 fetch 加载本地 JSON 文件时可能会遇到跨域限制。常见做法是起一个简单的 HTTP 服务,比如用 Python:

python3 -m http.server 8080

然后在浏览器访问 http://localhost:8080。这样 fetch 就能正常加载 data/network.json 了。注意,如果你用的是 Windows 且装了 Python,命令可能是python -m http.server 8080,取决于环境变量配置。

3.2 数据转换的完整流程

假设你从 KEGG 下载了某个通路的 KGML 文件,比如hsa00010.kgml(糖酵解通路)。把它放到项目目录下,运行前面的 Python 脚本,生成 network.json。这里有几个细节要注意:

第一,KGML 里的 entry 可能包含多个图形元素(graphics),一个 entry 可能对应多个基因。如果你的分析需要区分每个基因,可能要把 entry 拆成多个节点。常见做法是看 entry 的 name 字段,如果包含多个基因名(用空格分隔),就拆成多个节点,边的关系复制到每个基因上。

第二,relation 的 subtype 可能包含更细的关系信息,比如 activation、inhibition、phosphorylation 等。如果你想让边的样式更丰富,可以把 subtype 也解析出来,前端根据 subtype 设置不同颜色或线型。

第三,有些 KGML 文件里的 entry 是 map 类型(表示通路本身),这种节点通常不需要画在网络里,可以在解析时过滤掉。

下面是一个增强版的解析脚本,处理了 entry 拆分和 subtype 提取:

import xml.etree.ElementTree as ET import json def parse_kgml(kgml_path): tree = ET.parse(kgml_path) root = tree.getroot() nodes = [] edges = [] entry_map = {} # 记录原始 entry id 到拆分后节点 id 的映射 for entry in root.findall('entry'): entry_id = entry.get('id') entry_type = entry.get('type') if entry_type == 'map': continue # 跳过通路本身 names = entry.get('name', '').split() graphics = entry.find('graphics') label = graphics.get('name') if graphics is not None else '' # 如果一个 entry 对应多个基因,拆成多个节点 for i, name in enumerate(names): node_id = f"{entry_id}_{i}" if len(names) > 1 else entry_id nodes.append({ 'id': node_id, 'name': name, 'type': entry_type, 'label': label if len(names) == 1 else name }) entry_map.setdefault(entry_id, []).append(node_id) for relation in root.findall('relation'): source_ids = entry_map.get(relation.get('entry1'), []) target_ids = entry_map.get(relation.get('entry2'), []) subtypes = [s.get('name') for s in relation.findall('subtype')] for s in source_ids: for t in target_ids: edges.append({ 'source': s, 'target': t, 'type': relation.get('type'), 'subtype': subtypes }) return {'nodes': nodes, 'edges': edges} if __name__ == '__main__': data = parse_kgml('hsa00010.kgml') with open('data/network.json', 'w') as f: json.dump(data, f, indent=2)

这个版本多了几个处理:跳过 map 类型节点,拆分多基因 entry,提取 subtype。参数上,entry_map 用来记录原始 id 和拆分后 id 的对应关系,确保边能正确连到拆分后的节点。subtype 是一个列表,前端可以根据它决定边的样式。注意,如果 entry 的 name 为空,拆分逻辑会出问题,实际使用时最好加个判断。

3.3 前端交互功能的实现

图渲染出来只是第一步,真正好用还需要交互。常见的交互功能包括:节点搜索、类型筛选、点击高亮、导出图片。下面以搜索和高亮为例,展示怎么在 Cytoscape.js 里实现。

// 搜索框输入时高亮匹配节点 document.getElementById('search').addEventListener('input', function(e) { const keyword = e.target.value.toLowerCase(); cy.nodes().forEach(node => { const label = (node.data('label') || '').toLowerCase(); if (keyword && label.includes(keyword)) { node.addClass('highlighted'); } else { node.removeClass('highlighted'); } }); }); // 点击节点时高亮它的邻居 cy.on('tap', 'node', function(e) { const node = e.target; cy.elements().removeClass('faded'); cy.elements().not(node).not(node.neighborhood()).addClass('faded'); node.neighborhood().addClass('highlighted'); });

这段代码做了两件事:搜索框输入时,遍历所有节点,匹配 label 的加上 highlighted 类;点击节点时,把非邻居元素加上 faded 类,邻居加上 highlighted 类。样式上,你需要在 Cytoscape 的 style 里定义 .highlighted 和 .faded 的视觉效果,比如 highlighted 加粗边框,faded 降低透明度。参数上,node.neighborhood() 返回节点的所有邻居(包括边),如果你只想高亮直接相连的节点,可以用 node.neighborhood().nodes()。

导出图片的功能,Cytoscape.js 提供了 cy.png() 方法,可以生成 PNG 的 data URI,然后触发下载。代码大概是这样:

document.getElementById('export').addEventListener('click', function() { const png = cy.png({ full: true, scale: 2 }); const link = document.createElement('a'); link.href = png; link.download = 'kegg-network.png'; link.click(); });

参数上,full: true 表示导出完整图(包括视口外的部分),scale: 2 表示两倍分辨率,适合放到论文或报告里。注意,如果图很大,导出可能会卡顿,建议先调整好布局再导出。

4. 避坑指南:数据解析和渲染时最容易翻车的几个地方

4.1 坑一:KGML 里的 entry id 和 relation 对不上

现象:解析完 KGML 后,前端图里只有节点没有边,或者边连到了错误的节点上。

原因:KGML 里 entry 的 id 是字符串,relation 里的 entry1 和 entry2 也是字符串,但如果你在解析时把 id 转成了数字,或者做了其他处理,就可能对不上。另外,如果一个 entry 被拆成了多个节点,relation 里的原始 id 需要映射到拆分后的 id,映射逻辑写错就会丢边。

解决:解析时始终保持 id 为字符串,不要做类型转换。拆分 entry 时,用字典记录原始 id 到新 id 列表的映射,relation 解析时通过映射找到所有对应的新 id,逐一连边。可以在解析脚本里加一句打印,检查 nodes 和 edges 的数量是否合理。

4.2 坑二:力导向布局节点重叠严重

现象:图渲染出来后,节点挤在一起,标签互相遮挡,根本看不清。

原因:力导向布局的参数没有调好,或者节点数量太多,默认的斥力不够。另外,如果节点初始位置随机,布局迭代次数不够,也会导致重叠。

解决:调整布局参数。以 Cytoscape.js 的 cose 布局为例,可以设置 nodeRepulsion(节点斥力)、idealEdgeLength(理想边长)、numIter(迭代次数)。常见做法是把 nodeRepulsion 调大,比如设为 10000 或更高;idealEdgeLength 设为 100 左右;numIter 设为 1000 以上。如果节点实在太多,可以考虑先用聚类算法把相似节点合并,或者改用圆形布局、网格布局。

4.3 坑三:浏览器加载本地 JSON 跨域报错

现象:直接双击 index.html 打开,控制台报 CORS 错误,fetch 加载 network.json 失败。

原因:浏览器的同源策略限制,file:// 协议下 fetch 不允许读取本地文件。

解决:起一个本地 HTTP 服务,比如前面说的python3 -m http.server。如果你不想装 Python,也可以用 Node.js 的 http-server 包,或者用 VS Code 的 Live Server 插件。注意,部署到线上时,确保 JSON 文件和 HTML 在同一个域名下,或者配置好 CORS 头。

4.4 坑四:节点标签太长导致图面混乱

现象:基因名或化合物名很长,标签直接盖住了整个图。

原因:没有对标签做截断或换行处理。

解决:在前端样式里设置 text-wrap 和 text-max-width。Cytoscape.js 支持 'text-wrap': 'wrap' 和 'text-max-width': 80 这样的样式,让长标签自动换行。如果还是太长,可以在数据里把 label 截断,比如只显示前 10 个字符,鼠标悬停时再显示完整名称。悬停显示可以用 cy.on('mouseover', 'node', ...) 实现,弹出一个 tooltip。

4.5 坑五:导出图片时背景透明或缺失

现象:导出的 PNG 图片背景是透明的,放到 Word 里变成黑色或看不清。

原因:cy.png() 默认导出透明背景。

解决:在导出时指定 bg 参数,比如cy.png({ full: true, scale: 2, bg: '#ffffff' })。这样背景就是白色,放到文档里不会出问题。如果你想要其他背景色,改成对应的十六进制色值即可。

5. 进阶技巧:让 KEGG 网络图从“能看”变成“好用”

前面把基本流程跑通了,但如果你想让这张图真正能用在报告或论文里,还需要一些进阶处理。第一个技巧是节点分组和聚类。KEGG 通路里的基因往往属于不同的功能模块,你可以根据 KO 编号或通路层级给节点打上分组标签,前端用不同形状或边框颜色区分。比如,属于同一代谢途径的节点用相同底色,属于调控因子的节点用菱形。这样一眼就能看出哪些基因是协同作用的。

第二个技巧是边权重的可视化。KEGG 的 relation 本身没有权重,但你可以结合自己的数据,比如基因表达差异倍数或代谢物浓度变化,给边加上权重。前端用边的粗细表示权重,粗线表示强关联,细线表示弱关联。实现上,在 JSON 的 edge 里加一个 weight 字段,前端样式里用 'width': 'data(weight)' 映射。注意,权重需要先做归一化,否则数值差异太大,线宽会失控。

第三个技巧是动态加载多个通路。如果你要比较多个通路的网络,可以做一个下拉菜单,切换时重新加载对应的 JSON 文件,销毁旧的图实例再创建新的。代码上,把图初始化逻辑封装成一个函数,接收数据源路径作为参数。切换时先调用 cy.destroy(),再重新初始化。这样可以避免内存泄漏。

第四个技巧是结合后端做实时查询。如果你的数据经常更新,可以写一个简单的后端接口,接收通路 ID,实时解析 KGML 并返回 JSON。前端用 fetch 调用接口,拿到数据后渲染。后端可以用 Flask 或 FastAPI,解析逻辑复用前面的 Python 脚本。这样你就不用手动下载 KGML 再转换了,整个流程自动化。

最后说一个我自己的习惯:每次做完图,我都会把 network.json 和生成它的 KGML 文件一起存档,并在文件名里带上日期和通路 ID。因为 KEGG 的数据会更新,半年后你再想复现同一张图,如果只存了图片,根本不知道当时用的是哪个版本的数据。从那以后我每次做 KEGG 网络图都强制走一遍“下载 KGML → 解析 JSON → 渲染 → 存档”的流程,虽然多花几分钟,但省掉了后面无数次的“这张图怎么来的”的追问。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询