- 数据可视化
【免费下载链接】cytoscape.js
Graph theory (network) library for visualisation and analysis
导读
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()的返回值是"元素集合"(可能同时包含节点与边),其组成为:
- 与原集合中每个节点直接相连的边(
connectedEdges); - 通过这些边连接到的另一端节点(距原集合一跳的邻居节点)。
这一设计的好处是:拿到结果后你可以直接对"邻居关系"整体做样式操作(如高亮边与节点),而无需再单独查询边。它对节点集合、边集合以及混合集合都适用:对边调用时,结果等价于对边的两个端点节点分别求邻域后的并集。
二、开放邻域与闭合邻域: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 的完整语法(node、edge、#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'),从源码结构可以梳理出几个值得注意的实现细节:
- 以节点为遍历入口:实现首先调用
this.nodes()取出集合中的节点部分(边的邻域最终也归约到其两端节点),然后对每个节点调用node.connectedEdges()(定义见同文件 src/collection/traversing.mjs,内部直接读取节点私有数据node._private.edges,因此效率很高)。 - 一跳邻居的判定:对每条连接边,通过
edge.source()/edge.target()求出与当前节点相对的"另一端"节点并加入结果;同时把该边本身也加入结果——这正是文档强调的"包含连接边"的邻域。 - 自环(loop)防护:当节点存在指向自身的自环时,
otherNode会退化为空集合,代码中的if (otherNode.length > 0)检查确保不会把空元素压入结果。 - 结果去重与过滤:
this.spawn( elements, true )会把收集到的原始元素包装成去重的集合对象,最后统一执行.filter( selector ),因此无论是否传 selector,返回的都是标准的 Cytoscape.js 集合,支持链式调用。 - 结果缓存:
neighborhood用cache(...)包装,与outgoers、incomers、connectedEdges等遍历方法一样会缓存计算结果,多次调用不会重复遍历(缓存可通过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
相关推荐
Cytoscape.js outgoers() 详解:节点出边与后继集合的图遍历实战
Cytoscape.js outgoers 详解:节点出边与后继集合的图遍历实战 outgoers 是 Cytoscape.js 集合(collection)遍
数据可视化cytoscape.js 集合遍历 API 详解:使用 eles.sources() 高效获取边的源节点
cytoscape.js 集合遍历 API 详解:使用 eles.sources 高效获取边的源节点 本篇文章聚焦 cytoscape.js 图论可视化库中集合
数据可视化如何获取内购商品与价格信息?flutter_inapp_purchase商品查询fetchProducts完全教程
如何获取内购商品与价格信息?flutter_inapp_purchase商品查询fetchProducts完全教程 flutter_inapp_purchase
数据可视化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考