Лекция 1.6. Графвиз
на выходе будет
<graph> digraph G{ Рождение->Юность->Зрелость->Старость->Смерть; Юность->Смерть; Зрелость->Смерть; } </graph>
Graphviz — это разработанный специалистами лаборатории AT&T пакет утилит по автоматической визуализации графов, заданных в виде текстового описания. Пакет распространяется с открытыми исходными файлами и работает на всех операционных системах, включая Windows, Linux/Unix, Mac OS. Самой интересной программой пакета является «dot», автоматический визуализатор направленных графов, который принимает на вход текстовый файл со структурой графа, а на выходе формирует граф в виде графического, векторного или текстового файла.
Содержание
Быстрый старт
<graphviz> digraph G{ "Коржуков Валентин Григорьевич" -> "Коржуков Максим Валентинович" [label="отец"]; "Миронова Татьяна Рудольфовна" -> "Коржуков Максим Валентинович" [label="мать"]; } </graphviz>
Входной файл для программы «DOT» является обычным текстовым файлом на специальном языке разметки графа. Структура файла очень простая, например,
digraph G{ Рождение->Юность->Зрелость->Старость->Смерть; Юность->Смерть; Зрелость->Смерть; }
на выходе будет
<graph>
digraph G{ Рождение->Юность->Зрелость->Старость->Смерть; Юность->Смерть; Зрелость->Смерть; }
</graph>
Программа «Dot» сама распознает все связи графа и упорядочит его таким образом, чтобы было наименьшее количество пересечений.
Чтобы использовать «dot»-графы в Wiki, используйте следующий синтаксис:
<graph> digraph G{ Рождение->Юность->Зрелость->Старость->Смерть; Юность->Смерть; Зрелость->Смерть; } </graph>
Если у вас узлы поименованы словосочетаниями, заключите их в кавычки, т. е.
<graph> digraph G{ "Полет фантазии"->"Расход горючего"; } </graph>
Поздравляем! Теперь вы способны рисовать графы в Wiki. Остальной текст будет посвящен некоторым тонкостям использования Graphviz.
Внешний вид графа
«Dot» позволяет изменять внешний вид графа. Например, можно изменять форму фигур (прямоугольники, овалы, круги, параллелограммы, многоугольники), цвет и шрифт текста, цвет фона фигур, стиль стрелок и рамок фигур, подписи стрелок и т. д. Итак, основные объектами являются узлы («node») и ребра («edge»). Для того, чтобы настроить свойства всех узлов или ребер нужно вначале использовать команды
node[свойство1="значение1",свойство2="значение2",...] edge[свойство1="значение1",свойство2="значение2",...]
Также (в квадратных скобках после описания объекта) можно изменять настройки конкретного узла или ребра. Параметры графа, просто задаются в виде параметр=значение. Полезно запомнить параметр «rankdir», он может быть «TB» (top->bottom, параметр по умолчанию), или «LR» (left->right), и определяет, сверху-вниз, или справа-налево, нужно располагать узлы графа. Вот пестрый пример:
digraph G{ rankdir=LR; node[color="red",fontsize=14]; edge[color="darkgreen",fontcolor="blue",fontsize=12]; OPEN[shape="rectangle",style="filled",fillcolor="lightgrey"]; CLOSED[shape="octagon",label="Финиш"]; VERIFIED[shape="rectangle",style="rounded"]; OPEN->RESOLVED->VERIFIED->CLOSED; OPEN->CLOSED[style="bold"]; VERIFIED->OPEN[label="обнаружены ошибки",style="dashed",arrowhead="dot"]; }
на выходе будет
<graphviz> digraph G{ rankdir=LR; node[color="red",fontsize=14]; edge[color="darkgreen",fontcolor="blue",fontsize=12]; OPEN[shape="rectangle",style="filled",fillcolor="lightgrey"]; CLOSED[shape="octagon",label="Финиш"]; VERIFIED[shape="rectangle",style="rounded"]; OPEN->RESOLVED->VERIFIED->CLOSED; OPEN->CLOSED[style="bold"]; VERIFIED->OPEN[label="обнаружены ошибки",style="dashed",arrowhead="dot"]; } </graphviz>
Если предполагается, что граф будут не только просматривать через IE, но и печатать, то необходимо установить ширину картинки, иначе при печати картинка будет обрезана. Для этого следует задать внутри описания
size="6.7,15";
Существенна только первая цифра. Число 6.7 подобрано эмпирически, оно обеспечивает печать полной картинки при настройках IE по умолчанию.
Уровни в графах
В «Dot» присутствует возможность связать узлы графа не только стрелками, но и уровнями отображения, что позволяет создавать шкалу и располагать узлы графа соответственно данной шкале. Для связывания используется следующая конструкция:
{ rank = same; "элемент уровня"; "элемент для привязки 1"; "элемент для привязки 2"; ..}
Например, при использовании следующей конструкции:
<graph> digraph G{ node[fontsize=9]; { /* шкала месяцев*/ node[shape=plaintext]; /* что бы не было видно рамок */ edge[color=white] /* что бы не было видно стрелок */ "март" -> "июнь" -> "сентябрь" -> "декабрь"; } { rank = same; "март"; "весна"; "a"; } { rank = same; "июнь"; "лето";} { rank = same; "сентябрь"; "осень"; "d"; } { rank = same; "декабрь"; "зима"; "e"} "весна" -> "лето" -> "осень" -> "зима" -> "весна" "a" -> "b" -> "c" -> "d" -> "e" ; } </graph>
на выходе получается: <graph> digraph G{
node[fontsize=9]; { /* шкала месяцев*/ node[shape=plaintext]; /* что бы не было видно рамок */ edge[color=white] /* что бы не было видно стрелок */ "март" -> "июнь" -> "сентябрь" -> "декабрь"; } { rank = same; "март"; "весна"; "a"; } { rank = same; "июнь"; "лето";} { rank = same; "сентябрь"; "осень"; "d"; } { rank = same; "декабрь"; "зима"; "e"} "весна" -> "лето" -> "осень" -> "зима" -> "весна" "a" -> "b" -> "c" -> "d" -> "e" ; }
</graph>
Многосекционный узлы
Dot позволяет создавать многосекционные узлы при это каждая секция может быть поименована, и тогда ребра можно продоводить между секциями и узлами.
Для включения режима многосекционности устанавливается атрибут узла shape.
shape=record;
Секции описываются в атрибуте label узла, с помощью разделителя «|». Для именования секции ее имя указывается в <>. При описание ребра, исходящего или входящего в секцию, секция именуется следующим образом:
элемент:<имя_секции>
Например, из такого описания:
digraph structs { rankdir=HR; first [shape=record,label=" x1\n all | { x21 | <f0> x22| x23} | x3" ]; second [shape=record,label=" x22_1 | x22_2 | x22_3"]; first:<f0> -> second; }
Получается следующее:
<graph>
digraph structs { rankdir=HR; first [shape=record,label=" x1\n all | { x21 | <f0> x22| x23} | x3" ]; second [shape=record,label=" x22_1 | x22_2 | x22_3"]; first:<f0> -> second; }
</graph>
Гиперссылки на графах
Можно использовать атрибут «URL», задавая относительные или абсолютные гиперссылки для узлов и ребер. Например
<graph> digraph G { rankdir=LR; SGML [URL="SGML"]; HTML [URL="HTML"]; XML [URL="XML"]; XHTML [URL="http://www.w3schools.com/xhtml/"]; SGML->HTML; SGML->XML; HTML->XHTML; XML->XHTML; SGML->XHTML[color="red",fontcolor="blue",label="ссылка на Google",URL="http://www.google.com"]; } </graph>
<graph>
digraph G { rankdir=LR; SGML [URL="SGML"]; HTML [URL="HTML"]; XML [URL="XML"]; XHTML [URL="http://www.w3schools.com/xhtml/"]; SGML->HTML; SGML->XML; HTML->XHTML; XML->XHTML; SGML->XHTML[color="red",fontcolor="blue",label="ссылка на Google",URL="http://www.google.com"];
} </graph>
Кластеры в графах
Программа «Dot» позволяет объединять узлы графов в кластеры для подчеркивания общности.
Кластер описывается следующим синтаксисом:
subgraph имя{ свойство1 = "значение1",свойство2="значение2",... узел1; узел2; ... }
При этом имя подграфа должно начинаться с префикса cluster, иначе подграф не позволяет себя отобразить на экран(раскраска, контур, подпись, .. ).
Например:
digraph G { rankdir=LR; subgraph cluster0 { node [style=filled,color=white]; style=filled; color=lightgrey; a0; a1 label = "process #1"; } subgraph cluster1 { node [style=filled]; b0; label = "process #2"; color=blue } start -> a0; start -> b0; a0 -> a1 -> end; b0 -> end; }
<graph>
digraph G { rankdir=LR; subgraph cluster0 { node [style=filled,color=white]; style=filled; color=lightgrey; a0; a1 label = "process #1"; } subgraph cluster1 { node [style=filled]; b0; label = "process #2"; color=blue } start -> a0; start -> b0; a0 -> a1 -> end; b0 -> end; }
</graph>
Цвета и черно-белая печать
Graphviz позволяет использовать широкую цветовую палитру, однако, стоит не забывать, что контрастно выглядящие на цветном мониторе цвета, могут быть совершенно неразличимы после черно-белой печати. После проделанных экспериментов (Шаблон:Bug), можно рекомендовать следующие палитры цветов (иллюстрированы на цвете ребер графа):
<graph-print> digraph G{ rankdir=TB; size="7,6";
Палитра1->goldenrod1 [color=goldenrod1] Палитра1->green [color=green] Палитра1->sienna4 [color=sienna4] Палитра1->red1 [color=red1] Палитра1->blue2 [color=blue2]
Палитра2->lightcyan2 [color=lightcyan2] Палитра2->pink2 [color=pink2] Палитра2->green [color=green] Палитра2->sienna4 [color=sienna4] Палитра2->red2 [color=red2] Палитра2->black1 [color=black1] }
</graph-print>
Формы вершин
Перечислим палитру возможных форм вершин (узлов).
<neato> digraph G{
edge [arrowtail="none"] node [style=filled, colorscheme="brbg9"]; "box" [shape="box" fillcolor="1"]; "polygon" [shape="polygon" fillcolor="2"]; "ellipse" [shape="ellipse" fillcolor="3"]; "circle" [shape="circle" fillcolor="4"]; "point" [shape="point" fillcolor="black"]; "egg" [shape="egg" fillcolor="6"]; "triangle" [shape="triangle" fillcolor="7"]; "plaintext" [shape="plaintext" fillcolor="8"]; "diamond" [shape="diamond" fillcolor="9"]; "trapezium" [shape="trapezium" fillcolor="1"]; "parallelogram" [shape="parallelogram" fillcolor="2"]; "house" [shape="house" fillcolor="3"]; "pentagon" [shape="pentagon" fillcolor="4"]; "hexagon" [shape="hexagon" fillcolor="5"]; "septagon" [shape="septagon" fillcolor="6"]; "octagon" [shape="octagon" fillcolor="7"]; "doublecircle" [shape="doublecircle" fillcolor="8"]; "doubleoctagon" [shape="doubleoctagon" fillcolor="9"]; "tripleoctagon" [shape="tripleoctagon" fillcolor="1"]; "invtriangle" [shape="invtriangle" fillcolor="1"]; "invtrapezium" [shape="invtrapezium" fillcolor="2"]; "invhouse" [shape="invhouse" fillcolor="3"]; "Mdiamond" [shape="Mdiamond" fillcolor="4"]; "Msquare" [shape="Msquare" fillcolor="5"]; "Mcircle" [shape="Mcircle" fillcolor="6"]; "rect/rectangle" [shape="rect" fillcolor="7"]; "none" [shape="none" fillcolor="8"]; "note" [shape="note" fillcolor="9"]; "tab" [shape="tab" fillcolor="1"]; "folder" [shape="folder" fillcolor="2"]; "box3d" [shape="box3d" fillcolor="3"]; "component" [shape="component" fillcolor="4"];
} </neato>
Окончания ребер
Можно задавать стиль офомления начала («arrowtail») и конца («arrowhead») дуг (ребер):
<circo> digraph G{
edge [arrowtail="none"] A [label="Arrowhead" style=filled fillcolor="yellow"]; A->"normal" [arrowhead="normal"]; A->"dot" [arrowhead="dot"]; A->"odot" [arrowhead="odot"]; A->"none" [arrowhead="none"]; A->"empty" [arrowhead="empty"]; A->"diamond" [arrowhead="diamond"]; A->"ediamond" [arrowhead="ediamond"]; A->"box" [arrowhead="box"]; A->"open" [arrowhead="open"]; A->"vee" [arrowhead="vee"]; A->"inv" [arrowhead="inv"]; A->"invdot" [arrowhead="invdot"]; A->"invodot" [arrowhead="invodot"]; A->"tee" [arrowhead="tee"]; A->"invempty" [arrowhead="invempty"]; A->"odiamond" [arrowhead="odiamond"]; A->"crow" [arrowhead="crow"]; A->"obox" [arrowhead="obox"]; A->"halfopen" [arrowhead="halfopen"];
} </circo>
Неориентированные графы
Наряду с рисованием ориентированных графов, есть несколько методов для автоматического рисования неориентированных графов (будем рассматривать их на примере несложной ER-диаграммы).
В отличие от автоматического рисования направленных («directed») графов, основанных на ранговой модели, есть несколько подходов к раскладке ненаправленных графов.
Graph
Ненаправленный граф можно нарисовать с помощью рангового подхода (несмотря на ненаправленность ребер) — будет использоваться программа «dot». Как это будет выглядеть для простой ER-диаграммы, можно увидеть ниже. <graph> graph ER {
node [fontsize=12]; node [shape=box]; course; institute; student; node [shape=ellipse]; {node [label="name"] name0; name1; name2;} code; grade; number; node [shape=diamond,style=filled,color=lightgrey]; "C-I"; "S-C"; "S-I"; name0 -- course; code -- course; course -- "C-I" [label="n",len=1.00]; "C-I" -- institute [label="1",len=1.00]; institute -- name1; institute -- "S-I" [label="1",len=1.00]; "S-I" -- student [label="n",len=1.00]; student -- grade; student -- name2; student -- number; student -- "S-C" [label="m",len=1.00]; "S-C" -- course [label="n",len=1.00]; label = "\n\nEntity Relation Diagram\ndrawn by DOT";
} </graph>
Очевидна неоптимальность такого подхода для неориентированных графов.
Neato
Метод «neato» использует «энергетическую» (spring) модель, по сути, близкую к методу искуственного отжига — начиная с некоторого состояния вершины перемещаются, чтобы минимизировать некую потенциальную энергию. Рекомендуем для ненаправленных графов общего вида.
<neato> graph ER {
node [fontsize=12]; node [shape=box]; course; institute; student; node [shape=ellipse]; {node [label="name"] name0; name1; name2;} code; grade; number; node [shape=diamond,style=filled,color=lightgrey]; "C-I"; "S-C"; "S-I"; name0 -- course; code -- course; course -- "C-I" [label="n",len=1.00]; "C-I" -- institute [label="1",len=1.00]; institute -- name1; institute -- "S-I" [label="1",len=1.00]; "S-I" -- student [label="n",len=1.00]; student -- grade; student -- name2; student -- number; student -- "S-C" [label="m",len=1.00]; "S-C" -- course [label="n",len=1.00]; label = "\n\nEntity Relation Diagram\ndrawn by NEATO";
} </neato>
FDP
Метод «fdp» по сути, близок к методу «neato», и использует другую разновидность «энергетического» («spring») подхода. Также рекомендуется для ненаправленных графов общего типа.
<fdp> graph ER {
node [fontsize=12]; node [shape=box]; course; institute; student; node [shape=ellipse]; {node [label="name"] name0; name1; name2;} code; grade; number; node [shape=diamond,style=filled,color=lightgrey]; "C-I"; "S-C"; "S-I"; name0 -- course; code -- course; course -- "C-I" [label="n",len=1.00]; "C-I" -- institute [label="1",len=1.00]; institute -- name1; institute -- "S-I" [label="1",len=1.00]; "S-I" -- student [label="n",len=1.00]; student -- grade; student -- name2; student -- number; student -- "S-C" [label="m",len=1.00]; "S-C" -- course [label="n",len=1.00]; label = "\n\nEntity Relation Diagram\ndrawn by FDP";
} </fdp>
Twopi
Метод «twopi» рисует графы с радиальной раскладкой. По сути одна вершина выбирается центральной, и помещается в центр, а остальные размещаются на последовательности концентрических орбит, вокруг этой вершины. Т.е. все вершины на расстоянии в «одно ребро» от центра, лежат на первой орбите, «в два ребра» — на второй и т. д.
<twopi> graph ER {
node [fontsize=12]; node [shape=box]; course; institute; student; node [shape=ellipse]; {node [label="name"] name0; name1; name2;} code; grade; number; node [shape=diamond,style=filled,color=lightgrey]; "C-I"; "S-C"; "S-I"; name0 -- course; code -- course; course -- "C-I" [label="n",len=1.00]; "C-I" -- institute [label="1",len=1.00]; institute -- name1; institute -- "S-I" [label="1",len=1.00]; "S-I" -- student [label="n",len=1.00]; student -- grade; student -- name2; student -- number; student -- "S-C" [label="m",len=1.00]; "S-C" -- course [label="n",len=1.00]; label = "\n\nEntity Relation Diagram\ndrawn by TWOPI";
} </twopi>
CIRCO
Метод «circo» использует «circular layout». Выделяются двусвязные компоненты (каждая вершина имеет по крайней мере два ребра) и вершины этих компонент рисуются на некотором круге. «Дополнительные» ребра рисуются радиально и далее процесс повторяется. Пересечение ребер внутри круга минимизируется максимально возможным выносом ребер с круга за его периметр.
<circo> graph ER {
node [fontsize=12]; node [shape=box]; course; institute; student; node [shape=ellipse]; {node [label="name"] name0; name1; name2;} code; grade; number; node [shape=diamond,style=filled,color=lightgrey]; "C-I"; "S-C"; "S-I"; name0 -- course; code -- course; course -- "C-I" [label="n",len=1.00]; "C-I" -- institute [label="1",len=1.00]; institute -- name1; institute -- "S-I" [label="1",len=1.00]; "S-I" -- student [label="n",len=1.00]; student -- grade; student -- name2; student -- number; student -- "S-C" [label="m",len=1.00]; "S-C" -- course [label="n",len=1.00]; label = "\n\nEntity Relation Diagram\ndrawn by CIRCO";
} </circo>
Версии для печати
Как известно, трудно добиться хорошего результата одновременно на экране и на принтере, в силу разных разрешений. Картинка экранного разрешения будет плохо (с «зазубринами») выглядеть на принтере, а картинка печатного разрешения, будет очень плохо выглядеть на экране (к сожалению, современные броузеры выполняют очень примитивный ресайзинг картинок при показе), и будет достаточно много «весить». Все соображения о печатных картинках также относятся к случаю, когда вы переносите (например, копируя вебстраницу из броузера через клипборд) содержимое MediaWiki-статьи в MS Word или другой текстовый редактор. Для такого, «печатного» случая (т. е. если у вас не примитивные графы, и вы собираетесь их печатать или переносить в другую систему верстки), мы сделали «печатную версию» всех перечисленных графов, с разрешением около 200 DPI. Для этого надо использовать те же самые тэги с постфиксом «-print», например «graph-print»,«neato-print», и т.п.:
<graph-print> digraph G{
rankdir=LR; node[color="red",fontsize=14]; edge[color="darkgreen",fontcolor="blue",fontsize=12]; OPEN[shape="rectangle",style="filled",fillcolor="lightgrey"]; CLOSED[shape="octagon",label="Финиш"]; VERIFIED[shape="rectangle",style="rounded"]; OPEN->RESOLVED->VERIFIED->CLOSED; OPEN->CLOSED[style="bold"]; VERIFIED->OPEN[label="обнаружены ошибки",style="dashed",arrowhead="dot"]; }
</graph-print>
Полученные картинки являются компромиссом, между весом, читаемостью на экране и читаемостью на бумаге. Желательно не использовать для совершенно тривиальных графов, или графов, которых вы не собираетесь печатать.