news 2026/9/23 20:25:40

Cytoscape.js 集合邻域 API 详解:neighborhood、openNeighborhood 与 closedNeighborhood 的图遍历实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Cytoscape.js 集合邻域 API 详解:neighborhood、openNeighborhood 与 closedNeighborhood 的图遍历实战
  • 数据可视化

【免费下载链接】cytoscape.js

Graph theory (network) library for visualisation and analysis

项目地址:https://gitcode.com/gh_mirrors/cy/cytoscape.js
点击查看免费下载

导读

eles.neighborhood()是 Cytoscape.js 图遍历体系中用于获取"一跳邻域"的核心方法,与图论教材中传统的邻域(neighbourhood)定义不同,它返回的集合同时包含邻居节点与连接边,从而为可视化高亮、图算法实现提供了更大灵活性。本文将基于documentation/md/collection/neighborhood.md的官方说明,结合源码实现与测试用例,完整讲解 open / closed 邻域的语义差异、selector 过滤用法、底层遍历算法及其在路径搜索、中心性计算和自动布局中的真实应用。

一、什么是"邻域":与图论传统定义的差异

在标准图论中,一个节点 $v$ 的邻域通常指与 $v$ 直接相邻的节点集合(即一阶邻居)。Cytoscape.js 的neighborhood()在此基础上做了实用化扩展——官方文档明确指出:

The neighbourhood returned by this function is a bit different than the traditional definition of a "neighbourhood": This returned neighbourhood includes the edges connecting the collection to the neighbourhood. This gives you more flexibility.

也就是说,neighborhood()的返回值是"元素集合"(可能同时包含节点与边),其组成为:

  1. 与原集合中每个节点直接相连的边connectedEdges);
  2. 通过这些边连接到的另一端节点(距原集合一跳的邻居节点)。

这一设计的好处是:拿到结果后你可以直接对"邻居关系"整体做样式操作(如高亮边与节点),而无需再单独查询边。它对节点集合、边集合以及混合集合都适用:对边调用时,结果等价于对边的两个端点节点分别求邻域后的并集。

二、开放邻域与闭合邻域:open vs closed

官方文档给出了两种邻域的明确定义:

  • 开放邻域(open neighbourhood)不包含原始元素集合本身。当你不指定参数时,默认返回的就是开放邻域。
  • 闭合邻域(closed neighbourhood)包含原始元素集合本身,即open结果与原始集合的并集。

对应到 API 上:

方法是否包含原集合说明
eles.neighborhood()否(默认)开放邻域,等价于openNeighborhood()
eles.openNeighborhood()开放邻域的显式别名
eles.closedNeighborhood()闭合邻域,等价于neighborhood().add(this)

三个方法都接受一个可选的selector 字符串参数,用于对结果进行二次过滤:

cy.$('#j').neighborhood(); // 获取节点 j 的开放邻域(邻居节点 + 连接边) cy.$('#j').neighborhood('node'); // 只保留邻域中的节点 cy.$('#j').neighborhood('edge'); // 只保留邻域中的边 cy.$('#j').closedNeighborhood(); // 加上节点 j 本身 cy.$('#j').openNeighborhood('node'); // 开放邻域 + 节点过滤

