tl; dr:如何使graphviz坚持节点的网格布局?
我试图绘制一个时间序列的“完整因果图”。这意味着我有一些图,其中单位和时间索引在时间方向上重复。
我想用Graphviz绘制图形,因为它是程序性的。我不知道单位数,也不知道时间步数。随着项目的继续,这将有所不同。我可能还希望通过编程方式来调整颜色,笔划宽度等,以使机器学习模型更具生气。
为了使图易于阅读,我需要考虑一些布局注意事项:
为此,我从一些'SO帖子中得到了启发,并添加了带有rank=same
和不可见边缘的子图。这篇文章显示了它:https://stackoverflow.com/a/49736304/4050510
从其他SO帖子中,我已经能够按照自己喜欢的方式订购节点。当前输出如下。由于我使用的是pydot
,因此python代码和点代码非常难看。我将根据要求链接到它。
如您所见,除了一些古怪之处,它都可以工作:
1)不可见节点未与可见节点对齐 1)橙色箭头弯曲,因为它们与不可见的箭头相撞
有什么方法可以使Graphviz优雅地处理吗? 如何强制网格布局,如何使橙色箭头笔直?
以上情节的Pydot源代码
import io
import pydot
import matplotlib.image as img
import matplotlib.pyplot as plt
def render_pydot(g: pydot.Dot, prog):
# noinspection PyUnresolvedReferences
png_bytes = g.create(prog=prog, format="png")
bytes_as_inmemory_file = io.BytesIO(png_bytes)
img2 = img.imread(bytes_as_inmemory_file)
plt.figure()
plt.imshow(img2, aspect='equal')
plt.axis(False)
plt.grid(False)
plt.show()
def create_dot_for_timeseries_with_pydot():
"""Generate a dot object for a static 'full time series'"""
g = pydot.Dot(rankdir='LR')
units = ["Alfa", "Beta", "Gamma"]
time_steps = list(range(0, 5)) # five steps, two invisible
for t in time_steps:
sg = pydot.Subgraph(rank="same", rankdir="TB")
for u, _ in enumerate(units):
# create nodes
this_node_name = f"{t}_{u}"
opts = {'name': this_node_name,
'label': this_node_name
}
if t not in time_steps[1:-1]:
opts['style'] = 'invis'
opts['color'] = 'gray70'
n = pydot.Node(**opts)
# create invisible edges to enforce order vertically and horizontally
# https://stackoverflow.com/q/44274518/4050510
if u != 0:
prev = f"{t}_{u - 1}"
e = pydot.Edge(src=prev, dst=this_node_name,
style='invis',
color="gray70",
weight=1000)
sg.add_edge(e)
if t in time_steps[:-1]:
next = f"{t + 1}_{u}"
g.add_edge(pydot.Edge(src=this_node_name, dst=next,
style="invis",
color="gray70", weight=1000))
sg.add_node(n)
g.add_subgraph(sg)
# Draw lag 0 effects
if t in time_steps[1:-1]:
g.add_edge(pydot.Edge(f"{t}_{0}", f"{t}_{1}", color="orange"))
# Draw lag 1 effects
if t in time_steps[:-1]:
for u, _ in enumerate(units):
g.add_edge(pydot.Edge(f"{t}_{u}", f"{t + 1}_{u}", color="blue"))
g.add_edge(pydot.Edge(f"{t}_{0}", f"{t + 1}_{1}", color="blue"))
g.add_edge(pydot.Edge(f"{t}_{1}", f"{t + 1}_{2}", color="blue"))
# Draw lag 2 effects
if t in time_steps[:-2]:
g.add_edge(pydot.Edge(f"{t}_{0}", f"{t + 2}_{1}", color="brown"))
return g
g = create_dot_for_timeseries_with_pydot()
print(g) # print the dot document as text for inspection
render_pydot(g, prog='dot') # show the image
从上面的python文件生成的DOT代码
digraph G {
rankdir=LR;
splines=False;
"0_0" -> "1_0" [color=gray70, style=invis, weight=1000];
"0_1" -> "1_1" [color=gray70, style=invis, weight=1000];
"0_2" -> "1_2" [color=gray70, style=invis, weight=1000];
subgraph {
rank=same;
rankdir=TB;
"0_0" [color=gray70, label="0_0", style=invis];
"0_0" -> "0_1" [color=gray70, style=invis, weight=1000];
"0_1" [color=gray70, label="0_1", style=invis];
"0_1" -> "0_2" [color=gray70, style=invis, weight=1000];
"0_2" [color=gray70, label="0_2", style=invis];
}
"0_0" -> "1_0" [color=blue];
"0_1" -> "1_1" [color=blue];
"0_2" -> "1_2" [color=blue];
"0_0" -> "1_1" [color=blue];
"0_1" -> "1_2" [color=blue];
"0_0" -> "2_1" [color=brown];
"1_0" -> "2_0" [color=gray70, style=invis, weight=1000];
"1_1" -> "2_1" [color=gray70, style=invis, weight=1000];
"1_2" -> "2_2" [color=gray70, style=invis, weight=1000];
subgraph {
rank=same;
rankdir=TB;
"1_0" [label="1_0"];
"1_0" -> "1_1" [color=gray70, style=invis, weight=1000];
"1_1" [label="1_1"];
"1_1" -> "1_2" [color=gray70, style=invis, weight=1000];
"1_2" [label="1_2"];
}
"1_0" -> "1_1" [color=orange];
"1_0" -> "2_0" [color=blue];
"1_1" -> "2_1" [color=blue];
"1_2" -> "2_2" [color=blue];
"1_0" -> "2_1" [color=blue];
"1_1" -> "2_2" [color=blue];
"1_0" -> "3_1" [color=brown];
"2_0" -> "3_0" [color=gray70, style=invis, weight=1000];
"2_1" -> "3_1" [color=gray70, style=invis, weight=1000];
"2_2" -> "3_2" [color=gray70, style=invis, weight=1000];
subgraph {
rank=same;
rankdir=TB;
"2_0" [label="2_0"];
"2_0" -> "2_1" [color=gray70, style=invis, weight=1000];
"2_1" [label="2_1"];
"2_1" -> "2_2" [color=gray70, style=invis, weight=1000];
"2_2" [label="2_2"];
}
"2_0" -> "2_1" [color=orange];
"2_0" -> "3_0" [color=blue];
"2_1" -> "3_1" [color=blue];
"2_2" -> "3_2" [color=blue];
"2_0" -> "3_1" [color=blue];
"2_1" -> "3_2" [color=blue];
"2_0" -> "4_1" [color=brown];
"3_0" -> "4_0" [color=gray70, style=invis, weight=1000];
"3_1" -> "4_1" [color=gray70, style=invis, weight=1000];
"3_2" -> "4_2" [color=gray70, style=invis, weight=1000];
subgraph {
rank=same;
rankdir=TB;
"3_0" [label="3_0"];
"3_0" -> "3_1" [color=gray70, style=invis, weight=1000];
"3_1" [label="3_1"];
"3_1" -> "3_2" [color=gray70, style=invis, weight=1000];
"3_2" [label="3_2"];
}
"3_0" -> "3_1" [color=orange];
"3_0" -> "4_0" [color=blue];
"3_1" -> "4_1" [color=blue];
"3_2" -> "4_2" [color=blue];
"3_0" -> "4_1" [color=blue];
"3_1" -> "4_2" [color=blue];
subgraph {
rank=same;
rankdir=TB;
"4_0" [color=gray70, label="4_0", style=invis];
"4_0" -> "4_1" [color=gray70, style=invis, weight=1000];
"4_1" [color=gray70, label="4_1", style=invis];
"4_1" -> "4_2" [color=gray70, style=invis, weight=1000];
"4_2" [color=gray70, label="4_2", style=invis];
}
}
答案 0 :(得分:2)
我认为在这种情况下,技巧是指定完整的(网格)图,然后使不想要的部分不可见。 这是您的案例的最小示例。 (我刚刚省略了颜色。)
digraph{
# Columns
subgraph {
"0_0" [style=invis]
"0_1" [style=invis]
"0_2" [style=invis]
}
subgraph {
"1_0"
"1_1"
"1_2"
}
subgraph {
"2_0"
"2_1"
"2_2"
}
subgraph {
"3_0"
"3_1"
"3_2"
}
subgraph {
"4_0" [style=invis]
"4_1" [style=invis]
"4_2" [style=invis]
}
# Rows
subgraph {
rank=same
"0_0"
"1_0"
"2_0"
"3_0"
"4_0"
}
subgraph {
rank=same
"0_1"
"1_1"
"2_1"
"3_1"
"4_1"
}
subgraph {
rank=same
"0_2"
"1_2"
"2_2"
"3_2"
"4_2"
}
# Straight edges
"0_0" -> "1_0"
"0_1" -> "1_1"
"0_2" -> "1_2"
"1_0" -> "2_0"
"1_1" -> "2_1"
"1_2" -> "2_2"
"2_0" -> "3_0"
"2_1" -> "3_1"
"2_2" -> "3_2"
"3_0" -> "4_0"
"3_1" -> "4_1"
"3_2" -> "4_2"
"0_0" -> "0_1" [style=invis]
"1_0" -> "1_1"
"2_0" -> "2_1"
"3_0" -> "3_1"
"4_0" -> "4_1" [style=invis]
"0_1" -> "0_2" [style=invis]
"1_1" -> "1_2" [style=invis]
"2_1" -> "2_2" [style=invis]
"3_1" -> "3_2" [style=invis]
"4_1" -> "4_2" [style=invis]
# Diagonal edges
"0_0" -> "1_1"
"0_0" -> "2_1"
"1_0" -> "3_1"
"2_0" -> "4_1"
"0_1" -> "1_2"
"1_1" -> "2_2"
"2_1" -> "3_2"
"3_1" -> "4_2"
}