如何在Graphviz/dot/neato中创建命名边缘“类型”?

6
我需要使用graphviz/dot绘制一个图表,在节点之间有共同的边类型,我正在尝试找到一种定义每种边类型标签的方法,然后在图表中多次使用该标签。
例如,想象一下传统的吊扇FSM示例,其中它最初处于OFF状态,每当有人拉动绳索时,它都会根据风扇的速度转换到新的状态:
     Pull         Pull        Pull
OFF ------> HIGH ------> MED ------> LOW
 ^                                    |
 |                Pull                |
 +------------------------------------+

每条边都被命名为“Pull”,我可以通过以下方式在dot中定义它:
digraph fan {
    OFF  -> HIGH [label="Pull"];
    HIGH -> MED  [label="Pull"];
    MED  -> LOW  [label="Pull"];
    LOW  -> OFF  [label="Pull"];
}

但是我不想每次都指定相同的文本标签,因为

  1. 我的标签可能会变得相当长,这样容易出错,而且
  2. 我的边缘除了标签之外还有其他属性,比如颜色,而且
  3. 我有多种不同类型的边缘可供选择,因此我希望确保在图表中不同上下文中使用的边缘类型“A”始终具有相同的所有属性。

我期望dot有一种语法,可以让我为我的边缘类型定义名称,类似于:

digraph fan {
    edge_a [label="Pull"];

    OFF  -> HIGH edge_a;
    HIGH -> MED  edge_a;
    MED  -> LOW  edge_a;
    LOW  -> OFF  edge_a;
}

当然,这样做实际上是创建一个名为“Pull”的节点和没有标签的边。

我已经在网上搜索了几个小时,但是没有成功。有人知道如何提前定义边的类型以在多个位置使用吗?

更新:@vaettchen建议定义一种边缘类型,然后列出该边缘类型的所有转换,然后定义下一个边缘类型及其转换。尽管这在技术上可以解决我的问题,但会引入一些其他问题,因为我的图表今天可能如下所示:

digraph {
    subgraph cluster_1 {
        a -> b [label="type x", color=red, style=solid];
        b -> a [label="type y", color=green, style=dashed];

        b -> c [label="type x", color=red, style=solid];
        c -> b [label="type y", color=green, style=dashed];

        c -> d [label="type z", color=blue, style=dotted];
    }

    subgraph cluster_2 {
        d -> e [label="type x", color=red, style=solid];
        e -> d [label="type y", color=green, style=dashed];

        e -> f [label="type x", color=red, style=solid];
        f -> e [label="type y", color=green, style=dashed];

        f -> c [label="type z", color=blue, style=dotted];
    }
}

如果按边类型重新排列,我将失去在代码中具有双向边相邻的直观清晰度(a->b 和 b->a),并且我必须明确列出每个子图中的节点,并且必须将子图内部的边定义提取到主图中:

digraph {
    edge [label="type x", color=red, style=solid];
    a -> b;
    b -> c;
    d -> e;
    e -> f;

    edge [label="type y", color=green, style=dashed];
    b -> a;
    c -> b;
    e -> d;
    f -> e;

    edge [label="type z", color=blue, style=dotted];
    c -> d;
    f -> c;

    subgraph cluster_1 {
        a; b; c;
    }

    subgraph cluster_2 {
        d; e; f;
    }
}

所以,虽然它可以解决我提出的问题,我很感谢建议,但我不确定它是否值得这样做,因为最终你会得到一个等同于C程序的东西,在其中你必须在函数外定义所有变量,并按其类型而不是逻辑关联进行组织。
清楚地说,如果存在“edge_type”定义关键字,则上述示例中我真正希望看到的内容将如下所示:
digraph {
    edge_type edge_x [label="type x", color=red, style=solid];
    edge_type edge_y [label="type y", color=green, style=dashed];
    edge_type edge_z [label="type z", color=blue, style=dotted];

    subgraph cluster_1 {
        a -> b edge_x;
        b -> a edge_y;

        b -> c edge_x;
        c -> b edge_y;

        c -> d edge_z;
    }

    subgraph cluster_2 {
        d -> e edge_x;
        e -> d edge_y;

        e -> f edge_x;
        f -> e edge_y;

        f -> c edge_z;
    }
}

