
【open harmony/harmonyos】ArkTS 实现知识图谱节点连接与关系高亮前言 知识图谱类应用最核心的能力之一就是表达“关系”。如果只有节点没有连线那它更像一个标签集合只有当节点之间可以建立连接用户才能真正看到知识点之间的结构。这篇文章会结合我的 HarmonyOS / OpenHarmony 项目星图 Xingtu分享如何使用 ArkTS 实现知识图谱中的节点连接、去重、防误操作和关系高亮。主要内容包括 节点数据结构 关系边数据结构 两步式节点连接 防止重复连线✨ 选中节点关系高亮 删除节点时同步删除连线一、节点和边的数据模型项目中知识图谱由两类数据组成nodes节点数组edges连线数组节点保存内容和空间位置连线保存两个节点之间的关系。exportinterfaceXingtuNode{ id:string; title:string; note:string; tags:string[]; position: Vec3; } exportinterfaceXingtuEdge{ id:string; fromId:string; toId:string; }这种设计比较简单但非常适合第一版图谱应用节点本身不嵌套关系关系单独放在edges删除、遍历、渲染都比较清楚后续可以扩展关系类型、权重、方向等字段二、图谱状态集中到 Store项目中使用XingtuGraphStore管理所有图谱状态。exportclassXingtuGraphStore{nodes:XingtuNode[][];edges:XingtuEdge[][];selectedNodeId:string|nullnull;linkingSourceId:string|nullnull;camera:CameraStatedefaultCamera();}其中两个状态很关键selectedNodeId当前选中的节点linkingSourceId当前正在发起连接的起点节点这样可以把“选中”和“连线中”两个概念区分开。三、两步式节点连接交互 项目采用的是两步式连接用户选中一个节点点击“连接”再点击另一个节点系统生成连线这种方式比拖拽连线更适合移动端因为手机屏幕小拖拽线条容易误触。发起连接时只需要记录起点节点startLink(nodeId:string):void{this.linkingSourceId nodeId;this.selectedNodeId nodeId; }当用户点击第二个节点时再调用finishLink完成连接。四、完成连接并防止重复连线连线逻辑的核心在finishLink。finishLink(targetId: string): XingtuEdge |null{if(!this.linkingSourceId ||this.linkingSourceId targetId) {returnnull; }constexists: boolean this.edges.some((edge: XingtuEdge) (edge.fromId this.linkingSourceId edge.toId targetId) || (edge.fromId targetId edge.toId this.linkingSourceId) );if(exists) {this.linkingSourceId null;returnnull; }constedge: XingtuEdge { id: edge-${edgeSequence}, fromId:this.linkingSourceId, toId: targetId };this.edges [...this.edges, edge];this.linkingSourceId null;this.selectedNodeId targetId;returnedge; }这里做了几个保护没有起点时不创建连线起点和终点相同不创建连线已经存在的关系不重复创建连接成功后清空linkingSourceId连接成功后选中目标节点尤其是重复判断这一段很重要(edge.fromIdthis.linkingSourceIdedge.toId targetId) || (edge.fromId targetId edge.toIdthis.linkingSourceId)因为当前图谱关系是无向关系所以 A-B 和 B-A 应该被视为同一条连接。五、页面层如何触发连线在主界面中节点点击逻辑会判断当前是否处于连线状态。onNodeTap: (nodeId: string) {if(this.store.linkingSourceId this.store.linkingSourceId ! nodeId) {this.store.finishLink(nodeId); }else{this.store.selectNode(nodeId); }this.revision 1; }这段代码让同一个“点击节点”动作具备两种含义普通状态点击节点表示选中连线状态点击第二个节点表示完成连接这就是移动端交互中常见的“模式化操作”。只要提示清楚用户就能理解。六、连线中的状态提示节点详情弹层中如果正在连线会给用户提示if (this.linking) { Text(请再点一个节点完成连线).fontSize(12).fontColor(XingtuTheme.activeGlow) }连接按钮也会切换状态Button(this.linking ?连线中:连接) .enabled(!this.linking)这样用户点击“连接”后不会疑惑下一步要做什么。七、绘制节点之间的连线 ✨在场景组件中需要把edges转换成屏幕上的线条。项目先把投影后的节点放进Map方便通过 id 查找constprojectionMap:Mapstring,ProjectedNode newMapstring,ProjectedNode(); nodes.forEach((node: ProjectedNode) { projectionMap.set(node.id, node); });然后根据每条边的起点和终点计算线条constdx: number toNode.screenX -fromNode.screenX;constdy: number toNode.screenY -fromNode.screenY;constline: XingtuLineProjection { id: edge.id, x:fromNode.screenX, y:fromNode.screenY, width: Math.sqrt(dx * dx dy * dy), angle: Math.atan2(dy, dx) *180/ Math.PI, active: relatedIds.has(edge.fromId) relatedIds.has(edge.toId) };这里的width是线条长度angle是线条旋转角度。八、用 Row 绘制轻量连线真正渲染时可以用一个空的Row表示线条。Row() {} .width(line.width) .height(line.active ?2:1) .backgroundColor(line.active ? XingtuTheme.primaryAction :#66BFDBFE) .opacity(line.active ?0.82:0.32) .position({ x:line.x, y:line.y -1}) .rotate({ angle:line.angle })这种方式非常轻量不需要 Canvas 或复杂图形库。对于节点数量不大的知识星图来说用 ArkUI 普通组件也能做出不错的关系线效果。九、选中节点的关系高亮当用户选中一个节点时希望能看到它和哪些节点有关。项目中通过relatedNodeIds找到相关节点relatedNodeIds():Setstring {if(!this.selectedNodeId) {returnnewSetstring(); }constrelated:Setstring newSetstring([this.selectedNodeId]);this.edges.forEach((edge: XingtuEdge) {if(edge.fromIdthis.selectedNodeId) { related.add(edge.toId); }if(edge.toIdthis.selectedNodeId) { related.add(edge.fromId); } });returnrelated; }然后在计算连线时判断是否高亮active: relatedIds.has(edge.fromId) relatedIds.has(edge.toId)这样用户点中一个节点后和它有关的连线会更亮、更粗关系路径会立刻清晰起来。十、删除节点时同步删除连线 图谱中删除节点时不能只删除节点本身还要删除和它相关的所有边。deleteNode(nodeId: string): void {this.nodes this.nodes.filter((node: XingtuNode) node.id ! nodeId);this.edges this.edges.filter((edge: XingtuEdge) edge.fromId ! nodeId edge.toId ! nodeId );if(this.selectedNodeId nodeId) {this.selectedNodeId null; }if(this.linkingSourceId nodeId) {this.linkingSourceId null; } }这里也顺便清理了选中状态和连线状态避免出现“选中了一个已经不存在的节点”的问题。十一、单元测试保障项目里也为 Store 写了测试验证连线和删除逻辑。it(creates one edgewhentwo different nodes are linked,0,(){ const store newXingtuGraphStore(false); const first store.addNode({title: A,note: ,tags: [] }); const second store.addNode({title: B,note: ,tags: [] }); store.startLink(first.id); store.finishLink(second.id); expect(store.edges.length).assertEqual(1); expect(store.edges[0].fromId).assertEqual(first.id); expect(store.edges[0].toId).assertEqual(second.id); });图谱关系逻辑很适合写测试因为它不依赖 UI输入输出非常明确。十二、总结 这篇文章分享了如何在 HarmonyOS / OpenHarmony 中使用 ArkTS 实现知识图谱的节点连接和关系高亮。核心思路是用nodes保存节点用edges保存关系用linkingSourceId表示连线起点用两步式点击完成移动端连线交互防止重复边和自连接删除节点时同步清理相关连线选中节点后高亮相关关系这套方案比较适合知识管理、AI 思维导图、概念关系网络、单词联想图谱等应用。它不复杂但能让应用从“信息展示”升级为“关系表达”。✨