图形图:带有给定节点半径和非旋转边缘的径向布局

发布于 2025-02-13 03:26:10 字数 10220 浏览 4 评论 0 原文

我想绘制 subgroups的晶格带有图形工具的离散空间组,例如 yed graphviz networkx ,...

一个示例输入文件
将遵循 graphml 二维空间组的文件 p4gm 到索引8(由 gaap ):

<?xml version='1.0' encoding='UTF-8'?>
<graphml
      xmlns='http://graphml.graphdrawing.org/xmlns'
      xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
      xsi:schemaLocation='http://graphml.graphdrawing.org/xmlns
      http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd'>

  <key id='idx' for='node' attr.name='index'  attr.type='int'    />
  <key id='r'   for='node' attr.name='radius' attr.type='double' />

  <key id='idx' for='edge' attr.name='index'  attr.type='int'    />

  <graph id='G' edgedefault='directed'>
    <node id='01'> <data key='idx'>1</data> <data key='r'>0.</data> </node>
    <node id='02'> <data key='idx'>2</data> <data key='r'>0.33333333333333337</data> </node>
    <node id='03'> <data key='idx'>2</data> <data key='r'>0.33333333333333337</data> </node>
    <node id='04'> <data key='idx'>2</data> <data key='r'>0.33333333333333337</data> </node>
    <node id='05'> <data key='idx'>4</data> <data key='r'>0.66666666666666674</data> </node>
    <node id='06'> <data key='idx'>4</data> <data key='r'>0.66666666666666674</data> </node>
    <node id='07'> <data key='idx'>6</data> <data key='r'>0.8616541669070521</data> </node>
    <node id='08'> <data key='idx'>4</data> <data key='r'>0.66666666666666674</data> </node>
    <node id='09'> <data key='idx'>4</data> <data key='r'>0.66666666666666674</data> </node>
    <node id='10'> <data key='idx'>4</data> <data key='r'>0.66666666666666674</data> </node>
    <node id='11'> <data key='idx'>4</data> <data key='r'>0.66666666666666674</data> </node>
    <node id='12'> <data key='idx'>4</data> <data key='r'>0.66666666666666674</data> </node>
    <node id='13'> <data key='idx'>6</data> <data key='r'>0.8616541669070521</data> </node>
    <node id='14'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='15'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='16'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='17'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='18'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='19'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='20'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='21'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='22'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='23'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='24'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='25'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='26'> <data key='idx'>8</data> <data key='r'>1.</data> </node>

    <edge id='e01' target='02' source='01'> <data key='idx'>2</data> </edge>
    <edge id='e02' target='03' source='01'> <data key='idx'>2</data> </edge>
    <edge id='e03' target='04' source='01'> <data key='idx'>2</data> </edge>
    <edge id='e04' target='05' source='02'> <data key='idx'>2</data> </edge>
    <edge id='e05' target='06' source='02'> <data key='idx'>2</data> </edge>
    <edge id='e06' target='07' source='02'> <data key='idx'>3</data> </edge>
    <edge id='e07' target='06' source='03'> <data key='idx'>2</data> </edge>
    <edge id='e08' target='08' source='03'> <data key='idx'>2</data> </edge>
    <edge id='e09' target='06' source='04'> <data key='idx'>2</data> </edge>
    <edge id='e10' target='10' source='04'> <data key='idx'>2</data> </edge>
    <edge id='e11' target='11' source='04'> <data key='idx'>2</data> </edge>
    <edge id='e12' target='09' source='04'> <data key='idx'>2</data> </edge>
    <edge id='e13' target='12' source='04'> <data key='idx'>2</data> </edge>
    <edge id='e14' target='13' source='04'> <data key='idx'>3</data> </edge>
    <edge id='e15' target='14' source='05'> <data key='idx'>2</data> </edge>
    <edge id='e16' target='15' source='05'> <data key='idx'>2</data> </edge>
    <edge id='e17' target='14' source='06'> <data key='idx'>2</data> </edge>
    <edge id='e18' target='16' source='06'> <data key='idx'>2</data> </edge>
    <edge id='e19' target='17' source='06'> <data key='idx'>2</data> </edge>
    <edge id='e20' target='18' source='06'> <data key='idx'>2</data> </edge>
    <edge id='e21' target='16' source='08'> <data key='idx'>2</data> </edge>
    <edge id='e22' target='19' source='08'> <data key='idx'>2</data> </edge>
    <edge id='e23' target='18' source='09'> <data key='idx'>2</data> </edge>
    <edge id='e24' target='20' source='09'> <data key='idx'>2</data> </edge>
    <edge id='e25' target='14' source='10'> <data key='idx'>2</data> </edge>
    <edge id='e26' target='16' source='11'> <data key='idx'>2</data> </edge>
    <edge id='e27' target='21' source='11'> <data key='idx'>2</data> </edge>
    <edge id='e28' target='22' source='11'> <data key='idx'>2</data> </edge>
    <edge id='e29' target='23' source='11'> <data key='idx'>2</data> </edge>
    <edge id='e30' target='18' source='12'> <data key='idx'>2</data> </edge>
    <edge id='e31' target='21' source='12'> <data key='idx'>2</data> </edge>
    <edge id='e32' target='24' source='12'> <data key='idx'>2</data> </edge>
    <edge id='e33' target='25' source='12'> <data key='idx'>2</data> </edge>
    <edge id='e34' target='26' source='12'> <data key='idx'>2</data> </edge>

  </graph>
