简介:neovis.js 是一套基于 vis.js 构建的图形可视化方案,能够直接连接 Neo4j 实例读取实时数据,在浏览器中渲染交互式图网络,适合需要展示知识图谱、社交关系或社区聚类的前端开发者与数据可视化学习者。资源包共 34 个文件,以 12 个 js 源码与 5 个 html 示例为主,另含 md 说明、json 配置、map 映射、png 截图及 yml 工作流等,压缩后约 3.01MB,结构覆盖 src 核心模块、dist 打包产物、examples 示例页面与tests测试用例。功能上支持用户指定标签与属性、自定义 Cypher 查询填充、节点图片 URL、边厚度、社区聚类与节点大小等视觉映射,并可配置弹出窗口,安装方式涵盖 npm 与 CDN。目前已有 2602 人学习下载,读者可借助示例页面与测试代码快速理解数据接入、样式配置与渲染流程,为图数据库前端展示提供可复用的参考实现。
1. neovis.js 把 Neo4j 数据搬进浏览器:为什么值得做,谁该上手
很多团队把 Neo4j 装好、数据导完,neo4j浏览器里跑 Cypher 也顺,但一到“给业务同事看图谱”就卡住:要么截图,要么让对方装桌面客户端,要么自己从零写一套前端。neovis.js 解决的正是这个断层——它把 Neo4j 的查询结果直接映射成浏览器里的图形化可视化,底层用 vis.js 渲染,前端只认一个配置对象,不用手写节点和边的解析逻辑。适合谁?做知识图谱、风控关系、设备拓扑、组织架构这类“关系比属性更重要”的场景,前端只有一两个人、又不想引入重型图可视化框架的团队。它不追求炫技,追求的是“数据在 Neo4j,图在浏览器,中间少写胶水代码”。这一章先把边界划清楚,后面再动手。
2. neovis.js 的渲染链路:从 Cypher 结果到画布上的节点
2.1 它到底替你做了哪几件事
Neo4j 的查询返回的是记录流,每条记录里可能包含节点、关系、路径、属性。浏览器要画图,需要的是nodes数组和edges数组,每个元素还要有id、label、title这类字段。neovis.js 的核心价值就在这层转换:你给它一段 Cypher,它通过 Neo4j 的 HTTP 接口或 Bolt 接口取回结果,按你配置的labels和relationships规则,把图元素抽出来,再交给 vis.js 的Network去渲染。换句话说,它把“查询—解析—布局—交互”串成了一条默认链路,你只需要在配置里声明“哪些标签当节点、哪些关系当边、节点上显示哪个属性”。
这里有个容易混淆的点:neovis.js 不是 Neo4j 官方出品的浏览器插件,也不是数据库的一部分,它是一个独立的前端库。它依赖 Neo4j 的查询能力,但渲染完全在浏览器里完成。所以你的 Neo4j 必须允许来自浏览器所在域的连接,这一点在后面的避坑章节会重点讲。
2.2 最小可运行页面:一个 HTML 文件跑通
先不引入构建工具,用最朴素的方式验证链路。新建一个index.html,内容如下:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>neovis.js 最小示例</title> <!-- vis.js 是 neovis.js 的渲染底座,必须先引入 --> <script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script> <!-- neovis.js 本体 --> <script src="https://unpkg.com/neovis.js@2.1.0/dist/neovis.js"></script> <style> /* 容器必须有明确高度,否则画布高度为 0,图看不见 */ #graph { width: 100%; height: 600px; border: 1px solid #ddd; } </style> </head> <body> <div id="graph"></div> <script> // 配置对象是 neovis.js 的唯一入口 const config = { containerId: "graph", // Neo4j 连接信息,按你的实际环境改 serverUrl: "bolt://localhost:7687", serverUser: "neo4j", serverPassword: "your_password", // 初始查询:先拿少量数据验证链路 initialCypher: "MATCH (n)-[r]->(m) RETURN n, r, m LIMIT 25", // 节点样式:按标签匹配,caption 决定节点上显示什么 labels: { "Person": { caption: "name", size: 30, font: { size: 14, color: "#333" } }, "Movie": { caption: "title", size: 25 } }, // 关系样式:按类型匹配,caption 决定边上显示什么 relationships: { "ACTED_IN": { caption: "roles", thickness: 2 } } }; // 实例化后会自动执行 initialCypher 并渲染 const viz = new NeoVis.default(config); viz.render(); </script> </body> </html>这段代码的逻辑很直白:containerId指定画布挂载点,serverUrl指向 Neo4j 的 Bolt 端口,initialCypher是首次加载执行的查询,labels和relationships是样式映射表。参数上最需要留意的是caption——它对应节点或关系上的属性名,如果属性不存在,节点上就是空白,看起来像“图渲染失败”,其实是数据字段没对上。size和thickness控制视觉权重,数值越大越突出,但不要把所有节点都设成同样大小,否则关系密集的区域会糊成一团。
提示:如果你的 Neo4j 是 4.x 及以上,Bolt 默认端口是 7687;如果走 HTTP,端口是 7474,但 neovis.js 对 Bolt 的支持更稳定,优先用 Bolt。
2.3 用 npm 接入现有前端工程
真实项目里很少直接写 HTML,更多是在 Vue、React 或原生模块化工程里用。安装方式和引入方式如下:
# 安装 neovis.js 和它的渲染依赖 npm install neovis.js vis-network// 在模块里引入,注意 neovis.js 默认导出的是构造函数 import NeoVis from 'neovis.js'; import { DataSet } from 'vis-network'; const config = { containerId: 'graph', serverUrl: 'bolt://localhost:7687', serverUser: 'neo4j', serverPassword: 'your_password', initialCypher: 'MATCH (n:Person)-[r:KNOWS]->(m:Person) RETURN n, r, m LIMIT 50', labels: { Person: { caption: 'name', // 用 group 区分颜色,vis.js 会按 group 自动分配 group: 'person', size: 28 } }, relationships: { KNOWS: { caption: 'since', thickness: 1.5 } } }; const viz = new NeoVis(config); viz.render(); // 后续可以手动触发重新查询 // viz.reload();模块化引入时,vis-network的DataSet通常不需要你手动构造,neovis.js 内部会处理。但如果你要做自定义交互,比如点击节点后高亮邻居,就需要拿到viz实例暴露的network对象。参数上,serverPassword在前端明文出现是不可避免的,所以生产环境不要用管理员账号,应该建一个只读账号,并限制它只能访问特定标签和关系。
3. 查询与样式配置:让图按业务语义长出来
3.1 Cypher 写法的三个约束
neovis.js 对 Cypher 没有语法限制,但渲染效果受查询结果结构影响很大。第一条约束:查询必须返回图元素,也就是节点、关系或路径,不能只返回聚合值。比如RETURN count(n)不会画出任何东西。第二条约束:如果返回的是路径,neovis.js 能自动展开路径上的节点和关系;如果分别返回n和r,也能识别,但字段名要对应。第三条约束:LIMIT一定要加,浏览器渲染几千个节点就会明显卡顿,vis.js 的物理布局引擎在节点超过 500 个时就会开始“飘”。
一个常见的业务查询是“从一个节点出发,找它的多跳邻居”。这在 Neo4j 里用变长路径表达:
// 从指定节点出发,找 1 到 3 跳内的所有关系 MATCH path = (start:Person {name: '张三'})-[*1..3]-(neighbor) RETURN path LIMIT 100这条查询返回的是路径,neovis.js 会把路径上的所有节点和关系都画出来。参数*1..3控制跳数,跳数越大图越密,建议从 1..2 开始调。如果只想看特定类型的关系,把-[*1..3]-改成-[:KNOWS|WORKS_WITH*1..3]-,用竖线分隔关系类型。
3.2 样式映射表怎么配才不翻车
labels和relationships的配置项直接透传给 vis.js,所以 vis.js 支持的样式字段这里都能用。下面这张表列出最常用的几个,以及容易踩坑的地方:
| 配置项 | 作用 | 常见坑 |
|---|---|---|
caption | 节点/边上显示的文字 | 属性名写错时显示空白,不是报错 |
size | 节点半径 | 所有节点同尺寸时,密集区域无法区分主次 |
group | 分组,影响颜色 | 不设 group 时所有节点同色 |
font.size | 文字大小 | 设太大节点会重叠,设太小看不清 |
thickness | 关系线宽 | 超过 5 会显得笨重,建议 1~3 |
color | 自定义颜色 | 用十六进制字符串,不要用颜色名 |
一个实用的技巧是:用group按标签分组,让 vis.js 自动分配颜色,再用size按度数(连接数)动态调整。但 neovis.js 的配置是静态的,做不到按数据动态设 size。变通办法是在 Cypher 里把度数作为属性返回,然后在caption里拼出来,或者用viz.network在渲染后手动改节点样式。
3.3 交互事件:点击节点后做什么
neovis.js 暴露了network对象,可以绑定 vis.js 的事件。最常见的需求是点击节点后执行新的查询,把邻居展开:
const viz = new NeoVis(config); viz.render(); // 等渲染完成后绑定事件 viz.network.on('click', (params) => { if (params.nodes.length > 0) { const nodeId = params.nodes[0]; // 用节点 id 构造新查询,注意 id 是 Neo4j 内部 id const cypher = `MATCH (n)-[r]-(m) WHERE id(n) = ${nodeId} RETURN n, r, m LIMIT 30`; // 重新执行查询并更新画布 viz.renderWithCypher(cypher); } });这里的关键是id(n)拿到的是 Neo4j 内部节点 id,它在数据库生命周期内唯一,但重建数据库后会变。如果业务上需要稳定标识,应该用业务主键,比如MATCH (n {uid: 'xxx'})。renderWithCypher会清空当前画布再渲染新结果,如果希望叠加而不是替换,需要自己维护节点集合,用viz.network的DataSet手动增删。
4. 避坑与排查:连接、渲染、性能的五个血泪经验
4.1 现象:页面空白,控制台报 WebSocket 连接失败
原因:Neo4j 默认只监听localhost,浏览器从其他机器访问时,Bolt 端口 7687 连不上。或者 Neo4j 的neo4j.conf里dbms.default_listen_address没改。
解决:在neo4j.conf里设置dbms.default_listen_address=0.0.0.0,并确认防火墙放行 7687。如果走 HTTP,还要改dbms.connector.http.listen_address。改完重启 Neo4j。注意不要在生产环境直接暴露 7687 到公网,应该通过内网或反向代理限制来源。
4.2 现象:图渲染出来了,但所有节点都是灰色,没有文字
原因:labels里的标签名和数据库里的标签不一致,或者caption指定的属性不存在。neovis.js 匹配不到样式时,会用 vis.js 的默认样式,默认就是灰色无文字。
解决:先在 Neo4j 浏览器里跑MATCH (n) RETURN labels(n) LIMIT 10确认标签名,再跑MATCH (n:YourLabel) RETURN keys(n) LIMIT 1确认属性名。标签和属性都区分大小写。如果标签是动态的,可以用*作为通配,但样式会统一,失去区分度。
4.3 现象:节点超过 200 个后浏览器卡死,风扇狂转
原因:vis.js 的物理布局引擎默认开启,每新增一个节点都会重新计算受力,节点越多计算量越大。neovis.js 没有默认关闭物理布局。
解决:在配置里加physics: false,或者用stabilization限制迭代次数。关闭物理布局后节点位置需要手动指定或依赖初始布局,图会变成静态的,但交互流畅度大幅提升。另一个办法是在 Cypher 里严格LIMIT,先让用户看局部,再按需展开。
const config = { // ...其他配置 physics: false, // 关闭物理引擎,适合节点数多的场景 // 或者保留物理但限制稳定迭代 // stabilization: { iterations: 100 } };4.4 现象:查询返回了数据,但画布上只有节点没有关系
原因:Cypher 返回了节点和关系,但relationships配置里没有声明该关系类型,neovis.js 会忽略未配置的关系。或者查询只返回了节点,没有返回关系。
解决:检查relationships对象里是否包含查询中出现的关系类型。如果关系类型很多且不确定,可以先用MATCH ()-[r]->() RETURN DISTINCT type(r)列出所有类型,再决定哪些需要显示。如果查询本身只返回节点,改成MATCH (n)-[r]->(m) RETURN n, r, m。
4.5 现象:本地开发正常,部署到服务器后连不上 Neo4j
原因:浏览器端代码里的serverUrl写的是localhost,但用户访问的是服务器域名,浏览器会尝试连接用户自己的 localhost,而不是服务器的 Neo4j。
解决:把serverUrl改成服务器可访问的地址,比如bolt://your-server-ip:7687。如果前端通过 HTTPS 访问,Bolt 连接可能被浏览器拦截,因为混合内容策略。这种情况下需要给 Neo4j 配置 TLS,或者前端走 HTTP。更稳妥的做法是加一层后端代理,前端只调后端接口,由后端去连 Neo4j,避免把数据库凭据暴露在浏览器里。
5. 进阶:用参数化查询和增量渲染撑住真实业务
真实业务里,图不是一次性画完的,用户会不断展开、过滤、搜索。neovis.js 的renderWithCypher每次都会清空重画,体验上会有闪烁。更好的做法是维护一个DataSet,增量添加节点和关系。vis.js 的DataSet支持add和update,配合 neovis.js 暴露的network对象,可以做到只添加新节点而不重绘全图。
具体做法是:先用viz.render()完成初始渲染,然后拿到viz.network.body.data.nodes和viz.network.body.data.edges这两个 DataSet,后续查询结果手动解析成节点和边对象,调用nodes.add()和edges.add()。解析逻辑需要自己写,但换来的是流畅的增量体验。参数上要注意节点 id 必须唯一,Neo4j 的内部 id 可以直接用,但跨查询时要用业务主键做映射,避免重复添加。
另一个进阶点是参数化查询。neovis.js 的renderWithCypher只接受字符串,不支持参数对象,所以拼接 Cypher 时要注意转义。如果用户输入作为查询条件,必须做转义或白名单校验,否则就是 Cypher 注入。常见做法是用viz.renderWithCypher之前,把用户输入里的单引号和反斜杠替换掉,或者改用后端接口,由后端用参数化查询执行后再把结果传给前端渲染。
我自己的习惯是:任何要上生产的 neovis.js 页面,都不直接把数据库凭据放前端,而是加一个薄薄的后端层,前端只传查询标识和参数,后端执行 Cypher 并返回 JSON,前端再用 neovis.js 的renderWithCypher或手动 DataSet 渲染。这样既安全,又能在后端做缓存和限流。图谱可视化这件事,渲染只是冰山一角,数据权限和查询性能才是真正决定能不能上线的门槛。希望帮到你。
本文还有配套的精品资源,点击获取