KEGG Network Viewer 部署避坑指南:HTML 可视化工具的 CORS 与数据格式实战 简介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.js2.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_PROTOCOLFirefox默认禁用需手动修改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 srcdist/main.js会 404。2.3 数据格式硬性约束你的 JSON 必须长这样少一个字段就白忙kegg-network-viewer不解析原始 KEGG KGML 文件它只接受一种预处理后的 JSON 格式。这是它和其它 KEGG 工具最大的分水岭——你必须自己完成 KGML → JSON 的转换。合法 JSON 必须是扁平对象含且仅含两个 keynodes和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值拼写错误如g01vsg1→ 边不渲染但控制台无报错极难排查。我一般用 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, indent2)运行后生成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)); /scriptDATA_URL必须是相对于index.html的路径。若你的 JSON 在data/下就写data/my_pathway.json若放在同级直接写my_pathway.json。LAYOUT_ENGINEdagre有向图自动布局适合通路图或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; }改完后需强制刷新浏览器缓存CtrlShiftR否则 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 而非 dictJSON 文件末尾有多余逗号,导致解析失败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是数字1edges.source是字符串1JavaScript 严格相等不匹配。解决用浏览器开发者工具 → 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-securefile:/// --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()中为节点添加 tooltipsvg.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.pypython kgml_to_viewer_json.py map04010.xml data/map04010.json生成的data/map04010.json应含约 200 nodes 和 300 edgesMAPK 通路较复杂。步骤 3配置index.html修改DATA_URL data/map04010.jsonLAYOUT_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); // 上调红下调蓝这样学生不用学 D35 分钟做出带生物学意义的交互图——这才是kegg-network-viewer的隐藏价值。从那以后我每次给新同学配环境都强制走一遍python3 -m http.server启动 JSON 格式校验 控制台日志检查这三步。哪怕只是临时看一眼通路也绝不双击index.html。因为一次file://的侥幸成功后面十次调试都会栽在同一类 CORS 错误里。希望帮到你。本文还有配套的精品资源点击获取