</graphml>

我将数据匿名为专注于图形图。

我正在寻找一个可以在径向布局上布局的图形绘图工具,类似于 radial树,但可以绘制非旋转边缘以避免边缘节点交叉。边缘交叉很好。但是,理想情况下,观看者可以遵循从源节点到目标节点的每个边缘。

yed (3.22)
提供一个 radial layout ,可以将边缘绘制为弧或弯曲以避免边缘节点交叉:

“在此处输入图像描述”

但是,节点放在同一同心圆上基于距中心的最短距离,以横穿边缘的数量来衡量。

但是我想根据节点的子组索引放置这些节点(确切地说是索引的对数)。在上图中,具有索引6的节点与索引4的节点在同一圆圈上,这不是我想要的。

NetworkX (2.8.4)
具有允许您手动分配节点为shells

import networkx as nx
import matplotlib.pyplot as plt
from math import log

G = nx.read_graphml("ITC_2_012_idx8.graphml")

indices = set([idx for n, idx in G.nodes.data('index')])
radii = [log(idx)/log(max(indices)) for n, idx in G.nodes.data('index')]
shells = [[n for n, idx in G.nodes.data('index') if idx == x] for x in indices]
pos = nx.shell_layout(G, shells)

plt.box(False) # remove box

nx.draw_networkx(G, pos,
                 node_color="white",
                 node_size=500,
                 edgecolors="black",
                 labels={n: idx for n, idx in G.nodes.data('index')},
                )

但是,NetworkX仅绘制直边。

GraphViz或其他图形绘图工具可以做我想要的吗?

我已经开始创建一个自己的布局和边缘路由算法,该算法以以下绘图样式:

“输入图像描述在这里”

但是,这是未完成的,成为一个永无止境的故事。因此,我希望我已经忽略了一个工具,该工具可以自动提供所需的径向布局和合适的边路路线。 YED是我收到的最接近的工具(请参阅此问题的第一张图片)。

I want to draw the lattice of subgroups up to a finite subgroup index of an infinite, discrete space group with a graph drawing tool such as yEd, GraphViz, NetworkX, ...

An Example input file
would be following graphml file for the two-dimensional space group p4gm up to index 8 (generated by self-written code in gap):

