标记外部节点,与networkx中的其他节点/边缘重叠最小

Posted

技术标签:

【中文标题】标记外部节点,与networkx中的其他节点/边缘重叠最小【英文标题】:label nodes outside with minimum overlap with other nodes/edges in networkx 【发布时间】:2012-08-10 08:57:13 【问题描述】:

我正在尝试创建一个在节点外部打印节点标签的图形。我能够生成如下所示的“偏移量”来解决这个目的。但是,有时标签与边缘重叠(这是不可取的,因为节点周围有很多空白区域可以打印相应的标签)。我需要以标签不与任何边缘重叠的方式标记这些节点,或者至少尝试尽可能减少重叠。

import networkx as nx
from networkx.utils import is_list_of_ints, flatten
import matplotlib.pyplot as plt

G=nx.Graph()

G = nx.complete_graph(5)
mapping = 0:'aaaaaaa',1:'bbbbbbb',2:'ccccccc', 3:'dddddddd', 4:'eeeeeeeee'
G = nx.relabel_nodes(G,mapping)

plt.figure(figsize=(10,10), facecolor="w", frameon=False)
pos = nx.graphviz_layout(G, prog="fdp") #calculate position (x,y) coordinates
nx.draw_networkx_nodes(G,pos,node_size=1200,node_shape='o',node_color='0.75')
nx.draw_networkx_edges(G,pos, width=2,edge_color='b')


#for labeling outside the node
offset =10
pos_labels = 
keys = pos.keys()
for key in keys:
    x, y = pos[key]
    pos_labels[key] = (x, y+offset)
nx.draw_networkx_labels(G,pos=pos_labels,fontsize=2)
plt.show()

networkx 中是否有任何功能可以处理这种情况。我google了很久没有成功。

【问题讨论】:

您是否考虑过将标签视为节点本身,也许对它们标注的节点有一个隐藏的边缘? 【参考方案1】:

我之前曾尝试过类似的事情,主要想法是尽量避开边缘。

假设边是直线,有两种简单相似的方法可以实现:

    基于节点neighbourhood 的边缘相对于节点本身所形成的角度。

    基于邻域节点的centroid。

因此,找出从一个节点到其邻域的边缘形成的角度,并尝试将标签定位在远离大多数边缘的位置;或估计节点邻域的质心并将标签沿相反方向定位。

第一个解决方案可能有点问题,主要是因为atan2 函数的运行方式(它本质上决定了边缘角度),但它确实在定位标签方面提供了一些灵活性。

第二种方案是最简单的,工作原理如下:

import networkx as nx
import matplotlib.pyplot as plt

#Build the graph
#Please note, the code here is as per the original post
G=nx.Graph()
G = nx.complete_graph(5)
mapping = 0:'aaaaaaa',1:'bbbbbbb',2:'ccccccc', 3:'dddddddd', 4:'eeeeeeeee'
G = nx.relabel_nodes(G,mapping)

plt.figure(figsize=(10,10), facecolor="w", frameon=False)
#Get a graph layout
pos = nx.graphviz_layout(G, prog="fdp") #calculate position (x,y) coordinates
#Here is an alternative layout, please see below.
#pos = nx.layout.spring_layout(G)
nx.draw_networkx_nodes(G,pos,node_size=1200,node_shape='^',node_color='0.75')
nx.draw_networkx_edges(G,pos, width=2,edge_color='r')
#Show the original position of the labels using a Green colour.
nx.draw_networkx_labels(G,pos,font_color='g')

#Please note, the code below uses the original idea of re-calculating a dictionary of adjusted label positions per node.
label_ratio = 1.0/8.0
pos_labels =  
#For each node in the Graph
for aNode in G.nodes():
    #Get the node's position from the layout
    x,y = pos[aNode]
    #Get the node's neighbourhood
    N = G[aNode]
    #Find the centroid of the neighbourhood. The centroid is the average of the Neighbourhood's node's x and y coordinates respectively.
    #Please note: This could be optimised further
    cx = sum(map(lambda x:pos[x][0], N)) / len(pos)
    cy = sum(map(lambda x:pos[x][1], N)) / len(pos)
    #Get the centroid's 'direction' or 'slope'. That is, the direction TOWARDS the centroid FROM aNode.
    slopeY = (y-cy)
    slopeX = (x-cx)
    #Position the label at some distance along this line. Here, the label is positioned at about 1/8th of the distance.
    pos_labels[aNode] = (x+slopeX*label_ratio, y+slopeY*label_ratio)

