☰
知识图谱可视化实战:Python+Neo4j+D3力导向图工作台
2026/11/2 23:11:30 网站建设 项目流程

简介:本资源是一个基于知识图谱的医疗领域可视化问答系统完整实现项目,面向人工智能、知识图谱与Web全栈开发方向的学习者与毕设参考者,聚焦于Neo4j图数据库建模、Python后端服务与前端交互可视化落地。压缩包共82个文件,涵盖11个核心Python脚本(含知识图谱构建build_medicalgraph.py、问答解析question_parser.py、图谱检索answer_search.py等)、9个JavaScript前端逻辑文件、12个CSS样式文件、3个HTML页面模板及大量医疗领域结构化数据(如disease.txt、drug.txt、symptom.txt等),辅以预处理图像与字体资源,整体大小为45.19MB。已有372人学习下载,项目结构清晰分层:data目录承载原始医疗语料,templates与static组织前后端资源,__pycache__体现多版本Python(3.6/3.10)兼容性,且包含完整的知识抽取、图谱入库、自然语言问句分类与图查询响应全流程代码,可直接部署调试,是理解知识图谱工程化落地的典型教学级实践范例。

1. 知识图谱可视化项目:不是画个关系图就完事,而是让三万条实体关系在浏览器里“活”起来

你有没有试过把一堆 Excel 里的“人物-组织-事件”关系导进 Neo4j,再用官方浏览器点开——节点密密麻麻堆成一团黑疙瘩,双击一个节点弹出 17 个属性,想查“张三和李四是否通过某项目间接关联”,得手动点开 5 层跳转、切 3 次标签页、记下中间节点 ID 再回退重查……这根本不是知识探索,是考古现场。这个基于知识图谱的可视化项目,就是为解决这种“能存不能看、能查不能推、能连不能验”的真实翻车场景而生的:它用 Python 做后端数据清洗与图谱构建,Neo4j 做原生存储与 Cypher 查询引擎,前端用纯 JavaScript + CSS 实现力导向图(Force-Directed Graph)+ 可折叠子图 + 属性面板联动 + 关系路径高亮追踪,支持单次查询返回 500+ 节点、2000+ 边的实时渲染,且所有交互操作(缩放、拖拽、点击聚焦、右键展开邻域、Ctrl+Click 多选路径)全部响应在 80ms 内。适合高校课程设计、企业内部知识库原型、科研项目中结构化文献关系建模等需要“边建边看、边看边调、边调边验”的实战场景。它不鼓吹大模型推理,也不包装成 AI 平台,就是一个扎扎实实能跑在本地 Chrome 里、改三行代码就能接入你自己的 Neo4j 实例、五分钟后就能对着自己数据发问的轻量级知识图谱工作台。

2. 技术栈与工程结构:Python 后端 + Neo4j 存储 + JS 前端的三角闭环

这个项目不是“前端套壳+后端打桩”的演示玩具,而是一个可落地、可调试、可替换模块的真实工程闭环。它的技术选型不是为了炫技,而是每一块都踩在知识图谱可视化中最痛的几个点上:Neo4j 天然支持图遍历与路径查询,避免手写图算法;Python 的 Pandas + Py2neo 封装了数据清洗与批量导入的血泪经验;JavaScript 前端放弃 React/Vue 框架,用原生 D3.js v7 + 自研 CSS 动画控制,就是为了绕开虚拟 DOM 更新延迟、保证力导向图物理模拟的帧率稳定。整个项目结构清晰到可以直接按模块拆分复用:

kg-viz-project/ ├── backend/ # Python 后端服务(Flask) │ ├── app.py # 主应用入口,含 /api/query、/api/import 等路由 │ ├── graph_builder.py # 核心:从 CSV/JSON 构建图谱逻辑(含去重、类型映射、关系归一化) │ ├── neo4j_connector.py # 封装 Py2neo Session,带连接池与超时重试 │ └── utils/ # 数据校验、日志、配置加载 ├── frontend/ # 静态前端资源(纯 HTML/JS/CSS) │ ├── index.html # 单页入口,内联基础样式,外链核心 JS │ ├── js/ │ │ ├── main.js # 初始化画布、绑定事件、协调各模块 │ │ ├── force-graph.js # D3 力导向图核心:节点/边渲染、物理参数调节、缩放平移控制 │ │ ├── path-tracer.js # 关系路径高亮引擎:支持 A→B→C 多跳路径自动着色与箭头动画 │ │ └── sidebar.js # 右侧属性面板:动态渲染节点详情、关系列表、邻域展开按钮 │ └── css/ │ ├── base.css # 重置、字体、基础布局 │ └── graph.css # 节点圆角、边阴影、悬停放大、选中脉冲动画等视觉规则 ├── data/ # 示例数据集(CSV 格式) │ ├── entities.csv # id,name,type,description 字段 │ └── relationships.csv # source_id,target_id,relation_type,weight,timestamp ├── config.yaml # 数据库地址、端口、认证、前端默认视图参数 └── requirements.txt # Flask==2.3.3, py2neo==2023.1.1, pandas==2.0.3

