21

ノードの外側にノードラベルが印刷されたグラフを作成しようとしています。以下に示すように、目的を解決する「オフセット」を生成することができます。ただし、ラベルがエッジと重なる場合があります(対応するラベルを印刷できるノードの周囲に多くの空きスペースがあるため、これは望ましくありません)。ラベルがどのエッジにも重ならないように、または少なくともオーバーラップを可能な限り最小限に抑えるように、これらのノードにラベルを付ける必要があります。

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の関数はありますか?私は長い間グーグルで検索しましたが、成功しませんでした。

4

2 に答える 2

3

私は以前、ほとんどエッジの邪魔にならないようにすることを主なアイデアとして、同様のことを試みました。

エッジが直線であると仮定すると、これを実現するための単純で類似した方法が 2 つあります。

  1. ノードの近傍のエッジがノード自体に対して作っている角度に基づいています。

  2. 近隣ノードの重心に基づく。

そのため、ノードから隣接するエッジに向かってエッジが形成される角度を見つけ、エッジの大部分からラベルを離して配置してみてください。または、ノードの近傍の重心を推定し、反対方向に沿ってラベルを配置します。

最初の解決策は、主にatan2関数の動作方法 (本質的にエッジ角度を決定する) が原因で、少し問題が生じる可能性がありますが、ラベルの配置に関してある程度の柔軟性を提供します。

2 番目の解決策は最も単純で、次のように機能します。

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レイアウトの出力です...

graphviz fdp 出力

...そしてこれが networkx のspring layoutの出力です。

networkx 春のレイアウト

2 番目の図の緑と黒のラベルが近接していることに注意してください。基本的に、dddddd の近傍の重心は、ノードの実際の位置に比較的近いです。

より複雑なソリューションの場合は、ラベルがエッジと交差する場合にラベルの初期位置を調整するために、Wordle で使用されるアルゴリズムなど、より複雑なアルゴリズムを確認することをお勧めします。

お役に立てれば。

于 2015-07-07T16:42:28.877 に答える
0

@A_A によって概説されたアプローチは、優れた直感に基づいて構築されており、まともな最初の概算です。ただし、@A_A によって既に言及されている問題とは別に、両方のアプローチにはいくつかの追加の問題があります。

  1. どちらのアプローチも、ノードの (ユークリッド) 近傍のすべてのエッジもそのノードに属している場合にのみ、ラベル エッジのオーバーラップを減らします。ただし、グラフが大きいか密集している場合、ノードの近くにあるエッジの大部分が他のノードに属している可能性があり、どちらの方法も考慮されていません。

  2. どちらのアプローチも通常、小さくてまばらなグラフでラベルエッジのオーバーラップを減らしますが、どちらのアプローチもラベルノードとラベルラベルのオーバーラップには対処しません。

label-node と label-label のオーバーラップも解決する概念的に単純なアプローチがあります: ラベル付けされている各ノードの周りに円を描きます。各円で、他のすべて (ノード、エッジ、その他のラベル) から最も離れている点を見つけます。これにより、円のこの位置が周囲に最も空のキャンバスを持つようになるため、ラベルを配置するのに適した場所になります。

これは、次の方法で行うことができます。

  1. エッジに沿って密にサンプリングされた一連の点で各エッジを近似します。実際には、10 ~ 20 個のポイントで十分に機能しているように見えますが、100 ~ 1000 個のポイントでさえ、計算上は簡単に扱いやすいものです。エッジの始点と終点、つまりノードの位置を必ず含めてください。

  2. ラベルごとに、対応するノードの周りの円に沿ってサンプリングされたポイントの 2 番目のセットを計算します。繰り返しますが、通常は 35 ポイント (10 度ごとに 1 ポイント) で十分ですが、より多くのポイント (たとえば 100 ポイント) を使用しても実質的な害はありません。

  3. 各円について、最も近いユークリッド近傍が最大に離れている円上の点を見つけます (同じ円上の点を除外します)。そこにラベルを貼ります。

ステップ 3 をさらに改良して、最も近い 2 つの近傍の最大平均距離を使用できます。これにより、ノードがグラフの周辺にある場合に発生する可能性がある関係が解決され、円の大きなセクションの最近傍がラベル付けされたノードになります。

これらはすべて、数値的な観点からすれば恐ろしく聞こえるかもしれません。ただし、最近傍距離は、以下に示すように KD ツリーを使用することで非常に効率的に計算できます (100 ポイントを使用して各エッジと円を近似します)。

ここに画像の説明を入力

このアプローチは、ネットワークを視覚化するための Python ライブラリであるnetgraphに実装されています (私は作成者です)。networkxこのライブラリは、およびオブジェクトを含むほとんどの一般的なグラフ データ形式と完全に互換性があるigraph 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()
于 2021-06-03T12:41:40.203 に答える