如何在graphviz/pydot中强制执行网格布局? [英] How to enforce grid layout in graphviz/pydot?

查看:134
本文介绍了如何在graphviz/pydot中强制执行网格布局?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

tl; dr:如何使graphviz坚持节点的网格布局?

tl;dr: How do I make graphviz stick to a grid layout of nodes?

我正在尝试绘制一个时间序列的完整因果图".这意味着我有一些图,其中单位和时间索引在时间方向上重复.

I'm trying to draw a "full causal graph" for a time series. This means that I have some graph with Units and Time indices repeating in the time direction.

我想用Graphviz绘制图形,因为它是程序性的.我不知道单位数,也不知道时间步数.随着项目的继续,这将有所不同.我可能还希望通过编程方式来调整颜色,笔划宽度等,从而使机器学习模型更加生动.

I want to draw the graph with Graphviz since it is programmatic. I don't know the number of Units, nor the number of Time steps. This will be varied as the project continues. I might also want to adjust colors, stroke widths etc programatically as a vizualisation of machine learning models.

为了使图易于阅读,我需要考虑一些布局注意事项:

To make the diagram readable, I have a few layout considerations to take into account:

  • 单位在行中对齐
  • 列中的时间索引
  • 图形中的边缘周期性重复(在下面的图像中,橙色是垂直的,蓝色是1步宽,棕色是2步宽)

因此,我正在尝试复制此Powerpoint样机.

Therefore, I'm trying to replicate this powerpoint mockup.

为此,我从一些'SO帖子中获得了启发,并添加了带有rank=same以及不可见边缘的子图.这篇文章显示了它: https://stackoverflow.com/a/49736304/4050510

To accomplish this, I've got inspiration from a few 'SO posts and added subgraphs with rank=same and also invisible edges. This post shows it: https://stackoverflow.com/a/49736304/4050510

从其他SO帖子中,我已经能够按照自己喜欢的方式订购节点.当前输出如下.由于我使用的是pydot,因此python代码和点代码非常难看.我将根据要求链接到它.

From other SO posts, I've been able to order my nodes in the way I like. The current output is like below. Since I'm using pydot, the python code and the dot code is quite ugly. I'll link to it on request.

如您所见,除了一些怪癖之外,其他所有功能都可以使用:

As you see, it all works except a few quirks:

1)不可见节点未与可见节点对齐 1)橙色箭头弯曲了,因为它们与不可见的箭头相撞

1) The invisible nodes are not aligned with the visible nodes 1) The orange arrows are bent, since they are colliding with the invisible arrows

有什么方法可以使Graphviz优雅地处理吗? 如何强制网格布局,如何使橙色箭头笔直?

Is there any way to make Graphviz deal with this elegantly? How do I force the grid layout, and how do I make the orange arrows straight?

以上情节的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];
}
}

推荐答案

我认为在这种情况下,技巧是指定完整的(grid-)图形,然后使不想要的部分不可见. 这是您的案例的最小示例. (我刚刚省略了颜色.)

I think the trick in this case is to specify the full (grid-)graph and then make the unwanted parts invisible. Here is a minimal example for your case. (I have just left out the colors.)

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"
}

Graphviz输出

这篇关于如何在graphviz/pydot中强制执行网格布局?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