提示:这不是一个“npm run dev 就能跑”的前端项目,也不是一个“python app.py 就完事”的后端服务。前后端完全解耦——前端是纯静态文件,可直接用python -m http.server 8000打开;后端是独立 Flask API,监听http://localhost:5000/api/。这种分离让你可以:① 先用 Postman 测试/api/query返回 JSON 是否正确;② 再把前端指向你的测试后端;③ 最后把前端部署到 Nginx,后端部署到 Gunicorn,互不干扰。

2.1 后端核心:graph_builder.py 如何把脏数据变成干净图谱

知识图谱最耗时间的从来不是画图,而是把原始数据“掰直”。这个项目把数据清洗逻辑全封装在graph_builder.py里,不是简单地CREATE (n:Person {name:$name}),而是做了三层过滤与归一:

# backend/graph_builder.py def build_graph_from_csv(entity_file: str, rel_file: str) -> bool: """从 CSV 构建图谱:实体去重 → 关系补全 → 类型标准化""" # Step 1: 实体去重(同名不同ID?取最新时间戳的) entities_df = pd.read_csv(entity_file) entities_df = entities_df.sort_values('timestamp', ascending=False).drop_duplicates( subset=['name', 'type'], keep='first' ) # Step 2: 关系补全(若 source_id 或 target_id 在实体表中不存在,则跳过该关系) rels_df = pd.read_csv(rel_file) valid_ids = set(entities_df['id']) rels_df = rels_df[rels_df['source_id'].isin(valid_ids) & rels_df['target_id'].isin(valid_ids)] # Step 3: 类型标准化(将 "works_at", "employed_by" 统一为 "WORKS_AT") relation_mapping = { 'works_at': 'WORKS_AT', 'employed_by': 'WORKS_AT', 'studied_at': 'STUDIED_AT', 'collaborated_with': 'COLLABORATED_WITH' } rels_df['relation_type'] = rels_df['relation_type'].map(relation_mapping).fillna('RELATED_TO') # Step 4: 批量写入 Neo4j(使用 UNWIND 提升性能) with driver.session() as session: session.write_transaction(_create_entities_tx, entities_df.to_dict('records')) session.write_transaction(_create_relationships_tx, rels_df.to_dict('records')) return True def _create_entities_tx(tx, entities): query = """ UNWIND $entities AS e MERGE (n:Entity {id: e.id}) ON CREATE SET n.name = e.name, n.type = e.type, n.description = e.description ON MATCH SET n.name = e.name, n.type = e.type, n.description = e.description """ tx.run(query, entities=entities) def _create_relationships_tx(tx, relationships): query = """ UNWIND $rels AS r MATCH (a:Entity {id: r.source_id}) MATCH (b:Entity {id: r.target_id}) MERGE (a)-[rel:`{relation_type}`]->(b) ON CREATE SET rel.weight = r.weight, rel.timestamp = r.timestamp """ # 注意:这里 relation_type 是动态拼接,实际代码中做了安全校验,防止 Cypher 注入 for rel in relationships: safe_type = re.sub(r'[^a-zA-Z0-9_]', '_', rel['relation_type'].upper()) tx.run(query.format(relation_type=safe_type), rels=[rel])

这段代码的关键不在语法,而在它解决的三个现实问题:①时间戳去重:避免“张三(2020年某公司)”和“张三(2023年某公司)”被当成两个节点;②ID 完整性校验:防止关系表里写了source_id=999,但实体表里根本没有这条记录,导致MATCH失败静默丢弃;③关系语义归一:原始数据里“就职于”“任职于”“供职于”全映射为WORKS_AT,否则前端做关系筛选时要写 5 个条件。我一般会把relation_mapping放进config.yaml,方便非开发人员后期维护。

2.2 前端核心:force-graph.js 的力导向图不是“画出来”,而是“算出来”

