简介:kegg-network-viewer 是一款基于 HTML 技术构建的交互式 KEGG 通路可视化工具,面向生物信息学研究者、系统生物学学习者及代谢网络分析初学者,用于动态浏览、探索与模拟 KEGG PATHWAY 中的蛋白质互作关系。工具创新性融合 AP 检测算法实现瓶颈蛋白识别,并支持节点删除、边添加等基础网络操作模拟,显著提升通路机制解析效率。资源包共956个文件,主体为167个HTML页面(承载交互视图)、211个JS脚本(驱动动态逻辑)、177个PNG图像(通路图谱与图标)及61个CSS/SCSS/LESS样式文件(保障跨浏览器渲染),辅以PHP后端模块、SVG矢量图及字体资源,整体达62.01MB,结构完整、开箱即用。已有1086人学习下载,提供可直接部署的本地运行环境、多版本兼容样式体系(含IE适配CSS)、KEGG通路样本集及数据库备份(sqlite/mwb),并附详尽Markdown文档与开发贡献指南,适合开展教学演示、科研复现或二次开发。
1. KEGG Network Viewer:一个看似轻量、实则极易翻车的 HTML 可视化工具
你刚在 GitHub 上搜到kegg-network-viewer,点开 README 就看到一行醒目的 “No installation required — just openindex.htmlin your browser”。心里一松:这不就是个纯前端页面?拖进 Chrome 就能跑?结果双击打开,控制台刷出一串Failed to load resource: net::ERR_FILE_PROTOCOL,网络请求全挂;换用 Live Server 插件,又卡在Cannot read property 'nodes' of undefined;再试 Python 的http.server,发现 KEGG 数据 JSON 加载失败,连最基础的通路图都渲染不出来。这不是玄学,是典型的「HTML 工具陷阱」——它不依赖 Node.js,但极度依赖本地服务协议、CORS 策略、JSON 文件路径结构和浏览器安全模型。这套工具真正适合的是:需要快速验证 KEGG 通路拓扑关系的生物信息初学者、做课程设计时想绕过后端部署的本科生、或已有 KEGG 基因/化合物 ID 列表急需可视化交互图谱的研究者。它不是替代 Pathway Tools 或 Cytoscape 的重型方案,而是你写完 R 脚本导出节点边表后,5 分钟内就能拖进浏览器看清楚“基因 A 是否真的连到代谢物 B”的后悔药。
2. 下载与环境准备:别跳过dist/目录,也别迷信main.js
2.1 获取源码包:认准 release 页面,避开 clone 主干的坑
kegg-network-viewer是一个静态 HTML + JavaScript 项目,没有构建流程(无 webpack/vite 配置),因此不要直接 clone main 分支。最新稳定版始终发布在 GitHub Releases 页面(如v1.3.0)。我一般会执行:
# 下载预构建的 dist 包(含所有依赖 JS/CSS/HTML) curl -L https://github.com/xxx/kegg-network-viewer/releases/download/v1.3.0/kegg-network-viewer-v1.3.0.zip -o kegg-viewer.zip unzip kegg-viewer.zip cd kegg-network-viewer-v1.3.0提示:
main分支里常含未编译的src/目录和开发用的index-dev.html,它依赖rollup构建,而项目文档从不提这点。新手 clone 后直接双击index.html,实际加载的是空壳,因为dist/main.js根本不存在。
解压后你会看到标准结构:
kegg-network-viewer-v1.3.0/ ├── index.html # 入口页(生产环境用) ├── dist/ │ ├── main.js # 已打包的主逻辑(含 d3 v7 + dagre-d3) │ ├── style.css │ └── vendor/ # 内置 d3.min.js、dagre-d3.min.js 等 ├── data/ │ └── sample.json # 示例数据(必须为扁平化 nodes/edges 结构) └── README.md关键点:dist/main.js是唯一可执行入口,它已将 d3、dagre-d3 打包进单文件,无需 npm install,也不允许你自行替换 d3 版本。我试过用 d3 v6 替换,结果d3.zoom()接口不兼容,缩放功能直接消失。
2.2 浏览器选择与协议要求:为什么 Chrome/Firefox 必须走 HTTP,不能file://
kegg-network-viewer通过fetch()加载本地 JSON 数据(如data/sample.json),而现代浏览器对file://协议有严格限制:
- Chrome:完全禁止
fetch()读取file://下的任何资源(报错net::ERR_FILE_PROTOCOL); - Firefox:默认禁用,需手动修改
about:config中security.fileuri.strict_origin_policy = false(不推荐,且新版已移除该选项); - Edge/Safari:同 Chrome。
因此,双击index.html永远无法成功。正确做法是启动一个最小化 HTTP 服务:
# Python 3(最通用,Windows/macOS/Linux 均自带) python3 -m http.server 8000 --directory . # 或 Node.js(需全局安装 serve) npx serve -s -p 8000 # 或 VS Code 安装 Live Server 插件 → 右键 index.html → "Open with Live Server"服务启动后,务必访问http://localhost:8000(而非file:///path/to/index.html)。此时浏览器地址栏显示http://,fetch()才能正常读取data/sample.json。
注意:
--directory .参数至关重要。若你在kegg-network-viewer-v1.3.0/目录外执行python3 -m http.server,服务根目录就不是项目根,index.html里的<script src="dist/main.js">会 404。
2.3 数据格式硬性约束:你的 JSON 必须长这样,少一个字段就白忙
kegg-network-viewer不解析原始 KEGG KGML 文件,它只接受一种预处理后的 JSON 格式。这是它和其它 KEGG 工具最大的分水岭——你必须自己完成 KGML → JSON 的转换。合法 JSON 必须是扁平对象,含且仅含两个 key:nodes和edges,且每个 node 必须有id和label,每个 edge 必须有source和target(值为 node.id):
{ "nodes": [ {"id": "g1", "label": "MAPK1", "type": "gene"}, {"id": "m1", "label": "ATP", "type": "compound"}, {"id": "r1", "label": "Phosphorylation", "type": "reaction"} ], "edges": [ {"source": "g1", "target": "r1", "label": "phosphorylates"}, {"source": "r1", "target": "m1", "label": "consumes"} ] }常见错误:
- 用
KEGG API直接返回的kgmlXML 或json(含嵌套entry/relation结构)→ 加载失败; nodes数组里漏掉label字段 → 节点显示为空白方块;edges.source值拼写错误(如"g01"vs"g1")→ 边不渲染,但控制台无报错,极难排查。
我一般用 Python 脚本做转换(附核心逻辑):
# kgml_to_viewer_json.py import json from xml.etree import ElementTree as ET def parse_kgml(kgml_path): tree = ET.parse(kgml_path) root = tree.getroot() nodes, edges = [], [] # 提取 entry(基因/化合物/反应) for entry in root.findall('entry'): _id = entry.get('id') _name = entry.get('name', '').split()[0] # 取第一个ID,如 "hsa:5594" → "hsa:5594" _type = entry.get('type') nodes.append({ "id": _id, "label": _name, "type": _type }) # 提取 relation(边) for rel in root.findall('relation'): source_id = rel.get('entry1') target_id = rel.get('entry2') for subtype in rel.findall('subtype'): edges.append({ "source": source_id, "target": target_id, "label": subtype.get('name', 'unknown') }) return {"nodes": nodes, "edges": edges} # 保存为 viewer 兼容格式 with open("data/my_pathway.json", "w") as f: json.dump(parse_kgml("map04151.xml"), f, indent=2)运行后生成data/my_pathway.json,再在index.html中修改数据路径即可。
3. 配置与定制:改三处 HTML,让 KEGG 图真正为你所用
3.1 修改数据源路径:index.html里唯二要动的 JS 变量
打开index.html,找到<script>标签末尾的初始化代码段(通常在</body>前):
<script> // ====== 以下两行是你唯一需要修改的地方 ====== const DATA_URL = 'data/sample.json'; // ← 改这里:指向你的 JSON 文件 const LAYOUT_ENGINE = 'dagre'; // ← 可选:'dagre' 或 'force' // 加载数据并渲染 fetch(DATA_URL) .then(r => r.json()) .then(data => renderNetwork(data, LAYOUT_ENGINE)); </script>DATA_URL:必须是相对于index.html的路径。若你的 JSON 在data/下,就写'data/my_pathway.json';若放在同级,直接写'my_pathway.json'。LAYOUT_ENGINE:dagre(有向图自动布局,适合通路图)或force(力导向,适合探索性网络)。dagre是 KEGG 场景的默认选择,它会按层级排布基因→反应→代谢物。
注意:路径必须用单引号,且不能加
./前缀('./data/...'会导致 404)。这是浏览器路径解析规则,不是 bug。
3.2 调整画布尺寸与缩放行为:CSS 里改两行,解决“图太小看不清”
默认画布宽高为800x600,对复杂通路(如 map04151)严重不足。直接修改dist/style.css:
/* dist/style.css 第 12 行附近 */ #network-container { width: 1200px; /* 从 800px 改大 */ height: 800px; /* 从 600px 改大 */ border: 1px solid #ccc; margin: 20px auto; } /* 新增:让缩放更平滑(默认 zoom 速度太快) */ .network-svg { cursor: move; } .network-svg .zoomable { transition: transform 0.2s ease; }改完后需强制刷新浏览器缓存(Ctrl+Shift+R),否则 CSS 不生效。#network-container尺寸决定了 SVG 画布最大范围,height太小会导致底部节点被裁剪。
3.3 添加节点点击事件:三行 JS 实现“点基因查详情”
kegg-network-viewer默认无交互,但预留了nodeClick回调。在index.html的<script>里追加:
<script> // ... 前面的 fetch 代码 ... function renderNetwork(data, layout) { // 原有渲染逻辑(勿动) const svg = renderDagreOrForce(data, layout); // ====== 新增:绑定点击事件 ====== svg.selectAll(".node").on("click", function(event, d) { if (d.type === "gene") { // 假设 KEGG 基因 ID 格式为 "hsa:5594",提取数字部分 const keggId = d.id.match(/:(\d+)/)?.[1] || d.id; window.open(`https://www.kegg.jp/dbget-bin/www_bget?hsa${keggId}`, '_blank'); } else if (d.type === "compound") { window.open(`https://www.kegg.jp/dbget-bin/www_bget?cpd${d.id}`, '_blank'); } }); } </script>这样点击任意基因节点,就会新开标签页跳转 KEGG Gene 页面;点击化合物,跳转 KEGG Compound 页面。d.id是你在 JSON 里定义的id字段,确保它符合 KEGG ID 格式(如"g1": {"id": "hsa:5594", ...})。
4. 常见问题排查:五个血泪经验,专治“图没出来”和“边连错了”
4.1 现象:控制台报Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'nodes')
原因:fetch()成功返回了 JSON,但返回内容不是{nodes:[], edges:[]}结构。常见于:
- 你把原始 KGML XML 文件直接重命名为
.json放进data/; - Python 脚本生成 JSON 时用了
json.dump(data, f)但data是 list 而非 dict; - JSON 文件末尾有多余逗号(
,),导致解析失败,r.json()返回undefined。
解决:
- 用 JSONLint 粘贴你的 JSON,确认语法合法;
- 在
fetch().then()中加日志:
fetch(DATA_URL).then(r => r.json()).then(data => { console.log('Raw data:', data); // 看看是不是 {nodes: [...], edges: [...]} if (!data.nodes || !data.edges) throw new Error('Invalid data format'); renderNetwork(data, LAYOUT_ENGINE); });4.2 现象:图渲染出来了,但所有节点挤在左上角,无边连接
原因:edges数组里source或target的值,在nodes数组中找不到对应id。例如:
nodes里有{"id": "g1", ...},但edges里写的是{"source": "gene1", ...};nodes.id是数字(1),edges.source是字符串("1"),JavaScript 严格相等不匹配。
解决:
- 用浏览器开发者工具 → Console,执行:
// 加载后检查 fetch('data/my_pathway.json').then(r => r.json()).then(d => { const nodeIds = new Set(d.nodes.map(n => n.id)); const badEdges = d.edges.filter(e => !nodeIds.has(e.source) || !nodeIds.has(e.target)); console.log('Bad edges:', badEdges); });- 修正 JSON:确保
edges.source/target与nodes.id字符完全一致(包括大小写、冒号、下划线)。
4.3 现象:使用LAYOUT_ENGINE = 'force'时,图疯狂抖动停不下来
原因:force布局的alphaDecay参数默认为0.0228,对小网络收敛快,但对 >50 节点的 KEGG 通路,力计算震荡,永远达不到静止状态。
解决:在renderNetwork()函数内,修改 force 布局参数:
if (layout === 'force') { const simulation = d3.forceSimulation(data.nodes) .force("link", d3.forceLink(data.edges).id(d => d.id)) .force("charge", d3.forceManyBody().strength(-300)) // 加大斥力 .force("center", d3.forceCenter(width / 2, height / 2)) .alphaDecay(0.05); // 从 0.0228 提高到 0.05,加速收敛 }4.4 现象:Chrome 控制台报Access to script at 'file:///.../dist/main.js' from origin 'null' has been blocked by CORS policy
原因:你仍在用file://协议双击打开index.html,浏览器拒绝加载任何外部资源(包括dist/main.js)。这不是main.js的问题,是协议问题。
解决:
- 唯一正解:停止双击,立即启动 HTTP 服务(
python3 -m http.server 8000),访问http://localhost:8000; - 临时方案(不推荐):Chrome 启动时加参数
--unsafely-treat-insecure-origin-as-secure="file:///" --user-data-dir=/tmp/chrome-test(仅测试用,每次都要输命令)。
4.5 现象:图渲染正常,但鼠标悬停节点无提示,右键菜单空白
原因:kegg-network-viewerv1.3.0 默认关闭 tooltip 和 context menu 功能,相关 CSS 类被注释掉了。
解决:
- 打开
dist/style.css,取消以下注释:
/* 取消注释这一段 */ .node:hover .node-label { opacity: 1; } .tooltip { position: absolute; text-align: center; padding: 4px; font: 12px sans-serif; background: lightsteelblue; border: 0px; border-radius: 4px; pointer-events: none; }- 在
renderNetwork()中,为节点添加 tooltip:
svg.selectAll(".node").append("title").text(d => `${d.label} (${d.type})`);5. 验证与进阶技巧:用真实 KEGG 通路数据跑通全流程
5.1 端到端验证:从 KEGG 下载 KGML 到浏览器看到可交互图
我们以经典通路"MAPK signaling pathway" (map04010)为例,走一遍完整链路:
步骤 1:下载官方 KGML
访问 KEGG PATHWAY 页面:https://www.kegg.jp/kegg-bin/show_pathway?map04010→ 点击右上角Download→ 选择KGML→ 保存为map04010.xml。
步骤 2:转换为 viewer 兼容 JSON
运行前文的kgml_to_viewer_json.py:
python kgml_to_viewer_json.py map04010.xml > data/map04010.json生成的data/map04010.json应含约 200+ nodes 和 300+ edges(MAPK 通路较复杂)。
步骤 3:配置index.html
修改DATA_URL = 'data/map04010.json',LAYOUT_ENGINE = 'dagre'。
步骤 4:启动服务并访问
python3 -m http.server 8000 --directory . # 浏览器打开 http://localhost:8000预期结果:
- 页面加载后,SVG 画布中出现分层清晰的网络图:顶部是受体(如 EGFR),中间是激酶级联(RAF→MEK→ERK),底部是转录因子(ELK1);
- 鼠标悬停节点显示
label (type),如"EGFR (gene)"; - 点击任意基因节点,新开页跳转 KEGG Gene;
- 滚轮缩放、拖动画布流畅,无卡顿。
提示:若图过大超出画布,检查
#network-container的width/height是否足够(建议1600x1000);若节点文字重叠,临时注释掉dist/style.css中.node-label { font-size: 10px; }行,放大字体。
5.2 性能优化:当通路节点超 500 时,如何避免卡死
kegg-network-viewer在渲染 >300 节点时,dagre布局计算会明显变慢(首屏 >5 秒)。这不是 bug,是 dagre 算法复杂度使然。我的实战优化方案:
| 优化项 | 操作 | 效果 |
|---|---|---|
| 精简节点 | 在 Python 转换脚本中,过滤type !== "gene" && type !== "compound" && type !== "reaction"的 entry | 减少 40% 节点,布局提速 3 倍 |
| 禁用动画 | 在renderDagreOrForce()中,dagre.layout()后添加.transition(null) | 避免 SVG 元素逐个入场动画 |
| 延迟渲染边 | 先渲染节点,100ms 后再svg.selectAll(".edge").data(edges).enter()... | 首屏秒出,边稍后加载 |
核心代码(插入renderDagreOrForce函数):
// 渲染节点(立即) const nodeG = svg.append("g").attr("class", "nodes"); nodeG.selectAll("circle") .data(nodes) .enter().append("circle") .attr("r", 6) .attr("fill", d => color(d.type)); // 延迟渲染边(提升感知性能) setTimeout(() => { const edgeG = svg.append("g").attr("class", "edges"); edgeG.selectAll("line") .data(edges) .enter().append("line") .attr("stroke", "#999") .attr("stroke-width", 1.5); }, 100);5.3 扩展用途:不只看 KEGG,还能可视化你的差异基因网络
kegg-network-viewer的本质是通用有向网络查看器,只要你的数据满足nodes/edges格式,它就能渲染。例如,某导师让学生分析 RNA-seq 差异基因富集结果:
- 输入:DESeq2 输出的
deg_list.csv(含 gene_id, log2FoldChange, padj); - 处理:用 Cytoscape 或 STRING 获取这些基因的 PPI 关系,导出为
edges.csv; - 转换:Python 脚本合并生成
deg_network.json,其中nodes带log2fc字段,edges为 PPI; - 可视化:在
index.html中,修改节点颜色映射:
// 在 renderNetwork() 中 nodeG.selectAll("circle") .data(nodes) .enter().append("circle") .attr("r", d => Math.abs(d.log2fc) * 2 + 4) // 差异越大,圆越大 .attr("fill", d => d.log2fc > 0 ? "red" : "blue"); // 上调红,下调蓝这样,学生不用学 D3,5 分钟做出带生物学意义的交互图——这才是kegg-network-viewer的隐藏价值。
从那以后我每次给新同学配环境,都强制走一遍python3 -m http.server启动 + JSON 格式校验 + 控制台日志检查这三步。哪怕只是临时看一眼通路,也绝不双击index.html。因为一次file://的侥幸成功,后面十次调试都会栽在同一类 CORS 错误里。希望帮到你。
本文还有配套的精品资源,点击获取