关于 selector 的完整语法(nodeedge#id[dataAttr]、类选择器等),可参阅 selectors.md。

三、源码实现剖析:一次遍历,节点与边同时收集

neighborhood()及相关方法定义在 src/collection/traversing.mjs 的 "Neighbourhood functions" 区块。其核心实现如下:

neighborhood: cache(function( selector ){ let elements = []; let nodes = this.nodes(); for( let i = 0; i < nodes.length; i++ ){ // for all nodes let node = nodes[ i ]; let connectedEdges = node.connectedEdges(); // for each connected edge, add the edge and the other node for( let j = 0; j < connectedEdges.length; j++ ){ let edge = connectedEdges[ j ]; let src = edge.source(); let tgt = edge.target(); let otherNode = node === src ? tgt : src; // need check in case of loop if( otherNode.length > 0 ){ elements.push( otherNode[0] ); // add node 1 hop away } // add connected edge elements.push( edge[0] ); } } return ( this.spawn( elements, true ) ).filter( selector ); }, 'neighborhood'),

从源码结构可以梳理出几个值得注意的实现细节:

  1. 以节点为遍历入口:实现首先调用this.nodes()取出集合中的节点部分(边的邻域最终也归约到其两端节点),然后对每个节点调用node.connectedEdges()(定义见同文件 src/collection/traversing.mjs,内部直接读取节点私有数据node._private.edges,因此效率很高)。
  2. 一跳邻居的判定:对每条连接边,通过edge.source()/edge.target()求出与当前节点相对的"另一端"节点并加入结果;同时把该边本身也加入结果——这正是文档强调的"包含连接边"的邻域。
  3. 自环(loop)防护:当节点存在指向自身的自环时,otherNode会退化为空集合,代码中的if (otherNode.length > 0)检查确保不会把空元素压入结果。
  4. 结果去重与过滤this.spawn( elements, true )会把收集到的原始元素包装成去重的集合对象,最后统一执行.filter( selector ),因此无论是否传 selector,返回的都是标准的 Cytoscape.js 集合,支持链式调用。
  5. 结果缓存neighborhoodcache(...)包装,与outgoersincomersconnectedEdges等遍历方法一样会缓存计算结果,多次调用不会重复遍历(缓存可通过eles.clearTraversalCache()清除,见 src/collection/traversing.mjs)。

open / closed 与英式拼写别名

闭合邻域并非独立实现,而是对开放邻域结果做了一次并集:

closedNeighborhood: function( selector ){ return this.neighborhood().add( this ).filter( selector ); }, openNeighborhood: function( selector ){ return this.neighborhood( selector ); }

此外,源码为所有三个方法提供了英式拼写别名,在编码时两种写法均可使用:

elesfn.neighbourhood = elesfn.neighborhood; elesfn.closedNeighbourhood = elesfn.closedNeighborhood; elesfn.openNeighbourhood = elesfn.openNeighborhood;

四、测试用例验证:预期结果一目了然

项目测试 test/collection-traversing.mjs 用一条由n1 — n1n2 — n2 — n2n3 — n3构成的简单路径验证了邻域语义:

it('eles.neighborhood() etc', function(){ var nbhd = cy.$('#n2').neighborhood(); expect( nbhd.same( cy.$('#n1, #n3, #n1n2, #n2n3') ) ).to.be.true; expect( cy.$('#n1').neighborhood().same( cy.$('#n2, #n1n2') ) ).to.be.true; expect( cy.$('#n2').closedNeighborhood().same( cy.$('#n1, #n2, #n3, #n1n2, #n2n3') ) ).to.be.true; });

对照测试可以直观验证前文语义:

  • #n2的开放邻域 = 邻居节点#n1#n3加上连接边#n1n2#n2n3(共 4 个元素,不含#n2自身);
  • #n1的开放邻域 =#n2与边#n1n2
  • #n2的闭合邻域 = 开放邻域 ∪#n2自身(共 5 个元素)。

这与官方文档示例cy.$('#j').neighborhood()的行为完全一致,可直接作为验证脚本运行。

五、邻域在库内算法与布局中的真实调用

neighborhood()及其变体不是孤立 API,它被 Cytoscape.js 内部多个核心模块复用,可以作为理解其价值的"用户证据":

  • 最短路径算法:dijkstra.mjs 在松弛操作中通过u.neighborhood().intersect( nodes )获取节点u的邻居节点(先求含边的邻域,再与节点集求交集),是"邻域 + 集合运算"组合的典型范式。
  • 中心性计算:betweenness-centrality.mjs 用v.openNeighborhood().nodes()提取每个顶点的邻居节点集合,作为 Brandes 算法的前置数据结构。
  • 自动布局:breadthfirst.mjs 在广度优先分层时用ele.neighborhood()找出当前元素的邻居并继续扩散。
  • 集合比较器:comparators.mjs 的allAreNeighbors(collection)方法内部直接调用this.neighborhood()判断目标集合是否全部落在当前集合的邻域内,配套文档见 allAreNeighbors.md。

六、与其他遍历 API 的关系与实战建议

邻域函数属于"单跳遍历"族,可与同文件中的其他方法按需组合:

方法作用文档
neighborhood()一跳邻居节点 + 连接边本文档
connectedEdges()与节点相连的所有边connectedEdges.md
connectedNodes()边的两个端点节点connectedNodes.md
edgesWith() / edgesTo()两集合之间的边edgesWith.md
outgoers() / incomers()有向图中的出邻 / 入邻outgoers.md

实战中常用的组合模式包括:

// 高亮选中节点的邻域(含边),常用于"以点带面"的交互聚焦 cy.on('tap', 'node', function( evt ){ const node = evt.target; cy.elements().removeClass('highlighted'); // 先清除旧高亮 node.closedNeighborhood().addClass('highlighted'); // 闭合邻域:节点本身 + 邻居 + 边 }); // 仅统计一跳邻居节点数量(不含边) const degree = cy.$('#j').neighborhood('node').length; // 只取邻居节点,用于后续集合运算(如交集、差集) const neighborNodes = cy.$('#j').openNeighborhood('node'); const mutual = neighborNodes.intersect( cy.$('#k').neighborhood('node') ); // 共同邻居

七、注意事项与边界行为

  • 集合不可变性neighborhood()与 Cytoscape.js 其他集合方法一样,返回新的集合而不会修改原集合(集合不可变规则详见 collection.md),因此可以安全地用于函数式链式编程。
  • 默认开放语义:不传参数时是开放邻域;需要"连自己在内"的场景务必使用closedNeighborhood()
  • 自环与重边:自环在结果中只会贡献边本身(节点部分因去重被正确排除);平行边会各自出现在结果集合中。
  • selector 过滤发生在邻域计算之后:因此neighborhood('node')neighborhood().nodes()语义等价,开发者可按可读性自行选择。
  • 有向图说明neighborhood()不区分边的方向,返回无向意义上的一跳邻域;需要方向感知时请改用outgoers()/incomers()

小结

eles.neighborhood()以"节点 + 连接边"一体的返回结构区别于传统图论邻域,配合openNeighborhood()closedNeighborhood()与 selector 过滤,可以在一行代码内完成交互高亮、度数统计、共同邻居计算等常见图操作;而其底层实现(src/collection/traversing.mjs)与测试(test/collection-traversing.mjs)则完整印证了官方文档的描述,并为 Dijkstra、介数中心性和 BFS 布局等库内算法提供了统一的一跳遍历原语。

  • 数据可视化

【免费下载链接】cytoscape.js

Graph theory (network) library for visualisation and analysis

项目地址:https://gitcode.com/gh_mirrors/cy/cytoscape.js
点击查看免费下载
上一篇:TSF生产环境部署:监控、日志和安全配置的完整清单
下一篇:如何永久保存微信聊天记录:从数字碎片到个人记忆库的完整指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 20:25:40

WorkBuddy 智能体实战:从零搭建每日自动化工作流

1. 为什么我最终把每日重复工作交给了 WorkBuddy每天早上九点坐到工位&#xff0c;打开电脑的第一件事不是写代码&#xff0c;而是打开七八个网页挨个签到、把昨天的订单数据从三个平台导出来合并、再手动整理成日报发到群里。这套动作我做了快两年&#xff0c;熟练到闭着眼睛都…

作者头像 李华
网站建设 2026/9/23 20:24:33

FY-4A卫星云图识别实战:HDF5数据处理与轻量U-Net云分类

简介&#xff1a;本资源是一份面向高校计算机、遥感或人工智能方向本科生的课程设计实践项目&#xff0c;聚焦卫星云层图像的理解与识别任务&#xff0c;提供从传统图像处理到深度学习建模的双路径解决方案。资源共148个文件&#xff0c;包含70个Python源码&#xff08;含U-Net…

作者头像 李华
网站建设 2026/9/23 20:22:54

PDT团队KPI指标库搭建指南:从统一口径到落地避坑

简介&#xff1a;面向PDT&#xff08;产品开发团队&#xff09;绩效考核场景的KPI指标库文档&#xff0c;将财务、客户、内部业务三大维度的核心指标整理为可直接参考的评估体系。内容涵盖销售收入、毛利率、目标成本完成率、缺陷密度、问题解决率、NPD流程符合度、软件开发生产…

作者头像 李华