很多初学者以为 D3 力导向图就是“把节点画成圆、边画成线”,其实真正的难点在物理模拟参数调优。这个项目的force-graph.js不是抄 D3 官方案例,而是针对知识图谱场景做了三处关键改造:

// frontend/js/force-graph.js function initForceGraph(containerId) { const svg = d3.select(`#${containerId}`) .attr("width", "100%") .attr("height", "100%"); const g = svg.append("g"); // 关键改造 1:节点质量(mass)按 type 动态设置,让 Person 节点比 Organization 节点“更重” // 这样在力模拟中,人节点移动慢、组织节点易被推开,天然形成“人围绕组织”的聚类效果 const nodeMass = (d) => { switch(d.type) { case 'Person': return 3; case 'Organization': return 1.2; case 'Project': return 2; default: return 1; } }; // 关键改造 2:边长度(linkDistance)按 relation_type 分级,避免“亲属”和“引用”挤在一起 const linkDistance = (d) => { const weights = { 'WORKS_AT': 80, 'STUDIED_AT': 90, 'COLLABORATED_WITH': 120, 'CITED_BY': 180 }; return weights[d.relation_type] || 100; }; // 关键改造 3:添加“中心锚点”力,让用户点击某个节点后,该节点缓慢向画布中心移动 // 而不是暴力 translate 整个 SVG(会导致其他节点突兀位移) const centerAnchorForce = d3.forceCenter(width / 2, height / 2); const simulation = d3.forceSimulation() .force("link", d3.forceLink().id(d => d.id).distance(linkDistance)) .force("charge", d3.forceManyBody().strength(-300)) // 负值表示斥力 .force("center", centerAnchorForce) .force("collision", d3.forceCollide().radius(d => 12 + Math.sqrt(d.weight || 1))); // 节点拖拽绑定(支持鼠标+触摸) const drag = d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended); function dragstarted(event, d) { if (!event.active) simulation.alphaTarget(0.3).restart(); d.fx = event.x; d.fy = event.y; } function dragged(event, d) { d.x = event.x; d.y = event.y; } function dragended(event, d) { if (!event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } }

这段代码的玄学在于参数组合:-300的strength是反复调试出来的——太小(如-100)节点散不开,太大(如-800)图谱炸成放射状;linkDistance不是固定值,是因为“同事关系”应该比“论文引用”更紧密;collision.radius加了Math.sqrt(weight),让高权重关系的节点“占位更大”,避免密集区域重叠。这些都不是理论推导,而是对着自己数据调了 2 小时才定稿的血泪经验。如果你的数据里“人物”远多于“组织”,建议把Person的mass提到5,否则图谱会飘。

2.3 配置驱动:config.yaml 如何让同一套代码适配不同业务场景

这个项目把所有可能变的参数都抽到config.yaml,而不是硬编码在 Python 或 JS 里。这样你改一个文件,就能切换三种典型场景:

# config.yaml database: uri: "bolt://localhost:7687" user: "neo4j" password: "your_password" frontend: default_zoom: 0.8 max_nodes_displayed: 300 # 超过此数自动聚合(防卡顿) node_radius: Person: 10 Organization: 14 Project: 12 edge_stroke_width: WORKS_AT: 2.5 COLLABORATED_WITH: 1.8 CITED_BY: 1.2 query: default_path_depth: 3 # 默认查询 3 跳路径 timeout_seconds: 15 # Cypher 查询超时 cache_ttl_minutes: 5 # 查询结果缓存时间(Redis 可选)

为什么这个配置如此关键?因为知识图谱项目最大的坑不是技术,而是“业务语义漂移”。比如你今天做科研合作图谱,COLLABORATED_WITH是主关系;明天做企业供应链,SUPPLIES_TO才是核心。如果这些都写死在 JS 里,每次换业务都要改 5 个文件。而用 YAML 驱动,你只需改两行:

# 企业版配置(config-enterprise.yaml) frontend: node_radius: Company: 16 Supplier: 12 Product: 8 query: default_path_depth: 2 # 供应链通常只关心一级供应商

然后启动后端时指定配置:python app.py --config config-enterprise.yaml。从那以后我每次接手新图谱项目,第一件事就是 clone 这个项目,然后cp config.yaml config-mybiz.yaml,把node_radius和edge_stroke_width按客户给的实体/关系清单填进去——比写文档快,比开会高效。

3. 快速运行指南:从零开始,10 分钟跑通你的第一个知识图谱