<?xml version='1.0' encoding='UTF-8'?>
<graphml
      xmlns='http://graphml.graphdrawing.org/xmlns'
      xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
      xsi:schemaLocation='http://graphml.graphdrawing.org/xmlns
      http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd'>

  <key id='idx' for='node' attr.name='index'  attr.type='int'    />
  <key id='r'   for='node' attr.name='radius' attr.type='double' />

  <key id='idx' for='edge' attr.name='index'  attr.type='int'    />

  <graph id='G' edgedefault='directed'>
    <node id='01'> <data key='idx'>1</data> <data key='r'>0.</data> </node>
    <node id='02'> <data key='idx'>2</data> <data key='r'>0.33333333333333337</data> </node>
    <node id='03'> <data key='idx'>2</data> <data key='r'>0.33333333333333337</data> </node>
    <node id='04'> <data key='idx'>2</data> <data key='r'>0.33333333333333337</data> </node>
    <node id='05'> <data key='idx'>4</data> <data key='r'>0.66666666666666674</data> </node>
    <node id='06'> <data key='idx'>4</data> <data key='r'>0.66666666666666674</data> </node>
    <node id='07'> <data key='idx'>6</data> <data key='r'>0.8616541669070521</data> </node>
    <node id='08'> <data key='idx'>4</data> <data key='r'>0.66666666666666674</data> </node>
    <node id='09'> <data key='idx'>4</data> <data key='r'>0.66666666666666674</data> </node>
    <node id='10'> <data key='idx'>4</data> <data key='r'>0.66666666666666674</data> </node>
    <node id='11'> <data key='idx'>4</data> <data key='r'>0.66666666666666674</data> </node>
    <node id='12'> <data key='idx'>4</data> <data key='r'>0.66666666666666674</data> </node>
    <node id='13'> <data key='idx'>6</data> <data key='r'>0.8616541669070521</data> </node>
    <node id='14'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='15'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='16'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='17'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='18'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='19'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='20'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='21'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='22'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='23'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='24'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='25'> <data key='idx'>8</data> <data key='r'>1.</data> </node>
    <node id='26'> <data key='idx'>8</data> <data key='r'>1.</data> </node>

    <edge id='e01' target='02' source='01'> <data key='idx'>2</data> </edge>
    <edge id='e02' target='03' source='01'> <data key='idx'>2</data> </edge>
    <edge id='e03' target='04' source='01'> <data key='idx'>2</data> </edge>
    <edge id='e04' target='05' source='02'> <data key='idx'>2</data> </edge>
    <edge id='e05' target='06' source='02'> <data key='idx'>2</data> </edge>
    <edge id='e06' target='07' source='02'> <data key='idx'>3</data> </edge>
    <edge id='e07' target='06' source='03'> <data key='idx'>2</data> </edge>
    <edge id='e08' target='08' source='03'> <data key='idx'>2</data> </edge>
    <edge id='e09' target='06' source='04'> <data key='idx'>2</data> </edge>
    <edge id='e10' target='10' source='04'> <data key='idx'>2</data> </edge>
    <edge id='e11' target='11' source='04'> <data key='idx'>2</data> </edge>
    <edge id='e12' target='09' source='04'> <data key='idx'>2</data> </edge>
    <edge id='e13' target='12' source='04'> <data key='idx'>2</data> </edge>
    <edge id='e14' target='13' source='04'> <data key='idx'>3</data> </edge>
    <edge id='e15' target='14' source='05'> <data key='idx'>2</data> </edge>
    <edge id='e16' target='15' source='05'> <data key='idx'>2</data> </edge>
    <edge id='e17' target='14' source='06'> <data key='idx'>2</data> </edge>
    <edge id='e18' target='16' source='06'> <data key='idx'>2</data> </edge>
    <edge id='e19' target='17' source='06'> <data key='idx'>2</data> </edge>
    <edge id='e20' target='18' source='06'> <data key='idx'>2</data> </edge>
    <edge id='e21' target='16' source='08'> <data key='idx'>2</data> </edge>
    <edge id='e22' target='19' source='08'> <data key='idx'>2</data> </edge>
    <edge id='e23' target='18' source='09'> <data key='idx'>2</data> </edge>
    <edge id='e24' target='20' source='09'> <data key='idx'>2</data> </edge>
    <edge id='e25' target='14' source='10'> <data key='idx'>2</data> </edge>
    <edge id='e26' target='16' source='11'> <data key='idx'>2</data> </edge>
    <edge id='e27' target='21' source='11'> <data key='idx'>2</data> </edge>
    <edge id='e28' target='22' source='11'> <data key='idx'>2</data> </edge>
    <edge id='e29' target='23' source='11'> <data key='idx'>2</data> </edge>
    <edge id='e30' target='18' source='12'> <data key='idx'>2</data> </edge>
    <edge id='e31' target='21' source='12'> <data key='idx'>2</data> </edge>
    <edge id='e32' target='24' source='12'> <data key='idx'>2</data> </edge>
    <edge id='e33' target='25' source='12'> <data key='idx'>2</data> </edge>
    <edge id='e34' target='26' source='12'> <data key='idx'>2</data> </edge>

  </graph>
