From a1080a4641b308a7baa6895a02200e91d332a158 Mon Sep 17 00:00:00 2001 From: Wenjie Zhang Date: Mon, 1 Sep 2025 20:04:05 +0800 Subject: [PATCH] =?UTF-8?q?perf(graph):=20=E4=BC=98=E5=8C=96=E5=9B=BE?= =?UTF-8?q?=E5=B8=83=E5=B1=80=E6=80=A7=E8=83=BD=E5=B9=B6=E6=94=B9=E8=BF=9B?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E6=9F=A5=E8=AF=A2=E7=AE=97=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 调整力导向图布局参数以加快收敛速度并减少节点重叠 重构节点查询逻辑,优先返回连通的子图结构 完善关系查询,确保双向关系的完整性 --- src/knowledge/graphbase.py | 130 +++++++++++++++++++++++++++++++----- web/src/views/GraphView.vue | 27 +++++++- 2 files changed, 139 insertions(+), 18 deletions(-) diff --git a/src/knowledge/graphbase.py b/src/knowledge/graphbase.py index f7ff8672..823bd15c 100644 --- a/src/knowledge/graphbase.py +++ b/src/knowledge/graphbase.py @@ -56,27 +56,111 @@ class GraphDatabase: return self.status == "open" or self.status == "processing" def get_sample_nodes(self, kgdb_name='neo4j', num=50): - """获取指定数据库的 num 个节点信息""" + """获取指定数据库的 num 个节点信息,优先返回连通的节点子图""" assert self.driver is not None, "Database is not connected" self.use_database(kgdb_name) def query(tx, num): - """Note: 这里只查询带有 Entity 标签的节点""" + """Note: 使用连通性查询获取集中的节点子图""" + # 首先尝试获取一个连通的子图 query_str = """ - MATCH (n:Entity)-[r]->(m:Entity) - RETURN - {id: elementId(n), name: n.name} AS h, - {type: r.type, source_id: elementId(n), target_id: elementId(m)} AS r, - {id: elementId(m), name: m.name} AS t - LIMIT $num - """ - results = tx.run(query_str, num=int(num)) - formatted_results = {'nodes': [], 'edges': []} + // 获取高度数节点作为种子节点 + MATCH (seed:Entity) + WITH seed, COUNT{(seed)-[]->()} + COUNT{(seed)<-[]-()} as degree + WHERE degree > 0 + ORDER BY degree DESC + LIMIT 5 - for item in results: - formatted_results['nodes'].extend([item['h'], item['t']]) - formatted_results['edges'].append(item['r']) + // 为每个种子节点控制连接数量,避免过度集中 + UNWIND seed as s + MATCH (s)-[*1..1]-(neighbor:Entity) + WITH s, neighbor, COUNT{(s)-[]->()} + COUNT{(s)<-[]-()} as s_degree + WITH s, s_degree, collect(neighbor) as neighbors + WITH s, s_degree, neighbors[0..toInteger($num * 0.1)] as limited_neighbors - return formatted_results + // 从邻居节点扩展到二跳节点,形成开枝散叶结构 + UNWIND limited_neighbors as neighbor + OPTIONAL MATCH (neighbor)-[*1..1]-(second_hop:Entity) + WHERE second_hop <> s + WITH s, limited_neighbors, neighbor, collect(DISTINCT second_hop)[0..2] as second_hops + + // 收集所有连通节点 + WITH collect(DISTINCT s) as seeds, + collect(DISTINCT neighbor) as neighbors, + reduce(acc = [], x IN collect(second_hops) | acc + x) as second_hop_nodes + WITH seeds + neighbors + second_hop_nodes as connected_nodes + + // 只使用连接的节点,不添加随机节点 + WITH connected_nodes[0..$num] as final_nodes + + // 获取这些节点之间的关系,避免双向边 + UNWIND final_nodes as n + MATCH (n)-[rel]-(m) + WHERE m IN final_nodes AND elementId(n) < elementId(m) + RETURN + {id: elementId(n), name: n.name} AS h, + {type: rel.type, source_id: elementId(n), target_id: elementId(m)} AS r, + {id: elementId(m), name: m.name} AS t + """ + + try: + results = tx.run(query_str, num=int(num)) + formatted_results = {'nodes': [], 'edges': []} + node_ids = set() + + for item in results: + h_node = item['h'] + t_node = item['t'] + + # 避免重复添加节点 + if h_node['id'] not in node_ids: + formatted_results['nodes'].append(h_node) + node_ids.add(h_node['id']) + if t_node['id'] not in node_ids: + formatted_results['nodes'].append(t_node) + node_ids.add(t_node['id']) + + formatted_results['edges'].append(item['r']) + + # 如果连通查询没有返回足够的结果,回退到原始查询 + if len(formatted_results['nodes']) < num // 2: + fallback_query = """ + MATCH (n:Entity)-[r]-(m:Entity) + WHERE elementId(n) < elementId(m) + RETURN + {id: elementId(n), name: n.name} AS h, + {type: r.type, source_id: elementId(n), target_id: elementId(m)} AS r, + {id: elementId(m), name: m.name} AS t + LIMIT $num + """ + fallback_results = tx.run(fallback_query, num=int(num)) + formatted_results = {'nodes': [], 'edges': []} + + for item in fallback_results: + formatted_results['nodes'].extend([item['h'], item['t']]) + formatted_results['edges'].append(item['r']) + + return formatted_results + + except Exception as e: + # 如果连通查询失败,使用原始查询作为备选 + logger.warning(f"Connected subgraph query failed, falling back to simple query: {e}") + fallback_query = """ + MATCH (n:Entity)-[r]-(m:Entity) + WHERE elementId(n) < elementId(m) + RETURN + {id: elementId(n), name: n.name} AS h, + {type: r.type, source_id: elementId(n), target_id: elementId(m)} AS r, + {id: elementId(m), name: m.name} AS t + LIMIT $num + """ + results = tx.run(fallback_query, num=int(num)) + formatted_results = {'nodes': [], 'edges': []} + + for item in results: + formatted_results['nodes'].extend([item['h'], item['t']]) + formatted_results['edges'].append(item['r']) + + return formatted_results with self.driver.session() as session: results = session.execute_read(query, num) @@ -352,17 +436,29 @@ class GraphDatabase: def query(tx, entity_name, hops, limit): try: query_str = """ - MATCH (n {name: $entity_name})-[r1]-(m1) + MATCH (n {name: $entity_name})-[r1]->(m1) RETURN {id: elementId(n), name: n.name} AS h, {type: r1.type, source_id: elementId(n), target_id: elementId(m1)} AS r, {id: elementId(m1), name: m1.name} AS t UNION - MATCH (n {name: $entity_name})-[r1]-(m1)-[r2]-(m2) + MATCH (n {name: $entity_name})-[r1]->(m1)-[r2]->(m2) RETURN {id: elementId(m1), name: m1.name} AS h, {type: r2.type, source_id: elementId(m1), target_id: elementId(m2)} AS r, {id: elementId(m2), name: m2.name} AS t + UNION + MATCH (m1)-[r1]->(n {name: $entity_name}) + RETURN + {id: elementId(m1), name: m1.name} AS h, + {type: r1.type, source_id: elementId(m1), target_id: elementId(n)} AS r, + {id: elementId(n), name: n.name} AS t + UNION + MATCH (m2)-[r2]->(m1)-[r1]->(n {name: $entity_name}) + RETURN + {id: elementId(m2), name: m2.name} AS h, + {type: r2.type, source_id: elementId(m2), target_id: elementId(m1)} AS r, + {id: elementId(m1), name: m1.name} AS t LIMIT $limit """ results = tx.run(query_str, entity_name=entity_name, limit=limit) diff --git a/web/src/views/GraphView.vue b/web/src/views/GraphView.vue index e7212335..5cedd5f7 100644 --- a/web/src/views/GraphView.vue +++ b/web/src/views/GraphView.vue @@ -314,9 +314,34 @@ const initGraph = () => { layout: { type: 'd3-force', preventOverlap: true, + // 优化布局性能参数 + alphaDecay: 0.08, // 增加衰减率,加快收敛速度 + alphaMin: 0.01, // 提高最小alpha值,更早停止模拟 + velocityDecay: 0.8, // 增加速度衰减,减少震荡 + iterations: 100, // 限制最大迭代次数 + // 力的配置 + force: { + // 中心力 - 将节点拉向中心 + center: { + x: 0.5, + y: 0.5, + strength: 0.1 + }, + // 排斥力 - 节点间相互排斥 + charge: { + strength: -300, // 负值表示排斥力 + distanceMax: 400 // 限制力的作用距离 + }, + // 链接力 - 连接的节点相互吸引 + link: { + distance: 100, // 理想链接距离 + strength: 0.8 // 链接强度 + } + }, collide: { radius: 40, - strength: 0.5, // 碰撞强度 + strength: 0.8, // 增加碰撞强度,减少重叠 + iterations: 3 // 限制碰撞检测迭代次数 }, }, node: {