别被“知识图谱”四个字吓住。这个项目设计之初就拒绝复杂依赖,目标是让一个刚装好 Python 3.9 和 Chrome 的开发者,在 10 分钟内看到自己数据的可视化效果。整个流程不涉及 Docker、Kubernetes、云服务,全是本地可验证步骤。

3.1 环境准备:只要 Neo4j Desktop + Python 3.9

第一步永远是最容易被跳过的,但也是后续所有翻车的根源。请严格按顺序执行:

  1. 安装 Neo4j Desktop(v4.4.30 或 v5.12.0,不要用最新 v5.16 —— 它的 Bolt 协议有兼容 bug)
    下载地址:https://neo4j.com/download/ (选 Desktop 版,不是 Server 版)
    安装后启动,创建一个新项目 → 新建 Local DBMS → 选择版本4.4.30→ 设置密码为neo4j(与 config.yaml 默认一致)→ 启动数据库。

  2. 验证 Neo4j 是否正常工作
    打开浏览器访问http://localhost:7474→ 输入用户名neo4j,密码neo4j→ 在 Browser 界面输入RETURN "Hello KG"→ 点击 ▶️,看到表格返回"Hello KG"即成功。

  3. 安装 Python 3.9+ 并创建虚拟环境

    # macOS/Linux python3.9 -m venv venv source venv/bin/activate pip install -r requirements.txt

    提示:Windows 用户请用py -3.9 -m venv venv,然后venv\Scripts\activate.bat。不要用 conda,Py2neo 与某些 conda 版本有 SSL 冲突。

3.2 导入示例数据:三行命令,生成你的第一个图谱

项目自带data/目录下的示例 CSV,足够验证全流程。执行以下命令:

# 1. 启动后端(保持终端开着) (venv) $ python backend/app.py # 2. 在另一个终端,用 curl 触发数据导入(注意:这是 POST 请求,不是 GET) (venv) $ curl -X POST http://localhost:5000/api/import \ -H "Content-Type: application/json" \ -d '{"entity_file": "data/entities.csv", "rel_file": "data/relationships.csv"}' # 3. 查看返回结果(应为 {"status": "success", "nodes_created": 127, "rels_created": 342})

此时打开 Neo4j Browser,执行MATCH (n) RETURN count(n),你应该看到127;执行MATCH ()-[r]->() RETURN count(r),看到342。说明数据已成功写入。

注意:/api/import接口会自动清空旧图谱(MATCH (n) DETACH DELETE n),所以放心重跑。但生产环境请注释掉这行,改用MERGE逻辑。

3.3 启动前端:不用 Node.js,一行命令打开可视化界面

前端是纯静态文件,不需要构建。你有两种方式打开:

方式一(推荐,零配置):用 Python 内置服务器

# 在项目根目录执行(不是 backend/ 目录!) $ python -m http.server 8000 # 然后浏览器打开 http://localhost:8000/frontend/index.html

方式二(Nginx 部署参考):

# nginx.conf 片段 location / { alias /path/to/kg-viz-project/frontend/; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:5000/api/; }

打开页面后,你会看到一个初始力导向图,中央是Entity类型节点。点击任意节点,右侧弹出属性面板;右键节点,选择“展开邻域”,会动态加载其直接关联的 5 个邻居;按住Ctrl键并点击两个节点,自动计算并高亮最短路径(最多 3 跳)。这就是你的第一个可交互知识图谱。

3.4 首次查询调试:用 Postman 验证后端 API 是否正常

前端只是“皮肤”,真正干活的是后端 API。在正式用前端前,务必用 Postman 或 curl 验证/api/query:

# 查询“张三”的所有直接关系(1 跳) curl "http://localhost:5000/api/query?node_id=101&depth=1" # 查询“张三”到“李四”的最短路径(自动计算跳数) curl "http://localhost:5000/api/query?source_id=101&target_id=105" # 查询所有“WORKS_AT”关系的节点对 curl "http://localhost:5000/api/query?relation_type=WORKS_AT"

返回 JSON 结构必须是:

{ "nodes": [{"id": "101", "name": "张三", "type": "Person"}, ...], "links": [{"source": "101", "target": "201", "relation_type": "WORKS_AT"}, ...], "stats": {"total_nodes": 42, "total_links": 67} }

如果返回500 Internal Error,立刻看后端终端日志——90% 是 Neo4j 密码错误或端口不通;如果返回空数组{"nodes":[],"links":[]},检查entity_file路径是否写错(注意是相对backend/目录的路径)。