#Finally, redraw the labels at their new position.
nx.draw_networkx_labels(G,pos=pos_labels,fontsize=2)
#Show the figure
plt.show()

这主要适用于大部分位于图形外围的节点,但对于位于图形中心的节点具有挑战性,因为质心不会提供避开大部分边的可靠方向。

这是 graphviz 的 fdp 布局的输出...

...这里是 networkx'spring layout 的输出。

请注意第二个图中绿色和黑色标签的距离。本质上,dddddddd的邻域的质心与节点的实际位置比较接近。

对于更复杂的解决方案,您可能需要检查更复杂的算法,例如 the one that is used by Wordle,以便在标签与边相交时调整标签的初始位置。

希望这会有所帮助。

【讨论】:

信息量很大,Wordle 算法的链接对任何对更复杂的解决方案感兴趣的人都很有用。 谢谢。事实上,Wordle 的逐次逼近法甚至可​​能适用于中等大小的图,但随着节点和边数的增加,收敛可能会出现问题。 如果您将标签放置在远离大多数边缘的角度,您最终可能会到达一个恰好已经被另一边缘占据的位置。如果节点的边很少,但最终成为具有很多边的节点的常见情况,这不太可能发生。相反,您希望以顺时针或逆时针的方式对边缘进行排序,找到连续边缘之间的最大角度,并将标签放置在这两个边缘之间的半角处。 然而,这两种方法都存在根本缺陷,因为它们假设节点附近的边也属于该节点。如果图很大或很密集,这个假设很快就会失效。我在回答中概述了第三种方法,它在我的测试中更加稳健。【参考方案2】:

@A_A 概述的方法建立在良好的直觉之上,并且是不错的初步近似。然而,除了@A_A 已经提到的问题之外,这两种方法还有一些额外的问题。

    如果一个节点的(欧几里德)附近的所有边也属于该节点,这两种方法都只会减少标签边的重叠。但是,如果图很大或很密集,一个节点附近的大多数边可以属于其他节点,这两种方法都没有考虑到。

    尽管这两种方法通常会减少小型稀疏图中的标签边重叠,但两种方法都没有解决标签节点和标签标签重叠问题。

有一种概念上简单的方法也可以解决标签节点和标签标签重叠问题:在每个被标记的节点周围画一个圆圈。在每个圆上,找到离其他所有东西(节点、边、其他标签)最远的点。 这可以确保圆上的这个位置周围有最空白的画布,因此是一个很好的地方贴个标签。

这可以通过以下方式完成:

    使用沿边缘密集采样的一系列点来逼近每条边缘。在实践中,10-20 点似乎工作得很好,但即使是 100-1000 点在计算上也很容易处理。确保包括边缘的起点和终点,即节点位置。

    对于每个标签,计算沿相应节点周围的圆采样的第二组点。同样,35 点(每 10 度一个点)通常绰绰有余,但使用更多点(例如 100 点)并没有太大的危害。

    对于每个圆,在圆上找到其最近的欧几里得邻居最远的点(同时排除同一圆上的点)。将标签放在那里。

可以进一步细化步骤 3,以使用最近两个邻居的最大平均距离。这解决了当节点位于图的外围时可能发生的关系,因此圆的大部分区域的最近邻居是被标记的节点。

从数字的角度来看,所有这些都可能听起来很可怕。然而,通过使用 KD-trees,可以非常高效地计算最近邻距离,如下所示(使用 100 个点来近似每个边和圆)。

这种方法在netgraph 中实现,这是一个用于可视化网络的python 库(我是作者)。该库与大多数常见的图形数据格式完全兼容,包括 networkxigraph Graph 对象,因此制作漂亮的图形应该是容易和快速的。至少是这样的想法。

重现动画的代码(不包括鼠标移动):

#!/usr/bin/env python
import matplotlib.pyplot as plt
import networkx as nx
from netgraph import InteractiveGraph # pip install netgraph

g = InteractiveGraph(nx.complete_graph(10), node_size=2, edge_width=0.5,
                     node_labels=dict(zip(range(10), 'abcdefghij')), node_label_offset=0.05,
                     node_label_fontdict=dict(size=20, fontweight='bold'))
plt.show()

【讨论】:

以上是关于标记外部节点,与networkx中的其他节点/边缘重叠最小的主要内容,如果未能解决你的问题,请参考以下文章

NetworkX DiGraph 边缘具有基于其初始节点的特定

如何更改节点大小和边缘权重 NetworkX?

节点作为networkx中的格点

是否有可能让 networkx dijkstra 避免某些边缘?

如何从networkx图中提取随机节点?

加权边缘如何影响networkx中的PageRank?