资讯动态

kegg-network-viewer:纯前端KEGG网络可视化工具解析与二次开发指南

发布时间:2026/10/17 7:16:24 来源:尧图企业网站定制
简介KPV-KEGG PATHWAY VIEWER 是一套面向生物信息学与代谢通路研究者的在线可视化工具用于直观呈现 KEGG 代谢途径并支持交互操作。其特色在于结合 AP 检测算法对蛋白质进行分类帮助研究者快速识别网络中连接度最高的关键蛋白节点同时提供动态 HTML 可视化效果可模拟节点去除、边缘添加等基本操作适合需要分析通路瓶颈与蛋白互作关系的中高级用户。资源包共 956 个文件以 211 个 js 脚本、167 个 html 页面、61 个 css 与 42 个 less 样式文件构成前端主体另有 177 个 png、29 个 gif 等图像素材及 131 个 md 说明文档并包含 php、sql、sqlite 等后端与数据文件整体约 62.01MB目录结构完整便于二次开发与本地部署。目前已有 1086 人学习下载。通过该工具读者可获得一套可直接运行的 KEGG 网络可视化方案理解蛋白分类与网络拓扑分析思路并基于现有代码进行功能扩展或集成到自身研究流程中。1. 从一张画不出来的通路图说起kegg-network-viewer 能帮你做什么做过代谢通路或者基因功能富集分析的人大概都遇到过这种场景手里拿到一串基因或代谢物 ID想看看它们在 KEGG 网络里是怎么连起来的结果要么在网页版里一个个点要么导出一堆文本自己用 Cytoscape 画来回折腾半天图还没画明白。kegg-network-viewer 就是冲着这个痛点来的——它是一个基于 HTML 的可视化工具把 KEGG 通路里的基因、化合物、反应关系解析成网络结构然后在浏览器里直接渲染出来。你不需要装桌面软件也不需要写复杂的绘图脚本打开页面、导入数据、调参数网络图就出来了。适合做生信分析、代谢组学、转录组数据解读的开发者也适合想快速验证通路关系的研究人员。它本质上是一个前端驱动的网络可视化项目核心逻辑跑在浏览器里部署和二次开发的门槛都不高。2. 技术栈与目录结构一个纯前端项目是怎么搭起来的2.1 为什么选 HTML JavaScript 做网络可视化KEGG 网络可视化的核心需求是交互——缩放、拖拽、节点高亮、边筛选这些操作如果放在后端渲染每次交互都要发请求体验会很差。kegg-network-viewer 选择把渲染逻辑放在前端用 HTML 做页面骨架JavaScript 处理数据解析和图形绘制CSS 控制样式。这种架构的好处是部署简单一个静态文件服务器就能跑起来不需要 Node.js 后端或者 Python 服务。常见做法是用 D3.js 或者 Cytoscape.js 这类图论库来处理力导向布局kegg-network-viewer 大概率也是走这个路线因为纯手写 Canvas 或 SVG 的力导向算法成本太高而且交互细节很难做全。从项目标题和关键词来看HTML 是核心技术标签说明这个项目的入口是一个 HTML 文件可能配套若干 JavaScript 模块和 CSS 文件。这种结构对二次开发很友好——你想改布局算法找到对应的 JS 文件改就行想调样式直接改 CSS想加新的数据源在数据解析层加一个适配器。2.2 目录结构推演与关键文件说明虽然没有拿到完整的源码包但按照这类前端可视化项目的常见组织方式目录结构大概率是这样的kegg-network-viewer/ ├── index.html # 主页面包含容器和脚本引用 ├── css/ │ └── style.css # 全局样式控制画布、工具栏、图例 ├── js/ │ ├── main.js # 入口脚本初始化画布和事件绑定 │ ├── parser.js # KEGG 数据解析把文本转成节点和边 │ ├── layout.js # 布局算法力导向或层次布局 │ ├── renderer.js # 渲染逻辑绘制节点、边、标签 │ └── interaction.js # 交互处理缩放、拖拽、点击高亮 ├── data/ │ └── sample.json # 示例数据方便快速验证 └── lib/ └── d3.min.js # 第三方图论库这个结构里index.html是入口main.js负责串联各个模块parser.js是最需要关注的部分——KEGG 的数据格式有它自己的规范比如通路里的基因用 K 编号表示化合物用 C 编号表示反应关系用 R 编号表示解析的时候要把这些 ID 映射成网络里的节点把关系映射成边。layout.js决定图的布局效果力导向布局适合展示复杂网络层次布局适合展示有向的通路关系。renderer.js和interaction.js负责把数据画出来并让用户能操作。提示如果你拿到的源码目录和上面不完全一致不用慌先找index.html看它引用了哪些 JS 文件顺着引用关系就能理清模块划分。2.3 数据流的走向从 KEGG 文本到屏幕上的网络理解数据流是二次开发的前提。kegg-network-viewer 的数据流大致分四步第一步用户通过文件上传或者粘贴的方式把 KEGG 数据导入页面第二步parser.js解析数据提取节点列表和边列表节点可能包含 ID、类型、名称等属性边包含源节点、目标节点、关系类型第三步layout.js根据节点和边计算每个节点的坐标第四步renderer.js根据坐标把节点和边画到画布上interaction.js绑定鼠标事件实现缩放和拖拽。这个流程里解析和布局是最容易出问题的环节。KEGG 的数据格式有时候会有嵌套结构比如一个通路里包含多个子通路解析的时候要决定是展开还是折叠。布局算法对节点数量敏感节点超过几百个之后力导向布局会变得很慢需要做性能优化比如限制迭代次数或者用 Web Worker 把计算放到后台线程。3. 核心模块拆解解析、布局、渲染三段式3.1 数据解析模块把 KEGG 文本变成节点和边解析模块的任务是把 KEGG 的原始数据转成图结构。KEGG 的通路数据通常以制表符分隔的文本形式存在每行描述一个关系比如基因和通路的关系、化合物和反应的关系。下面是一个简化的解析函数示例// parser.js function parseKeggData(rawText) { const lines rawText.trim().split(\n); const nodes new Map(); // 用 Map 去重key 是节点 ID const edges []; lines.forEach(line { // KEGG 数据常见格式源ID \t 关系类型 \t 目标ID const parts line.split(\t); if (parts.length 3) return; // 跳过不合规的行 const [sourceId, relation, targetId] parts; // 注册源节点和目标节点如果已存在则跳过 if (!nodes.has(sourceId)) { nodes.set(sourceId, { id: sourceId, type: inferType(sourceId), label: sourceId }); } if (!nodes.has(targetId)) { nodes.set(targetId, { id: targetId, type: inferType(targetId), label: targetId }); } // 添加边记录关系类型 edges.push({ source: sourceId, target: targetId, relation }); }); return { nodes: Array.from(nodes.values()), edges }; } // 根据 ID 前缀推断节点类型 function inferType(id) { if (id.startsWith(K)) return gene; // K 编号通常是基因 if (id.startsWith(C)) return compound; // C 编号通常是化合物 if (id.startsWith(R)) return reaction; // R 编号通常是反应 if (id.startsWith(map)) return pathway; // map 开头是通路 return unknown; }这段代码的逻辑很直接逐行读取按制表符切分把源节点和目标节点注册到 Map 里同时记录边。inferType函数根据 ID 前缀判断节点类型这个规则来自 KEGG 的编号习惯K 开头是基因C 开头是化合物R 开头是反应map 开头是通路。参数方面rawText是原始文本nodes用 Map 是为了去重edges用数组是因为边通常不需要去重除非你要做多重边合并。实际项目中解析模块可能还需要处理更复杂的情况比如 KEGG 的 KGML 格式XML 结构那就需要引入 XML 解析器把entry和relation标签提取出来。常见做法是用浏览器内置的DOMParser不需要额外依赖。3.2 布局模块力导向算法的参数怎么调布局模块决定节点在画布上的位置。力导向布局是最常用的方案它的核心思想是模拟物理系统节点之间有斥力边有引力经过多次迭代后系统趋于稳定节点位置就确定了。下面是一个简化的力导向布局实现// layout.js function forceLayout(nodes, edges, options {}) { const { width 800, height 600, iterations 300, // 迭代次数越大越稳定但越慢 repulsion 5000, // 斥力强度越大节点越分散 attraction 0.01, // 引力强度越大边越短 damping 0.9 // 阻尼系数控制收敛速度 } options; // 初始化节点位置随机散布在画布内 nodes.forEach(node { node.x Math.random() * width; node.y Math.random() * height; node.vx 0; node.vy 0; }); for (let i 0; i iterations; i) { // 计算斥力所有节点两两之间 nodes.forEach(a { nodes.forEach(b { if (a b) return; const dx a.x - b.x; const dy a.y - b.y; const dist Math.sqrt(dx * dx dy * dy) || 1; const force repulsion / (dist * dist); a.vx (dx / dist) * force; a.vy (dy / dist) * force; }); }); // 计算引力有边相连的节点之间 edges.forEach(edge { const source nodes.find(n n.id edge.source); const target nodes.find(n n.id edge.target); if (!source || !target) return; const dx target.x - source.x; const dy target.y - source.y; source.vx dx * attraction; source.vy dy * attraction; target.vx - dx * attraction; target.vy - dy * attraction; }); // 更新位置并应用阻尼 nodes.forEach(node { node.vx * damping; node.vy * damping; node.x node.vx; node.y node.vy; // 限制在画布范围内 node.x Math.max(0, Math.min(width, node.x)); node.y Math.max(0, Math.min(height, node.y)); }); } return nodes; }这段代码里repulsion控制节点之间的排斥力值越大节点越分散适合节点密集的网络attraction控制边的引力值越大有边相连的节点越靠近damping是阻尼系数防止节点震荡一般取 0.8 到 0.95 之间iterations是迭代次数节点少的时候 100 次就够节点多的时候可能要 500 次以上。实际项目中如果节点超过 200 个这个 O(n²) 的斥力计算会明显变慢常见优化是用四叉树Quadtree把斥力计算降到 O(n log n)D3.js 的d3-force模块就是这么做的。注意力导向布局每次运行结果都不一样因为初始位置是随机的。如果你需要可复现的布局可以在初始化时用固定种子生成伪随机数或者把第一次计算好的坐标存下来复用。3.3 渲染模块用 SVG 还是 Canvas渲染模块把布局好的节点和边画到页面上。前端可视化有两个主流选择SVG 和 Canvas。SVG 的优点是每个节点和边都是 DOM 元素可以直接绑定事件样式用 CSS 控制调试方便缺点是节点多了之后 DOM 数量爆炸性能下降明显。Canvas 的优点是绘制性能好适合大规模网络缺点是没有 DOM 结构事件处理需要自己算坐标样式控制也没那么灵活。kegg-network-viewer 作为 HTML 项目大概率用的是 SVG因为 KEGG 通路网络的节点数量通常不会特别大几十到几百个SVG 的性能足够而且交互实现更简单。下面是一个 SVG 渲染的简化示例// renderer.js function renderNetwork(svgElement, nodes, edges) { const svg d3.select(svgElement); svg.selectAll(*).remove(); // 清空画布 // 绘制边 svg.selectAll(line) .data(edges) .enter() .append(line) .attr(x1, d nodes.find(n n.id d.source).x) .attr(y1, d nodes.find(n n.id d.source).y) .attr(x2, d nodes.find(n n.id d.target).x) .attr(y2, d nodes.find(n n.id d.target).y) .attr(stroke, #999) .attr(stroke-width, 1); // 绘制节点 svg.selectAll(circle) .data(nodes) .enter() .append(circle) .attr(cx, d d.x) .attr(cy, d d.y) .attr(r, 8) .attr(fill, d { // 按节点类型着色 const colors { gene: #4CAF50, compound: #2196F3, reaction: #FF9800, pathway: #9C27B0 }; return colors[d.type] || #999; }) .attr(stroke, #fff) .attr(stroke-width, 2); // 添加节点标签 svg.selectAll(text) .data(nodes) .enter() .append(text) .attr(x, d d.x) .attr(y, d d.y - 12) .attr(text-anchor, middle) .attr(font-size, 10px) .text(d d.label); }这段代码用 D3.js 的 data join 模式先绑定数据再 enter 创建元素最后设置属性。边的坐标从节点坐标里查节点的颜色按类型区分基因用绿色化合物用蓝色反应用橙色通路用紫色。标签放在节点上方字体大小 10px避免遮挡。参数方面svgElement是页面上的 SVG 容器nodes和edges是布局后的数据。实际项目中渲染模块可能还需要处理节点选中状态、边的高亮、图例的生成这些都可以在渲染函数里加逻辑。4. 跑起来本地部署与数据导入的完整步骤4.1 环境准备与静态服务器启动kegg-network-viewer 是纯前端项目不需要安装 Node.js 或者 Python 环境但你需要一个静态文件服务器来托管文件因为浏览器直接打开index.html时某些浏览器会因为跨域策略限制加载本地 JS 文件。最简单的办法是用 Python 自带的 HTTP 服务器# 进入项目目录 cd kegg-network-viewer # Python 3 启动静态服务器端口 8000 python3 -m http.server 8000如果你习惯用 Node.js也可以用npx serve# 在当前目录启动静态服务器 npx serve .启动之后浏览器打开http://localhost:8000应该能看到页面。如果页面空白按 F12 打开开发者工具看 Console 有没有报错常见问题是 JS 文件路径写错了或者依赖库没加载成功。4.2 导入 KEGG 数据并调整视图页面加载后通常会有一个文件上传按钮或者一个文本输入框。你可以把 KEGG 导出的通路数据粘贴进去或者上传一个.txt或.json文件。数据格式方面如果是制表符分隔的文本每行应该是「源ID \t 关系 \t 目标ID」如果是 JSON应该包含nodes和edges两个数组。导入数据后页面会自动触发布局计算和渲染。如果节点重叠严重可以调大斥力参数如果图太散可以调小斥力或者调大引力。有些版本会提供滑块或者输入框让你实时调参如果没有就需要改layout.js里的默认值然后刷新页面。提示第一次跑的时候建议先用项目自带的示例数据通常在data/sample.json确认流程通了再换自己的数据。这样出问题的时候容易定位是数据格式的问题还是代码的问题。4.3 常见运行问题与排查思路跑不起来的情况大概分三类第一类是页面完全空白通常是 JS 报错看 Console 的红色错误信息大概率是某个变量未定义或者文件路径不对第二类是页面有元素但图没画出来可能是数据解析失败检查数据格式是否符合预期比如分隔符是不是制表符而不是空格第三类是图出来了但交互没反应可能是事件绑定失败检查interaction.js是否加载或者 SVG 的层级是不是被其他元素挡住了。如果布局计算特别慢节点超过 300 个的时候可以考虑把布局计算放到 Web Worker 里避免阻塞主线程。具体做法是新建一个worker.js把forceLayout函数放进去主线程通过postMessage发送数据Worker 计算完再把坐标传回来。5. 避坑指南解析、布局、渲染里最容易翻车的地方5.1 数据格式不统一导致解析失败现象导入数据后页面没反应Console 报Cannot read property split of undefined或者解析出来的节点数量明显不对。原因KEGG 的数据格式有多种有的是制表符分隔有的是空格分隔有的是 XML。解析函数如果只按一种格式写换一种数据就挂了。解决在解析函数开头做格式检测先判断是 XML 还是纯文本再判断分隔符是制表符还是逗号。常见做法是用正则表达式匹配第一行的特征或者让用户手动选择数据格式。5.2 力导向布局节点重叠严重现象图是画出来了但节点挤在一起标签互相遮挡根本看不清。原因斥力参数太小或者迭代次数不够系统还没稳定就停止了。解决把repulsion调大比如从 5000 调到 10000把iterations从 300 调到 500。如果节点特别多可以考虑用聚类布局先把同一通路的节点聚在一起再在簇内做力导向。5.3 SVG 节点过多导致页面卡顿现象节点超过 500 个之后拖拽和缩放明显卡顿浏览器风扇狂转。原因SVG 的每个节点和边都是 DOM 元素DOM 数量太多浏览器的重绘和重排开销大。解决节点超过 300 个时考虑切换到 Canvas 渲染或者用虚拟化技术只渲染视口内的节点。另一个思路是聚合节点把同一类型的多个节点合并成一个点击再展开。5.4 事件绑定丢失导致交互失效现象图能画出来但点击节点没反应拖拽也拖不动。原因渲染函数每次重新绘制时会清空 SVG 内容svg.selectAll(*).remove()之前绑定的事件监听器也被清掉了。解决把事件绑定放在渲染之后或者用事件委托把监听器绑在 SVG 容器上通过event.target判断点的是哪个节点。D3.js 的on方法在 data join 之后调用可以保证每次渲染都重新绑定。5.5 跨域问题导致数据加载失败现象用fetch或d3.json加载本地数据文件时Console 报 CORS 错误。原因浏览器直接打开index.html时file://协议下的跨域策略会阻止加载本地文件。解决用静态服务器启动项目不要直接双击index.html。如果必须用file://协议可以把数据内联到 JS 文件里或者用script标签加载 JSONP 格式的数据。6. 二次开发换布局、加数据源、做导出6.1 替换布局算法从力导向到层次布局力导向布局适合展示无向网络但 KEGG 通路很多时候是有向的——基因调控基因化合物参与反应方向很重要。如果你想换成层次布局可以引入 Dagre.js 或者自己写一个简单的分层算法。下面是一个基于拓扑排序的层次布局示例// layout-hierarchy.js function hierarchyLayout(nodes, edges, options {}) { const { width 800, height 600, layerGap 100 } options; // 计算每个节点的入度 const inDegree new Map(); nodes.forEach(n inDegree.set(n.id, 0)); edges.forEach(e { inDegree.set(e.target, (inDegree.get(e.target) || 0) 1); }); // 拓扑排序分层 const layers []; const visited new Set(); let currentLayer nodes.filter(n inDegree.get(n.id) 0).map(n n.id); while (currentLayer.length 0) { layers.push(currentLayer); currentLayer.forEach(id visited.add(id)); const nextLayer []; currentLayer.forEach(id { edges.filter(e e.source id).forEach(e { if (!visited.has(e.target) !nextLayer.includes(e.target)) { // 检查目标节点的所有前驱是否都已访问 const allPredsVisited edges .filter(edge edge.target e.target) .every(edge visited.has(edge.source)); if (allPredsVisited) nextLayer.push(e.target); } }); }); currentLayer nextLayer; } // 按层分配坐标 layers.forEach((layer, layerIndex) { const y (layerIndex 1) * layerGap; layer.forEach((id, nodeIndex) { const node nodes.find(n n.id id); if (node) { node.x (nodeIndex 1) * (width / (layer.length 1)); node.y y; } }); }); return nodes; }这个算法的逻辑是先算入度入度为 0 的节点放第一层然后逐层往下推每层节点的 y 坐标固定x 坐标均匀分布。参数layerGap控制层间距width控制横向分布范围。这种布局适合展示有向无环图如果图里有环需要先做环检测和断环处理。6.2 接入新的数据源从 KEGG 到自定义网络kegg-network-viewer 默认解析 KEGG 格式但你可以扩展解析模块支持更多数据源。比如你想接入一个 CSV 文件每行是「基因A, 基因B, 关系类型」只需要在parser.js里加一个分支function parseCSV(rawText) { const lines rawText.trim().split(\n); const nodes new Map(); const edges []; lines.forEach((line, index) { if (index 0 line.includes(source)) return; // 跳过表头 const [source, target, relation] line.split(,).map(s s.trim()); if (!source || !target) return; if (!nodes.has(source)) nodes.set(source, { id: source, type: gene, label: source }); if (!nodes.has(target)) nodes.set(target, { id: target, type: gene, label: target }); edges.push({ source, target, relation: relation || unknown }); }); return { nodes: Array.from(nodes.values()), edges }; }然后在主入口根据文件扩展名或者用户选择调用不同的解析函数。这种适配器模式的好处是核心的布局和渲染逻辑不用改只加解析层就行。6.3 导出功能把网络图存成图片或 JSON可视化工具如果没有导出功能用起来会很别扭。导出图片可以用 Canvas 的toDataURL方法但 SVG 需要先转成 Canvas 再导出。常见做法是用html2canvas库或者手动把 SVG 序列化成字符串再用Blob下载function exportSVG(svgElement, filename network.svg) { const serializer new XMLSerializer(); const svgString serializer.serializeToString(svgElement); const blob new Blob([svgString], { type: image/svgxml }); const url URL.createObjectURL(blob); const link document.createElement(a); link.href url; link.download filename; link.click(); URL.revokeObjectURL(url); }导出 JSON 更简单直接把nodes和edges序列化就行方便下次导入时恢复现场。如果你想让导出的 JSON 包含布局坐标记得在导出前把x和y属性也带上。6.4 一个我踩过的坑布局参数硬编码刚开始改这个项目的时候我把斥力和引力参数直接写死在layout.js里结果换了一批数据节点数量从 50 变成 200图直接糊成一团。后来我把参数抽出来放到一个配置对象里并且在页面上加了两个滑块让用户可以实时调。再后来我发现不同规模的网络需要不同的参数策略节点少的时候斥力小一点节点多的时候斥力大一点于是又加了一个自动计算初始参数的逻辑repulsion 5000 nodes.length * 50。从那以后我每次接新的可视化需求都强制把布局参数做成可配置的并且给一个根据数据规模自动调整的默认值。希望这个习惯能帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