我将在此提供一个基于我两年前编写的Python脚本的答案,虽然它远非最佳。如Oli Charlesworth所建议的那样,我使用了PyDot通过Graphviz。完整的脚本已经发布在下面,但首先需要进行设置才能使其正常工作(假设您使用的是virtualenv):
首先,请确保已安装Graphviz。假设您使用的是apt等软件包管理器:
$> sudo apt-get install graphviz
使用virtualenv wrapper创建一个新的虚拟环境:
$> mkvirtualenv pydot
在新的虚拟环境中,将pyparsing版本固定在2.0之前(dot_parser需要使用此版本才能正常工作):
(pydot)$> pip install pyparsing==1.5.7
安装pydot:
(pydot)$> pip install pydot
现在我使用的Python(警告:它不是很好):
import os
import re
import sys
import tempfile
import pydot
TEST_GRAPH = {
"Root" : {
"inputs" : [],
},
"Leaf1" : {
"inputs" : ["Root"],
},
"Leaf2" : {
"inputs" : ["Leaf1"],
},
"Leaf3" : {
"inputs" : ["Leaf1"],
},
"Leaf4" : {
"inputs" : ["Leaf2"],
},
"Leaf5" : {
"inputs" : ["Leaf2"],
},
"Leaf6" : {
"inputs" : ["Leaf3"],
},
}
class DotError(StandardError):
"""Dot problems."""
RAW_NAME_RE = r"(^[A-Za-z_][a-zA-Z0-9_]*$)|(^-?([.[0-9]+|[0-9]+(.[0-9]*)?)$)"
def conditional_quote(name):
"""Dot quotes names if they match the regex above, otherwise not"""
if re.match(RAW_NAME_RE, name) is None:
return "\"%s\"" % name
return name
def get_node_positions(nodedict, aspect=None):
"""Build the pydot graph, outputting a dictionary of name -> [x,y]"""
g = pydot.Dot(margin="0.1", ranksep="0.7", nodesep="1.5")
if aspect is not None:
g.set_aspect(round(aspect))
for name, node in nodedict.items():
n = pydot.Node(name, width="0.5", fixedsize="0.5")
g.add_node(n)
for name, node in nodedict.items():
for i in node["inputs"]:
try:
src = g.get_node(conditional_quote(i))
if isinstance(src, list):
src = src[0]
dst = g.get_node(conditional_quote(name))
if isinstance(dst, list):
dst = dst[0]
g.add_edge(pydot.Edge(src, dst))
except IndexError:
print "Input %s not found" % i
if len(nodedict) < 4:
raise DotError("Dot breaks with less than 4 nodes.")
with tempfile.NamedTemporaryFile(delete=False, suffix=".dot") as t:
t.close()
g.write_dot(t.name)
g = pydot.graph_from_dot_file(t.name)
out = {}
for name, node in nodedict.items():
gn = g.get_node(conditional_quote(name))
if isinstance(gn, list):
gn = gn[0]
out[name] = [int(d) \
for d in gn.get_pos().replace('"', "").split(",")]
return out
if __name__ == "__main__":
print(get_node_positions(TEST_GRAPH))
尽管这看起来相当丑陋,但它基本上满足了我的需求。如果有人能找到更好的解决方案,我会很感兴趣。