</graphml>

I have anonymous-ed the data to focus on the graph drawing.

I am looking for a graph drawing tool which can layout the nodes on a radial layout, similar to a radial tree but can draw non-straight edges to avoid edge-node crossings. Edge-edge crossing are fine. Ideally however, a viewer can follow each edge from source to target node.

yEd (3.22)
provides a radial layout which can draw edges as arcs or curved to avoid edge-node crossings:

enter image description here

However, the nodes are placed on the same concentric circle based on the shortest distance to the center, measured by number of traversed edges.

But I want to place the nodes based on their subgroup index (to be precise the logarithm of the index). In the above picture the nodes with index 6 are on the same circle as the nodes with index 4 which is not what I want.

NetworkX (2.8.4)
has the shell layout which allows you to assign manually the nodes to the shells

import networkx as nx
import matplotlib.pyplot as plt
from math import log

G = nx.read_graphml("ITC_2_012_idx8.graphml")

indices = set([idx for n, idx in G.nodes.data('index')])
radii = [log(idx)/log(max(indices)) for n, idx in G.nodes.data('index')]
shells = [[n for n, idx in G.nodes.data('index') if idx == x] for x in indices]
pos = nx.shell_layout(G, shells)

plt.box(False) # remove box

nx.draw_networkx(G, pos,
                 node_color="white",
                 node_size=500,
                 edgecolors="black",
                 labels={n: idx for n, idx in G.nodes.data('index')},
                )

enter image description here

However, NetworkX draws only straight edges.

Can GraphViz or another graph drawing tool do what I want?

I have started to create an own layouting and edge routing algorithm which results in following style of drawing:

enter image description here

However, this is unfinished and becomes a never ending story. So I am hoping that I have overlooked a tool which can give automatically the desired radial layout and suitable edge routes.
yEd is the closest tool I have come by (see the first picture of this question).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

心在旅行 2025-02-20 03:26:10

首先,我喜欢您对边缘路由的解决方案,并希望为此看到代码。

其次,以下是我使用NetGraph的尝试,这是我编写(并维护)的网络可视化库。
NetGraph很容易安装( pip internetgraph ),并且接受 graph 来自各种网络分析库(NetworkX,igraph,Graph-tool)的对象,因此不应该有任何摩擦。

shell 节点布局使用所谓的中位启发式层中的节点以减少边缘交叉点。
弯曲边缘布局使用Fruchterman-Reingold算法的变体来分发边缘控制点,以便在可能的情况下避免边缘避免节点(彼此)。

#!/usr/bin/env python
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx

from netgraph import Graph # pip install netgraph


# from the question:
G = nx.read_graphml("tmp/test.graphml")
indices = set([idx for n, idx in G.nodes.data('index')])
shells = [[n for n, idx in G.nodes.data('index') if idx == x] for x in indices]
radii = [np.log(idx)/np.log(max(indices)) for n, idx in G.nodes.data('index')]
node_labels = {n: idx for n, idx in G.nodes.data('index')}