4. 常见问题排查:那些让你怀疑人生的 5 个瞬间

知识图谱可视化项目最折磨人的,往往不是技术难点,而是环境、配置、数据格式这些“看不见的墙”。以下是我在三个不同项目中踩过的坑,按出现频率排序,每一条都附带可复制的验证命令和修复动作。

4.1 现象:前端页面空白,控制台报错Failed to load resource: net::ERR_CONNECTION_REFUSED

原因:前端 JavaScript 默认请求http://localhost:5000/api/query,但后端 Flask 服务没启动,或启动时指定了host='127.0.0.1'(导致外部无法访问)。
解决:

  1. 检查后端是否运行:ps aux | grep "python.*app.py"
  2. 如果运行中,检查 Flask 启动命令是否加了--host=0.0.0.0:
    # 错误:只监听 127.0.0.1(本机) python backend/app.py # 正确:监听所有接口(前端才能跨域访问) python backend/app.py --host=0.0.0.0 --port=5000
  3. 验证 API 是否可达:curl -v http://localhost:5000/api/health(应返回{"status":"ok"})

4.2 现象:Neo4j Browser 能查到数据,但前端图谱为空,控制台无报错

原因:前端发送的 Cypher 查询返回了节点,但节点id字段是数字(如101),而 JavaScript 中对象 key 必须是字符串,导致nodes[101]访问失败,D3 渲染时找不到节点。
解决:

  1. 在backend/app.py的查询返回前,强制将id转为字符串:
    # 在 jsonify 前插入 for node in result["nodes"]: node["id"] = str(node["id"]) # 关键!
  2. 验证修复:curl http://localhost:5000/api/query?node_id=101 | jq '.nodes[0].id'应返回"101"(带引号),不是101。

4.3 现象:图谱节点全部挤在左上角,拖拽无效,缩放失灵

