Как я могу создать именованные ребра "типы" в 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"];
}
НО я не хочу продолжать указывать одну и ту же текстовую метку каждый раз, потому что
- Мои ярлыки могут быть довольно длинными, поэтому они подвержены ошибкам, и
- Мои края имеют другие атрибуты, такие как цвет в дополнение к метке, и
- У меня есть выбор из нескольких различных типов ребер, поэтому я хочу убедиться, что тип ребра "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]
}