# define a bounding box for the node layout
max_radius = np.max(radii)
origin = (-max_radius, -max_radius)
scale = (2 * max_radius, 2 * max_radius)

# initialise a figure
fig, ax = plt.subplots(figsize=(10, 8))

# indicate shells
radii = np.unique(radii)
for radius in radii[::-1]:
    ax.add_patch(plt.Circle((0, 0), radius=radius, facecolor='white', edgecolor='lightgray'))

# plot graph on top
g = Graph(G,
          node_size=6,
          edge_width=2,
          node_labels=node_labels,
          node_layout='shell',
          node_layout_kwargs=dict(shells=shells, radii=radii),
          edge_layout='curved',
          edge_layout_kwargs=dict(k=0.05), # larger values -> straighter edges
          origin=origin,
          scale=scale,
          arrows=True,
          ax=ax
)
plt.show()

First of all, I love your solution to the edge routing, and would love to see the code for that.

Secondly, below is my attempt using netgraph, which is a network visualisation library I wrote (and maintain).
Netgraph is easily installable (pip install netgraph), and accepts Graph objects from various network analysis libraries (networkx, igraph, graph-tool), so there shouldn't be any friction.

enter image description here

The shell node layout uses the so-called median heuristic to order the nodes within a layer to reduce edge crossings.
The curved edge layout uses a variant of the Fruchterman-Reingold algorithm to distribute the edge control points such that edges avoid nodes (and each other) -- where possible.

#!/usr/bin/env python
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx

from netgraph import Graph # pip install netgraph


# from the question:
G = nx.read_graphml("tmp/test.graphml")
indices = set([idx for n, idx in G.nodes.data('index')])
shells = [[n for n, idx in G.nodes.data('index') if idx == x] for x in indices]
radii = [np.log(idx)/np.log(max(indices)) for n, idx in G.nodes.data('index')]
node_labels = {n: idx for n, idx in G.nodes.data('index')}

# define a bounding box for the node layout
max_radius = np.max(radii)
origin = (-max_radius, -max_radius)
scale = (2 * max_radius, 2 * max_radius)

# initialise a figure
fig, ax = plt.subplots(figsize=(10, 8))

# indicate shells
radii = np.unique(radii)
for radius in radii[::-1]:
    ax.add_patch(plt.Circle((0, 0), radius=radius, facecolor='white', edgecolor='lightgray'))

# plot graph on top
g = Graph(G,
          node_size=6,
          edge_width=2,
          node_labels=node_labels,
          node_layout='shell',
          node_layout_kwargs=dict(shells=shells, radii=radii),
          edge_layout='curved',
          edge_layout_kwargs=dict(k=0.05), # larger values -> straighter edges
          origin=origin,
          scale=scale,
          arrows=True,
          ax=ax
)
plt.show()
神仙妹妹 2025-02-20 03:26:10

NetworkX shell_layout 用于节点位置&amp; 选择了graphViz neato 用于边路路由

neato 之所以选择,因为它尊重graphviz节点属性 pos pin> pin

使用 splines edge edge and attribute and attribute esep 可以避免边缘节点交叉。

import networkx as nx
import pygraphviz as pgv
from math import log

G = nx.read_graphml('ITC_2_012_idx8.graphml')

indices = set([idx for n, idx in G.nodes.data('index')])
radii = [log(idx)/log(max(indices)) for n, idx in G.nodes.data('index')]
shells = [[n for n, idx in G.nodes.data('index') if idx == x] for x in indices]
pos = nx.shell_layout(G, shells)

A = nx.nx_agraph.to_agraph(G)

A.graph_attr['splines'] = 'spline' # or 'polyline'
A.graph_attr['scale'] = '0.6'
A.graph_attr['esep'] = '0.5' # node-edge distance
A.node_attr['shape'] = 'circle'
A.node_attr['pin'] = 'true'
for k in A.nodes():
    n = A.get_node(k)
    n.attr['label'] = str(n.attr['index']) # add label 
    n.attr['pos'] = str(pos[k][0]*10)+','+str(pos[k][1]*10)+'!' # set pos string 
    