亲爱的恶意降低投票者 - 可以解释一下为什么吗?该问题具有样本输入、输出、尝试的解决方案和需求说明,所以不确定我该做些什么来改善它。 - Ed Morton
1
再次感谢,我会看一下。说到github,我希望能找到一种方法,在README.md中包含.dot规范,并在某人在github上查看时自动呈现图形。因此,如果我有一个系统的文本模型,我不必手动更新它的图像。我找到了一个网站(https://github.com/TLmaK0/gravizo),我可以链接到我的.dot文件的引用,实际上起作用了,但我正在使用的github网站是专有的,所以该外部网站实际上无法查看我的内部.dot文件。你遇到过类似的事情吗? - Ed Morton
基本上,这将是一些软件,我可以在 GitHub 存储库中安装它,它可以执行 Grazivo 所做的操作(但现在也可以使用您的 M4 预处理器!) - Ed Morton
3个回答

9
我想我找到了你的解决方案,使用 m4(感谢 Simon)。 通过使用和调整您的示例,我创建了一个名为 gv.m4 的文件:
digraph {
    define(`edge_x',`[label="type x", color=red, style=solid]')
    define(`edge_y',`[label="type y", color=green, style=dashed]')
    define(`edge_z',`[label="type z", color=blue, style=dotted]')

    subgraph cluster_1 {
        a -> b edge_x;
        b -> a edge_y;

        b -> c edge_x;
        c -> b edge_y;

        c -> d edge_z;
    }

    subgraph cluster_2 {
        d -> e edge_x;
        e -> d edge_y;

        e -> f edge_x;
        f -> e edge_y;

        f -> c edge_z;
    }
}

并使用简单的命令将其转换

m4 gv.m4 > gv.dot

现在包含了您定义的边缘

digraph {

    subgraph cluster_1 {
        a -> b [label="type x", color=red, style=solid];
        b -> a [label="type y", color=green, style=dashed];

        b -> c [label="type x", color=red, style=solid];
        c -> b [label="type y", color=green, style=dashed];

        c -> d [label="type z", color=blue, style=dotted];
    }

    subgraph cluster_2 {
        d -> e [label="type x", color=red, style=solid];
        e -> d [label="type y", color=green, style=dashed];

        e -> f [label="type x", color=red, style=solid];
        f -> e [label="type y", color=green, style=dashed];

        f -> c [label="type z", color=blue, style=dotted];
    }
}

并生成预期的图形:

enter image description here

您可以在m4中做更多的事情 - 这是graphViz中缺少的,例如维护和(甚至有条件地)包含子文件。例如,如果将两个子图放入两个单独的文件gv1.txtgv2.txt中,这将非常有效:

digraph incl
{
    define(`edge_x',`[label="type x", color=red, style=solid]')
    define(`edge_y',`[label="type y", color=green, style=dashed]')
    define(`edge_z',`[label="type z", color=blue, style=dotted]')
    include(gv1.txt)
    include(gv2.txt)
     e -> d[ color = yellow, label = "this is new!"];
}

enter image description here


{btsdaf} - Ed Morton
这确实是所需的语法。代码是从生成图表的实际测试中复制并粘贴的。 - vaettchen

8

虽然不是答案,但希望能给您提供思路。我认为在graphviz中没有命名标签,但你可以为以下边定义默认标签,如果您的工作流允许在一个地方定义所有边,这样做会很有效。例如:

digraph rs
{
    node[ shape = box, style = rounded]

    edge[ label = "pull" ];
    { A B } -> C;
    G -> H;
    C -> D[ label = "stop" ];
    edge[ label = "push"];
    D -> { E F };
    edge[ color = red, fontcolor = red ];
    { E F } -> G;
}

产生的结果是

这里输入图片描述

我还尝试使用你的图表进行实现

digraph fan 
{
    splines = ortho;
    node [ shape=box ]

    edge [ xlabel = "Pull", minlen = 4 ];
    { rank = same; OFF  -> HIGH -> LOW; }
    LOW:s -> OFF:s;
}

这将产生:

输入图像描述

所以它看起来很好,但是通过所有的调整很难扩展。


谢谢您的建议。从技术上讲,我可以按照您的建议定义边缘特征,然后列出该类型的所有转换,但现在大多数节点之间都存在双向边(例如Off->On和On->Off),而且我有子图,其中边缘类型A在多个子图中出现,因此我需要在每个子图中显式地列出节点,将内部边缘定义提取出来,并且我会失去对于每对节点的to/from边缘相邻性检查的可读性对称性。 - Ed Morton
2
是的,我认为它比风扇的事情更复杂... -- 你知道 gvpr 吗?对我来说太高级了,但你可能会找到将其用于你的方式。 - vaettchen
"gvpr" 很有趣,感谢您的建议。我一直在考虑编写一个 awk 程序来预处理带有边缘类型定义的类似 dot 的文件(如果 dot 不支持原生的话),但也许使用 gvpr 会是更强大的方法。我需要多做一些阅读... - Ed Morton
看起来虽然这不是我所希望的答案,但它似乎是正确的答案,所以我接受了它,再次感谢! - Ed Morton

1

我在我的电脑上下载m4时遇到了困难,因此选择使用通过Python API使用Graphviz,您可以定义一个字典样式,并根据需要应用于节点/边。

import graphviz

dot = graphviz.Digraph(comment='Test File')


nodeAttr_statement = dot.node_attr = {"shape": 'box', "style": 'filled', "fillcolor":"red"}
nodeAttr_question = dot.node_attr = {"shape": 'diamond', "style": 'filled', "fillcolor":"blue"}

dot.edge_attr

edge_Attr_sample = dot.edge_attr = {"arrowhead":'vee',"color":"yellow"}
edge_Attr_sample2 = dot.edge_attr = {"arrowhead": 'diamond', "color": "green"}


dot.node("A", "A", nodeAttr_statement)
dot.node("B", "B", nodeAttr_question )



dot.edge("A", "B", _attributes=edge_Attr_sample)
dot.edge("B", "A", _attributes=edge_Attr_sample2)
dot.format = 'pdf'
dot.render('test', view=True)

输出

// Test File
digraph {
    node [fillcolor=blue shape=diamond style=filled]
    edge [arrowhead=diamond color=green]
    A [label=A fillcolor=red shape=box style=filled]
    B [label=B fillcolor=blue shape=diamond style=filled]
    A -> B [arrowhead=vee color=yellow]
    B -> A [arrowhead=diamond color=green]
}

Python脚本输出图片



网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接