如何让networkx生成的tikz有向无环图按节点w属性分层?
问题:基于节点w属性的有向无环图TikZ分层绘制
我用NetworkX创建了一个带权有向无环图G = networkx.DiGraph(),每个节点都有非负整数属性w,这个图代表偏序集——如果节点n1到n2有路径,通常满足G.nodes[n1]["w"] < G.nodes[n2]["w"],但w不是图位置的直接函数,比如指向同一节点的两个节点可以有不同w值,只要比来源节点的w大就行。
现在需要把这个图转成LaTeX的TikZ图形,要求不同w值的节点处于不同高度,相同w值的节点在同一高度。我写了示例代码,但生成的图里节点C(w=2)和B(w=1)在同一层,没法体现1<2<3的层级关系,实际有50个节点,手动调整太麻烦,求自动实现的简便方法,高度不用完全精确,能区分不同w就行。
示例代码:
import networkx import dot2tex nodes = [ ["A", {"w": 0}], ["B", {"w": 1}], ["C", {"w": 2}], ["D", {"w": 3}], ] edges = [ ["A", "B"], ["A", "C"], ["B", "D"], ["C", "D"], ] G = networkx.DiGraph() G.add_nodes_from(nodes) G.add_edges_from(edges) A = networkx.nx_agraph.to_agraph(G) dot_string = A.string() latex_string = dot2tex.dot2tex( dot_string, preproc=True, docpreamble=r"\usepackage{amsmath,amssymb,amsthm,bm}", format="tikz" ) latex_string = dot2tex.dot2tex( latex_string, figonly=False, docpreamble=r"\usepackage{amsmath,amssymb,amsthm,bm}", autosize=True, crop=True, valignmode="dot", format="tikz" ) print(latex_string) with open("test.tex", "w") as f: f.write(latex_string)
解决方案:利用Graphviz的分层布局+节点属性映射
核心思路是给Graphviz指定节点的层级(rank),直接用节点的w值作为rank值,这样Graphviz会自动把相同w的节点放在同一层,不同w的按数值高低分层。
步骤1:给Graphviz图的节点添加rank属性
在把NetworkX图转成Graphviz的AGraph后,遍历每个节点,将其w属性赋值给Graphviz节点的rank属性。Graphviz的rank支持整数,直接映射即可。
步骤2:调整dot2tex参数,保留rank布局
修改代码,确保Graphviz的布局规则被正确传递给TikZ,避免二次调用dot2tex导致布局信息丢失。
修改后的完整代码
import networkx import dot2tex nodes = [ ["A", {"w": 0}], ["B", {"w": 1}], ["C", {"w": 2}], ["D", {"w": 3}], ] edges = [ ["A", "B"], ["A", "C"], ["B", "D"], ["C", "D"], ] G = networkx.DiGraph() G.add_nodes_from(nodes) G.add_edges_from(edges) # 转成Graphviz的AGraph,并设置节点rank A = networkx.nx_agraph.to_agraph(G) for node in G.nodes(data=True): node_name, attrs = node # 将w值设为Graphviz节点的rank属性 A.get_node(node_name).attr["rank"] = str(attrs["w"]) dot_string = A.string() # 直接生成完整的LaTeX文档 latex_string = dot2tex.dot2tex( dot_string, figonly=False, docpreamble=r"\usepackage{amsmath,amssymb,amsthm,bm}", autosize=True, crop=True, valignmode="dot", format="tikz", preproc=True # 启用预处理器,确保Graphviz布局生效 ) print(latex_string) with open("test.tex", "w") as f: f.write(latex_string)
关键说明
- rank属性作用:Graphviz会根据rank值强制分层,rank值小的节点在上方(默认布局方向),大的在下方,完美匹配偏序集的
w值递增关系。 - 单次调用dot2tex:原来的两次调用会丢失Graphviz的布局信息,改成一次调用即可保留rank设置。
- preproc=True:确保dot2tex先让Graphviz处理布局,再转成TikZ代码,rank的分层规则会被正确应用。
可选优化
如果需要调整布局方向或层级间距,可添加全局Graphviz属性:
# 设置布局方向为从左到右(默认从上到下) A.graph_attr["rankdir"] = "LR" # 调整层级之间的间距(单位:英寸) A.graph_attr["ranksep"] = "0.8"
内容的提问来源于stack exchange,提问作者Bulkilol
相关产品推荐
相关产品推荐