# print(A.to_string())

A.layout() # default 'neato' which respects the node attribute 'pos'
A # rich output in Jupyter Notebook/Lab using pygraphviz 1.9, Feb 2022

边缘节点交叉点已经消失。

但是,该解决方案尚未像我想拥有的那样:
我更喜欢入站和出站边缘在相对站点的节点上停靠(根节点除外)。
带有边路路由的YED径向布局可以做到这一点,或者查看我的低分辨率自制布局和路由。

更新:Enforce tailport

import networkx as nx
import pygraphviz as pgv
from math import atan2, log, pi

G = nx.read_graphml("ITC_2_012_idx8.graphml")
indices = set([int(idx) for n, idx in G.nodes.data('index')])
radii = [log(idx)/log(max(indices)) for n, idx in G.nodes.data('index')]
shells = [[n for n, idx in G.nodes.data('index') if idx == x] for x in indices]
pos = nx.shell_layout(G, shells)

A = nx.nx_agraph.to_agraph(G)

pos_factor = 12
A.graph_attr['scale'] = "0.5"
A.graph_attr['esep'] = "1"
A.graph_attr['splines'] = "spline"
A.node_attr['shape'] = "circle"
A.node_attr['pin'] = "true"
A.edge_attr['arrowhead']="vee"
for k in A.nodes():
    k.attr["label"] = str(k.attr["index"]) 
    k.attr["pos"] = str(pos[k][0]*pos_factor)+','+str(pos[k][1]*pos_factor)+"!" 

# Set 'tailport' based on quadrant of tail node 
for n1, n2 in A.edges():
    if n1 == A.nodes()[0]: # skip root edges
        continue
    e = A.get_edge(n1, n2)
    theta = atan2(pos[n1][1], pos[n1][0])/pi*180.
    
    if -22.5 <= theta < 22.5:
        e.attr["tailport"] = 'e'
    elif 22.5 <= theta < 67.5:
        e.attr["tailport"] = 'ne'
    elif 67.5 <= theta < 112.5:
        e.attr["tailport"] = 'n'
    elif 112.5 <= theta < 157.5:
        e.attr["tailport"] = 'nw'
    elif 157.5 <= theta <= 180. or -157.5 > theta > -180.:
        e.attr["tailport"] = 'w'
    elif -157.5 <= theta <= -112.5:
        e.attr["tailport"] = 'sw'
    elif -112.5 <= theta <= -67.5:
        e.attr["tailport"] = 's'
    elif -67.5 <= theta <= -22.5:
        e.attr["tailport"] = 'se'
    else:
        raise ValueError('Quadrant determination failed for node', n1,
                         'with polar angle', theta)
    
# Mark problematic edges
A.get_edge('10', '14').attr["color"] = "red"
A.get_edge('04', '06').attr["color"] = "blue"
A.get_edge('06', '18').attr["color"] = "orange"

A.layout() # default 'neato'
A

“在此处输入图像描述”

但是,以imho可视化并不清楚(易于掌握连接性),因为某些高度弯曲引入边缘(例如红色),并存在平行边缘重叠(例如蓝色和橙色)。

NetworkX shell_layout for node positions & GraphViz neato for edge routing

Neato is chosen because it respects the graphviz node attribute pos in combination with pin.

With splines edge routing and the attribute esep, edge-node crossings can be avoided.

import networkx as nx
import pygraphviz as pgv
from math import log

G = nx.read_graphml('ITC_2_012_idx8.graphml')

indices = set([idx for n, idx in G.nodes.data('index')])
radii = [log(idx)/log(max(indices)) for n, idx in G.nodes.data('index')]
shells = [[n for n, idx in G.nodes.data('index') if idx == x] for x in indices]
pos = nx.shell_layout(G, shells)

