如何从graphviz的布局中获取坐标?
Posted
技术标签:
【中文标题】如何从graphviz的布局中获取坐标?【英文标题】:How to get the coordinates from layout from graphviz? 【发布时间】:2012-12-06 00:02:15 【问题描述】:我一直在使用 pygraph 进行一些项目。我完成了this example,它工作正常。
现在,问题如下:图形是以图片格式 (gif) 绘制的。我需要的是为 gif 图像上显示的图形布局获取每个节点的实际坐标。我该怎么做呢?我一直在尝试和尝试,但找不到解决这个问题的方法。我认为问题的解决方案会以某种方式通过操纵以下两行之一:
gv.layout(gvv,'dot')
gv.render(gvv,'png','europe.png')
提前致谢!
【问题讨论】:
您能详细说明您的尝试吗?目前尚不清楚您的问题是什么。您没有设法从节点属性中获取位置吗? 我认为我的问题很清楚 - 我想提取 gv.render 在文件“europe.png”中绘制的节点的坐标。除了“print gv.layout”,我没有尝试任何东西,但它返回“True”。我找到了 gv.layout 的来源,但它只返回“_gv.layout”,我找不到这个来源。所以,我真的不知道每个节点的坐标分配发生在哪里。我尝试了“打印 gr.nodes”,但在那里找不到坐标。任何想法如何从 gv 对象中提取坐标? 有关于这个主题的任何消息吗?我已经专门针对 networkx-pygraphviz-graphviz 堆栈提出了类似的问题,但尚未得到答案。贝尔菲戈尔,你现在有办法解决吗? 【参考方案1】:您可以使用以下命令将布局信息添加到图表中:
gv.render(gvv)
然后找出一个节点的位置得到它的属性pos
:
n_france = gv.findnode(gvv, "France")
pos = gv.getv(n_france, "pos")
然后根据您想要做什么,您可能需要将点坐标转换为 png 图像坐标。您可以从这里获得有用的信息:
http://www.graphviz.org/faq/#FaqCoordTransformation
它非常详细地解释了从图形单元到图像像素的计算。
希望这是您正在寻找的。p>
【讨论】:
是的,安妮,这正是我所需要的。非常感谢! 链接似乎失效了。我试图在新网站 (graphviz.org/documentation) 上找到它来编辑问题,但找不到。你知道那个页面在哪里吗? 我已经修复了答案中的链接。【参考方案2】:我刚刚找到了一个非常适合我的需求的类似解决方案
pos = nx.drawing.nx_agraph.graphviz_layout(G, prog='dot', args='-Grankdir=LR')
干杯!
【讨论】:
【参考方案3】:使用 pydotplus,您可以加载并解析 dot/gv 文件并询问 pydotplus 生成的数据结构,但这种内部表示似乎最初并不具备所有节点属性,如 pos,除非它们已经在文件中。 但是您也可以调用 .write_dot() 来生成更详细的点文件版本。如果您对此进行解析,则生成的数据结构似乎具有所有节点的 pos(甚至是样条的 pos)
注意:最好按名称而不是索引来索引节点,因为详细文件中任何带有方括号的文本都将被解析为节点,因此节点列表可能包含虚假的额外元素。
在 Spyder 提示下的以下(略微编辑)实验中,我有一个简洁的点文件interior.gv(没有节点的 pos),我使用 .graph_from_dot_file(),然后是 .write_dot()。然后 .graph_from_dot_file() 再次 在详细生成的文件上,然后根据需要找到 pos。
import pydotplus as pdp
interior = pdp.graphviz.graph_from_dot_file('interior.gv')
interior.write_dot('interior2.dot')
Out[210]: True
interior2 = pdp.graphviz.graph_from_dot_file('interior2.dot')
interior2.get_nodes()[3].get_pos()
Out[214]: '"213.74,130"'
【讨论】:
【参考方案4】:Networkx 可以做到这一点:
import networkx as nx
def setup_europe():
G = nx.Graph()
G.add_edge("Portugal", "Spain")
G.add_edge("Spain","France")
G.add_edge("France","Belgium")
G.add_edge("France","Germany")
G.add_edge("France","Italy")
G.add_edge("Belgium","Netherlands")
G.add_edge("Germany","Belgium")
G.add_edge("Germany","Netherlands")
G.add_edge("England","Wales")
G.add_edge("England","Scotland")
G.add_edge("Scotland","Wales")
G.add_edge("Switzerland","Austria")
G.add_edge("Switzerland","Germany")
G.add_edge("Switzerland","France")
G.add_edge("Switzerland","Italy")
G.add_edge("Austria","Germany")
G.add_edge("Austria","Italy")
G.add_edge("Austria","Czech Republic")
G.add_edge("Austria","Slovakia")
G.add_edge("Austria","Hungary")
G.add_edge("Denmark","Germany")
G.add_edge("Poland","Czech Republic")
G.add_edge("Poland","Slovakia")
G.add_edge("Poland","Germany")
G.add_edge("Czech Republic","Slovakia")
G.add_edge("Czech Republic","Germany")
G.add_edge("Slovakia","Hungary")
return G
G = setup_europe()
写一个点文件:
nx.write_dot(G, '/tmp/out.dot')
计算节点的位置:
pos = nx.pygraphviz_layout(G, prog = 'dot')
print(pos)
# 'Netherlands': (713.86, 167.0), 'Italy': (473.86, 389.0), 'Czech Republic': (100.86, 241.0), 'Portugal': (879.86, 315.0), 'England': (1024.9, 241.0), 'Denmark': (568.86, 167.0), 'Poland': (100.86, 167.0), 'Scotland': (1024.9, 389.0), 'France': (571.86, 315.0), 'Belgium': (713.86, 19.0), 'Austria': (320.86, 167.0), 'Slovakia': (156.86, 315.0), 'Wales': (990.86, 315.0), 'Switzerland': (473.86, 241.0), 'Hungary': (294.86, 241.0), 'Germany': (465.86, 93.0), 'Spain': (879.86, 241.0)
渲染一个 png:
agraph = nx.to_agraph(G)
agraph.draw("/tmp/europe.png", format = 'png', prog = 'dot')
【讨论】:
感谢 unutbu,这当然解决了我的问题,尽管只是部分解决了。我实际上需要 gv 提供的布局中的坐标,而不是 networkx 提供的坐标。无论如何,感谢您的回答,它也对我帮助很大。任何想法如何用 gv 做到这一点? @Kiril:嗯,抱歉。我不知道。【参考方案5】:仅使用 pydot/dot,您可以通过生成 SVG 然后从 SVG 读取节点的位置来实现。它有点hacky,但足够可靠
from xml.dom import minidom
import pydot
from io import BytesIO
def extract_node_positions(
in_dot: pydot.Dot
) -> Dict[str, Tuple[str, float, float]]:
"""
Extract the x,y positions from a pydot graph by rendering
Args:
in_dot: the graph to render
Returns:
a list of all the nodes
Examples:
>>> g = pydot.Dot()
>>> g.add_node(pydot.Node("A"))
>>> g.add_node(pydot.Node("B"))
>>> g.add_node(pydot.Node("C"))
>>> g.add_edge(pydot.Edge("A", "B"))
>>> g.add_edge(pydot.Edge("B", "C"))
>>> extract_node_positions(g)
'A': ('A', 27.0, -157.8), 'B': ('B', 27.0, -85.8), 'C': ('C', 27.0, -13.8)
"""
node_mapping = f'nodei': k.get_name()
for i, k in enumerate(in_dot.get_nodes(), 1)
svg_io = BytesIO(in_dot.create_svg())
doc = minidom.parse(svg_io) # parseString also exists
node_dict = node_mapping[p.getAttribute('id')]: (c_text.firstChild.data,
float(c_text.getAttribute('x')),
float(c_text.getAttribute('y')))
for p in doc.getElementsByTagName("g") if "node" == p.getAttribute('class').lower()
for c_text in p.getElementsByTagName('text')
doc.unlink()
return node_dict
【讨论】:
【参考方案6】:要在 Python 中以二进制数据字符串的形式直接访问 Graphviz 渲染命令(例如点)的结果,而不是写入文件,请使用 Graph 或 Digraph 对象的 pipe() 方法:
h = Graph('hello', format='svg')
h.edge('Hello', 'World')
打印(h.pipe().decode('utf-8'))
【讨论】:
【参考方案7】:我最近在这个问题上苦苦挣扎,这里的答案有些帮助,但并不完全在那里。关于networkx的建议是正确的。 Networkx 使用 pygraphviz 与 graphviz 交互,因此如果您愿意,可以直接使用 pygraphviz:
import pygraphviz as pgv
G = pgv.AGraph(strict=False,directed=True)
# add nodes and edges
G.add_edge(1,2)
G.add_edge(2,1)
# generate a layout--this creates `pos` attributes for both nodes and edges
G.layout(prog="dot")
#change something about the graph that would change the layout, e.g.,
edge = G.get_edge("1", "2")
edge.attr["label"] = "test label"
# create the graph using the layout already generated; note, do not provide `prog`
G.draw("test.pdf")
# compare it to a newly generated layout
G.draw("test2.pdf", prog="dot")
如果你想使用layout
命令已经生成的节点和边缘位置,重要的部分是不向draw
命令提供prog
。我现在看到这在 draw
的 pygraphviz 文档字符串中有所说明。顺便说一句,它与带有-n2
参数的prog="neato"
相同。查看源代码,pygraphviz 调用 graphviz 来生成布局。
【讨论】:
以上是关于如何从graphviz的布局中获取坐标?的主要内容,如果未能解决你的问题,请参考以下文章
如何垂直而不是水平地制作点(graphviz)布局未连接的节点?
流程图 Graphviz - Graph Visualization Software