如何在 Graphviz/dot/neato 中创建命名边 "types"?

How can I create named edge "types" in Graphviz/dot/neato?

我需要用 graphviz/dot 绘制一个图表,其中节点之间有共同的边缘类型,我正在尝试找到一种方法来为每种类型的边缘定义标签,然后在图表。

例如,想象一下传统的吊扇 FSM 示例,它最初处于关闭状态,每次有人拉动电源线时,它都会根据风扇的速度更改为新状态:

     Pull         Pull        Pull
OFF ------> HIGH ------> MED ------> LOW
 ^                                    |
 |                Pull                |
 +------------------------------------+

每条边都被命名为 "Pull",我可以使用以下方法在点中定义它:

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" 的节点和未标记的边。

我已经在线搜索了几个小时,但没有成功。有人知道如何预先定义边缘类型以用于多个位置吗?

更新: 定义一个边缘类型,然后列出该边缘类型的所有转换,然后定义下一个边缘类型,然后是它的转换。虽然这在技术上可以解决我的问题,但它会引入其他几个问题,因为我今天的图表可能如下所示:

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

不是真正的答案,但 "food for thought" 因为我认为 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;
}

产生

所以它看起来不错,但所有的调整都很难扩展。

我想我得到了你的解决方案,使用 m4 (thanks to 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];
    }
}

并生成预期的图形:

您可以使用 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!"];
}

我很难在我的机器上下载 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]
}

Output image from python script