A = nx.nx_agraph.to_agraph(G)

A.graph_attr['splines'] = 'spline' # or 'polyline'
A.graph_attr['scale'] = '0.6'
A.graph_attr['esep'] = '0.5' # node-edge distance
A.node_attr['shape'] = 'circle'
A.node_attr['pin'] = 'true'
for k in A.nodes():
    n = A.get_node(k)
    n.attr['label'] = str(n.attr['index']) # add label 
    n.attr['pos'] = str(pos[k][0]*10)+','+str(pos[k][1]*10)+'!' # set pos string 
    
# print(A.to_string())

A.layout() # default 'neato' which respects the node attribute 'pos'
A # rich output in Jupyter Notebook/Lab using pygraphviz 1.9, Feb 2022

enter image description here

The edge-node crossings are gone.

However, this solution is not yet as I would like to have it:
I would prefer inbound and outbound edges are docked at the nodes on opposite sites (except root node).
yEd radial layout with edge routing does this or see my low-res self-made layout and routes.

Update: Enforce tailport

import networkx as nx
import pygraphviz as pgv
from math import atan2, log, pi

G = nx.read_graphml("ITC_2_012_idx8.graphml")
indices = set([int(idx) for n, idx in G.nodes.data('index')])
radii = [log(idx)/log(max(indices)) for n, idx in G.nodes.data('index')]
shells = [[n for n, idx in G.nodes.data('index') if idx == x] for x in indices]
pos = nx.shell_layout(G, shells)

A = nx.nx_agraph.to_agraph(G)

pos_factor = 12
A.graph_attr['scale'] = "0.5"
A.graph_attr['esep'] = "1"
A.graph_attr['splines'] = "spline"
A.node_attr['shape'] = "circle"
A.node_attr['pin'] = "true"
A.edge_attr['arrowhead']="vee"
for k in A.nodes():
    k.attr["label"] = str(k.attr["index"]) 
    k.attr["pos"] = str(pos[k][0]*pos_factor)+','+str(pos[k][1]*pos_factor)+"!" 

# Set 'tailport' based on quadrant of tail node 
for n1, n2 in A.edges():
    if n1 == A.nodes()[0]: # skip root edges
        continue
    e = A.get_edge(n1, n2)
    theta = atan2(pos[n1][1], pos[n1][0])/pi*180.
    
    if -22.5 <= theta < 22.5:
        e.attr["tailport"] = 'e'
    elif 22.5 <= theta < 67.5:
        e.attr["tailport"] = 'ne'
    elif 67.5 <= theta < 112.5:
        e.attr["tailport"] = 'n'
    elif 112.5 <= theta < 157.5:
        e.attr["tailport"] = 'nw'
    elif 157.5 <= theta <= 180. or -157.5 > theta > -180.:
        e.attr["tailport"] = 'w'
    elif -157.5 <= theta <= -112.5:
        e.attr["tailport"] = 'sw'
    elif -112.5 <= theta <= -67.5:
        e.attr["tailport"] = 's'
    elif -67.5 <= theta <= -22.5:
        e.attr["tailport"] = 'se'
    else:
        raise ValueError('Quadrant determination failed for node', n1,
                         'with polar angle', theta)
    
# Mark problematic edges
A.get_edge('10', '14').attr["color"] = "red"
A.get_edge('04', '06').attr["color"] = "blue"
A.get_edge('06', '18').attr["color"] = "orange"

A.layout() # default 'neato'
A

enter image description here

However, IMHO the visualisation does not become clearer (easy to grasp the connectivity) since some highly curved edges (e.g. red one) are introduced and there are parallel edge overlaps (e.g. blue and orange).

拧巴小姐 2025-02-20 03:26:10

GraphViz具有两个径向形式的布局引擎:Circo&amp; Twopi。两者都允许使用 len 属性设置所需的边缘长度。

Graphviz has two radial-ish layout engines: circo & twopi. Both allow desired edge length to be set using the len attribute.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文