Лекция 1.6. Графвиз — различия между версиями

Материал из Wiki
Перейти к:навигация, поиск
Строка 532: Строка 532:
 
Полученные картинки являются компромиссом, между весом, читаемостью на экране и читаемостью на бумаге.
 
Полученные картинки являются компромиссом, между весом, читаемостью на экране и читаемостью на бумаге.
 
Желательно не использовать для совершенно тривиальных графов, или графов, которых вы не собираетесь печатать.
 
Желательно не использовать для совершенно тривиальных графов, или графов, которых вы не собираетесь печатать.
 +
 +
= Самостоятельной работы =
 +
Используя графвиз отобразите сферу ваших интересов и увлечений.
  
  

Версия 15:47, 3 июня 2009

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>

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

Самостоятельной работы

Используя графвиз отобразите сферу ваших интересов и увлечений.