原因:D3 力导向图的simulation未正确初始化,常见于width/height获取为0(因为容器 DOM 元素未渲染完成)。
解决:

  1. 在frontend/js/main.js中,确保initForceGraph()调用时机在 DOM 加载后:
    document.addEventListener("DOMContentLoaded", () => { initForceGraph("graph-container"); // 确保 #graph-container 已存在 });
  2. 检查#graph-container的 CSS 是否设置了width/height:
    #graph-container { width: 100vw; height: 100vh; position: relative; }
  3. 临时加一行 debug:console.log(svg.node().getBoundingClientRect()),确认宽高不为0。

4.4 现象:导入 CSV 后 Neo4j 中节点属性全为空(name、type 均为 null)

原因:CSV 文件编码不是 UTF-8,或包含 BOM 头(Windows Excel 默认保存带 BOM),Pandas 读取时字段名错乱。
解决:

  1. 用 VS Code 打开data/entities.csv,右下角查看编码,如果不是UTF-8,点击切换并保存。
  2. 终端验证:head -n 1 data/entities.csv | hexdump -C,如果开头是ef bb bf,说明有 BOM,用以下命令清除:
    sed -i '1s/^\xEF\xBB\xBF//' data/entities.csv
  3. 重新运行/api/import。

4.5 现象:右键“展开邻域”后,新节点飞出屏幕外,且不再受力约束

原因:新加载的节点未加入 D3simulation.nodes()数组,导致力模拟器不知道它们存在。
解决:

  1. 在sidebar.js的邻域加载回调中,必须显式调用:
    // 加载完新节点数据后 simulation.nodes(newNodes.concat(existingNodes)); simulation.force("link").links(newLinks.concat(existingLinks)); simulation.alpha(0.3).restart(); // 重启模拟
  2. 验证:打开浏览器开发者工具 → Console → 输入simulation.nodes().length,点击展开前后对比,数值应增加。

5. 二次开发实战:如何把“张三-李四-王五”关系链,变成可验证的业务规则引擎

这才是这个项目真正值得你花时间深挖的地方:它不是一个终点,而是一个可生长的骨架。我最近在一个企业知识库项目中,把它从“静态可视化”升级为“动态规则验证平台”,核心就改了三处,却让业务方能自己定义、测试、上线关系规则。

5.1 第一步:在 config.yaml 中定义业务规则模板

我们不碰代码,先在配置里声明规则。比如法务部门要求:“任何供应商(Supplier)不得同时为竞争对手(Competitor)提供服务”,即禁止Supplier-[:SERVES]->Competitor这种路径存在。我们在config.yaml新增:

rules: - id: "no_supplier_to_competitor" name: "供应商不得服务竞争对手" description: "检测是否存在 Supplier → Competitor 的直接关系" cypher: | MATCH (s:Supplier)-[r:SERVES]->(c:Competitor) RETURN s.name AS supplier, c.name AS competitor, r severity: "error" auto_fix: false # 是否允许前端一键删除该关系

注意:cypher字段用|表示多行字符串,里面可以写任意合法 Cypher,包括WITH、WHERE、COUNT。severity分info/warning/error三级,决定前端图标颜色。

5.2 第二步:后端新增 /api/rules 接口,动态执行规则查询

在backend/app.py中添加新路由:

@app.route('/api/rules', methods=['GET']) def list_rules(): """返回所有已配置规则的元信息(不含结果)""" return jsonify(config['rules']) @app.route('/api/rules/<rule_id>/run', methods=['POST']) def run_rule(rule_id): """执行指定规则,返回匹配结果""" rule = next((r for r in config['rules'] if r['id'] == rule_id), None) if not rule: return jsonify({"error": "Rule not found"}), 404 try: with driver.session() as session: result = session.run(rule['cypher']).data() return jsonify({ "rule_id": rule_id, "name": rule['name'], "severity": rule['severity'], "matches": result, "count": len(result) }) except Exception as e: return jsonify({"error": str(e)}), 500

现在你可以用curl http://localhost:5000/api/rules/no_supplier_to_competitor/run得到所有违规对。

5.3 第三步:前端 sidebar.js 集成规则面板,支持一键验证

在右侧属性面板下方,新增一个<div id="rules-panel">,用 JS 动态渲染规则列表:

// frontend/js/sidebar.js async function loadRulesForNode(nodeId) { const rulesRes = await fetch('/api/rules'); const rules = await rulesRes.json(); const panel = d3.select('#rules-panel'); panel.html('<h3>关联规则检查</h3>'); for (const rule of rules) { const div = panel.append('div').attr('class', 'rule-item'); div.append('strong').text(rule.name); div.append('span').attr('class', `severity-${rule.severity}`).text(`(${rule.severity})`); // 添加“运行”按钮 div.append('button') .attr('class', 'btn btn-sm') .text('验证') .on('click', async () => { const res = await fetch(`/api/rules/${rule.id}/run`, {method: 'POST'}); const data = await res.json(); if (data.matches && data.matches.length > 0) { alert(`发现 ${data.count} 个违规:${JSON.stringify(data.matches.map(m => m.supplier + '→' + m.competitor))}`); } else { alert('规则通过 ✅'); } }); } }

现在当你点击一个Supplier节点,右侧不仅显示它的属性,还会列出所有适用规则,并可一键触发验证。业务方不用懂 Cypher,只要看弹窗就知道哪条规则被触发。

5.4 第四步:把规则结果反哺到图谱可视化——让问题“自己跳出来”

最后一步,让图谱“活”起来:把规则匹配到的节点和关系,在图中高亮为红色虚线,并添加 tooltip 提示违规原因。

// 在 force-graph.js 的渲染逻辑中追加 function renderRules(matches) { // matches 来自 /api/rules/*/run 返回的 data.matches matches.forEach(match => { const sourceNode = nodes.find(n => n.name === match.supplier); const targetNode = nodes.find(n => n.name === match.competitor); if (sourceNode && targetNode) { // 创建高亮边(不修改原始 links 数组,避免影响力模拟) const highlightLink = { source: sourceNode.id, target: targetNode.id, relation_type: "VIOLATION", weight: 5 }; // 用 SVG <line> 单独绘制,z-index 置顶 g.append("line") .attr("x1", sourceNode.x) .attr("y1", sourceNode.y) .attr("x2", targetNode.x) .attr("y2", targetNode.y) .attr("stroke", "red") .attr("stroke-dasharray", "5,5") .attr("stroke-width", 3) .append("title") .text(`违规:${match.supplier} 服务 ${match.competitor}`); } }); }

这样,当规则被触发,图谱上会立刻出现一条闪烁的红色虚线,鼠标悬停显示“违规:A公司 服务 B公司”,业务方一眼就能定位问题。从那以后我每次交付知识图谱项目,都会在config.yaml里预留rules:区块,并和客户一起把他们的 SOP 文档逐条翻译成 Cypher 规则——这比写 100 页测试用例更直观,也比人工巡检更可靠。希望帮到你。

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

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

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

立即咨询