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

Ed *_*ton 5 dot graphviz neato

I need to draw a diagram with graphviz/dot where there are common edge types between nodes and am trying to find a way to define a label for each type of edge and then use that label multiple times in the diagram.

For example imagine the traditional ceiling fan FSM example where it's initially in state OFF and every time someone pulls the cord it changes to a new state based on the speed of the fan:

     Pull         Pull        Pull
OFF ------> HIGH ------> MED ------> LOW
 ^                                    |
 |                Pull                |
 +------------------------------------+
Run Code Online (Sandbox Code Playgroud)

Every edge is named "Pull" and I can define that in dot by using:

digraph fan {
    OFF  -> HIGH [label="Pull"];
    HIGH -> MED  [label="Pull"];
    MED  -> LOW  [label="Pull"];
    LOW  -> OFF  [label="Pull"];
}
Run Code Online (Sandbox Code Playgroud)

BUT I don't want to keep specifying the same textual label every time because

  1. My labels can get quite long so that's error-prone, and
  2. My edges have other attributes like color in addition to label, and
  3. I have a selection of multiple different types of edge so I want to make SURE that edge type "A" used in different contexts in the diagram always has all the same attributes.

I expected dot to have a syntax that would let me define names for my edge types, something like:

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

    OFF  -> HIGH edge_a;
    HIGH -> MED  edge_a;
    MED  -> LOW  edge_a;
    LOW  -> OFF  edge_a;
}
Run Code Online (Sandbox Code Playgroud)

but of course what the really does is create a node named "Pull" and unlabeled edges.

I've been searching online for a few hours with no success. Anyone know how to define edge types up front to be used in multiple locations?

Update: @vaettchen had suggested defining an edge type then listing all of the transitions for that edge type, then defining the next edge type followed by it's transitions. While that would technically solve my problem, it would introduce a couple of others because my graphs today can look like:

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];
    }
}
Run Code Online (Sandbox Code Playgroud)

and to rearrange that by edge type I'd lose the immediate visual clarity in the code of having the bidirectional edges next to each other (a->b and b->a) and I'd have to explicitly list the nodes within each subgraph and I'd have to pull the subgraph-internal edge definitions up into the main graph:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

So while it would solve the problem I asked about and I appreciate the suggestion, I'm not sure it's worth the tradeoff as you end up with the equivalent of a C program where you had to define all of your variables outside of the functions and organize them by their type rather than logical associations.

To be clear, given the above example what I was really hoping for would look like the following if such an "edge_type" definition keyword existed:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

vae*_*hen 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;
}
Run Code Online (Sandbox Code Playgroud)

这产生

在此处输入图片说明

我也试过用你的图表来实现

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

    edge [ xlabel = "Pull", minlen = 4 ];
    { rank = same; OFF  -> HIGH -> LOW; }
    LOW:s -> OFF:s;
}
Run Code Online (Sandbox Code Playgroud)

产生

在此处输入图片说明

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

  • 是的,我认为它比风扇的事情更复杂...... - 你知道`gvpr`吗?这对我来说太过分了,但您可能会想办法让它为您服务。 (2认同)

vae*_*hen 8

我想我得到了你的解决方案,使用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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

m4 gv.m4 > gv.dot
Run Code Online (Sandbox Code Playgroud)

现在包含您定义的边缘

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];
    }
}
Run Code Online (Sandbox Code Playgroud)

并产生预期的图形:

在此处输入图片说明

你可以用 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!"];
}
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明