Как я могу создать именованные ребра "типы" в Graphviz/ точка / Neato?

Мне нужно нарисовать диаграмму с помощью графика / точки, где есть общие типы ребер между узлами, и я пытаюсь найти способ определить метку для каждого типа ребра, а затем использовать эту метку несколько раз на диаграмме.

Например, представьте пример традиционного потолочного вентилятора 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", используемый в разных контекстах на диаграмме, всегда имеет одинаковые атрибуты.

Я ожидал, что точка будет иметь синтаксис, который позволит мне определять имена для моих типов ребер, что-то вроде:

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

Так что, хотя это решило бы проблему, о которой я спрашивал, и я ценю это предложение, я не уверен, что оно того стоит, так как в итоге вы получите эквивалент программы на С, где вам нужно было определить все свои переменные вне функций и организовать их по типу, а не логические ассоциации.

Для ясности, учитывая приведенный выше пример, то, на что я действительно надеялся, выглядело бы следующим образом, если бы существовало такое ключевое слово определения "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;
    }
}

3 ответа

Решение

Не совсем ответ, но "пища для размышлений", так как я не думаю, что именные ярлыки существуют в 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 (спасибо Саймону). Используя и адаптируя ваш пример, я создал файл под названием 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.txt а также gv2.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 на свою машину, поэтому решил использовать graphviz через API Python, где вы можете определить стиль как словарь и применить его к узлам/ребрам по желанию.

      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

Другие вопросы по тегам