Перейти к содержимому

Graphviz как пользоваться

  • автор:

Использование Graphviz для построения блок-схем

Мы создаем ПО для разработки и поддержки баз данных Oracle, и статический анализатор PL/SQL является одной из основных фич наших приложений. Кто знаком с Oracle, тот хорошо знает что такое PL/SQL.

Известная поговорка гласит: «Лучше один раз увидеть, чем сто раз услышать». Поэтому мы решили заимпрувить статический анализатор таким образом, чтобы он визуализировал код в виде блок-схем (Flowcharts) и диаграмм вызовов (Call Trees). Хоть и нарисовать блоки и их связи несложно, оптимизировать их расположение на «листе» представлялось задачей, требующей значительных усилий. Чтобы стрелки минимально пересекались и обтекали блоки, блоки объединялись в группы, и диаграмма при этом не превращалась в «кашу», нужно было потратить много сил и времени.

И тогда мы решили поискать готовое решение, дабы не изобретать велосипед. Наше внимание сразу привлек Graphviz – open source решение по визуализации диаграмм. Первые его версии были разработаны компанией AT&T, а теперь он доступен как набор утилит и библиотек, а также в исходниках под лицензией Eclipse Public License (EPL).

Его движок диаграмм использует язык описания графов DOT, который представляет собой текстовое описание структуры графа: вершины, их связи, группы и атрибуты для их визуального оформления.

Описание простейшего графа:

digraph graphname < a ->b; > 

Если необходимо добавить узлам атрибуты, например подписи, то необходимо отдельно описать узлы:

digraph graphname < a [label="source"] b [label="destination"] a ->b; > 

Далее покажу на примере простой процедуры на PL/SQL:

CREATE PROCEDURE WRITE_STRING( P_MESSAGE_TYPE IN NUMBER , P_MESSAGE IN VARCHAR2 ) IS BEGIN IF P_MESSAGE_TYPE >= LOG_LEVEL THEN ADD_LOG_RECORD(P_MESSAGE_TYPE, P_MESSAGE); END IF; END WRITE_STRING; 

Код понятен, даже если вы не знакомы с синтаксисом PL/SQL.

А теперь опишем этот код на языке DOT. Пояснения снова излишни:

digraph G < N0 [label="”] N1_COND [label ]; N2 [label="ADD_LOG_RECORD(P_MESSAGE_TYPE, P_MESSAGE);"]; EXIT_LABEL [label="END"]; N0 ->N1_COND N1_COND -> N2 N1_COND -> EXIT_LABEL N2 -> EXIT_LABEL > 

Теперь можно «скормить» этот файл Graphviz, подставив соответственно вместо %PNGFILE% и %DOTFILE% имена выходного (png) и входного (dot) файлов:

dot -v -Tpng –o%PNGFILE% %DOTFILE% 

При помощи Graphviz из описания выше получается такая картина:

Наглядно, но весьма аскетично.

Можно сделать диаграмму привлекательнее, добавив атрибуты для определения формы блоков (shape=diamond) , подписи стрелок (label=»Yes») и цвета color , fontcolor .

Также мы можем объединить в подграф блоки, которые являются исполняемыми. Это реализуется конструкцией subgraph <> , внутри которой мы можем перечислить имена блоков, включенных в подграф, и указать атрибуты для визуального оформления (цвет рамки).

digraph G < comment="PL/SQL flowchart generated by ClearSQL"; node [fontname="Arial", fontsize=8, height=.2, width=.25, color="#000000", fontcolor="#000000"]; edge [fontname="Arial", fontsize=8, arrowsize=.8, color="#000000", fontcolor="#000000"]; N0 [shape=plaintext, label="", style=invis]; N1_COND [shape=diamond, conditional=true, label ]; N2 [shape=box, label="ADD_LOG_RECORD(P_MESSAGE_TYPE, P_MESSAGE);"]; EXIT_LABEL [shape=Msquare, label="END"]; N0 ->N1_COND [color=darkgoldenrod]; N1_COND -> N2 [label=" Yes " color="#228b22" fontcolor="#228b22"]; N1_COND -> EXIT_LABEL [label=" No " color="#ff7e40" fontcolor="#ff7e40"]; N2 -> EXIT_LABEL subgraph "cluster_#1" < color="#0000ff"; N1_COND; N2 >> 

Получается вполне симпатичная блок-схема, которую и людям показать не стыдно.

Чтобы код могли читать не только программисты, но и «простые смертные», мы придумали такую фишку: приделали распознавание специальных тегов в комментариях, которые бы разработчик писал как псевдокод – язык описания алгоритмов. Это помогло увеличить ценность блок-схемы, которая теперь может отображать бизнес-логику, а не только визуализировать листинг.

CREATE PROCEDURE WRITE_STRING( P_MESSAGE_TYPE IN NUMBER , P_MESSAGE IN VARCHAR2 ) IS BEGIN --## Уровень данного сообщения по отношению к порогу журналирования --##@true Выше --##@false Ниже IF P_MESSAGE_TYPE >= LOG_LEVEL THEN --## Добавляем запись в журнал ADD_LOG_RECORD(P_MESSAGE_TYPE, P_MESSAGE); END IF; END WRITE_STRING; 

Вуаля! Graphviz дает возможность делать потрясающие визуализации с минимальными затратами.

Graphviz

Graphviz — это разработанный специалистами лаборатории AT&T пакет утилит по автоматической визуализации графов, заданных в виде текстового описания. Пакет распространяется с открытыми исходными файлами и работает на всех операционных системах, включая Windows, Linux/Unix, Mac OS. Самой интересной программой пакета является «dot», автоматический визуализатор направленных графов, который принимает на вход текстовый файл со структурой графа, а на выходе формирует граф в виде графического, векторного или текстового файла.

Быстрый старт

Входной файл для программы «DOT» является обычным текстовым файлом на специальном языке разметки графа. Структура файла очень простая, например,

 digraph G< Рождение->Юность->Зрелость->Старость->Смерть; Юность->Смерть; Зрелость->Смерть; > 

на выходе будет

Программа «Dot» сама распознает все связи графа и упорядочит его таким образом, чтобы было наименьшее количество пересечений.

Чтобы использовать «dot»-графы в CustisWiki, используйте следующий синтаксис:

 digraph G< Рождение->Юность->Зрелость->Старость->Смерть; Юность->Смерть; Зрелость->Смерть; > 

Если у вас узлы поименованы словосочетаниями, заключите их в кавычки, т. е.

 digraph G< "Полет фантазии"->"Расход горючего"; > 

Поздравляем! Теперь вы способны рисовать графы в CustisWiki. Остальной текст будет посвящен некоторым тонкостям использования 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"]; >

на выходе будет

Если предполагается, что граф будут не только просматривать через IE, но и печатать, то необходимо установить ширину картинки, иначе при печати картинка будет обрезана. Для этого следует задать внутри описания

size="6.7,15";

Существенна только первая цифра. Число 6.7 подобрано эмпирически, оно обеспечивает печать полной картинки при настройках IE по умолчанию.

Уровни в графах

В «Dot» присутствует возможность связать узлы графа не только стрелками, но и уровнями отображения, что позволяет создавать шкалу и располагать узлы графа соответственно данной шкале. Для связывания используется следующая конструкция:

Например, при использовании следующей конструкции:

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

на выходе получается:

Многосекционный узлы

Dot позволяет создавать многосекционные узлы при это каждая секция может быть поименована, и тогда ребра можно продоводить между секциями и узлами.

Для включения режима многосекционности устанавливается атрибут узла shape.

shape=record;

Секции описываются в атрибуте label узла, с помощью разделителя «|». Для именования секции ее имя указывается в <>. При описание ребра, исходящего или входящего в секцию, секция именуется следующим образом:

элемент:

Например, из такого описания:

digraph structs < rankdir=HR; first [shape=record,label=" x1\n all | < x21 | x22| x23> | x3" ]; second [shape=record,label=" x22_1 | x22_2 | x22_3"]; first: -> second; >

Гиперссылки на графах

Можно использовать атрибут «URL», задавая относительные или абсолютные гиперссылки для узлов и ребер. Например

 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"]; > 

Кластеры в графах

Программа «Dot» позволяет объединять узлы графов в кластеры для подчеркивания общности.

Кластер описывается следующим синтаксисом:

subgraph имя

При этом имя подграфа должно начинаться с префикса 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; >

Цвета

Graphviz позволяет использовать широкую цветовую палитру, задавая цвета или по именам, в одной из известных палитр:

  • Палитра X11
  • SVG
  • Набор палитр Brewer-а, что удобно при автоматической генерации схем — задаваемые числовым индексом цвета в более-менее вменяемой палитре.

Кроме именованных цветов, можно использовать обычное трехбайтное шестнадцатиричное кодирование

color="#FF0EDD"

и выбирать цвета из палитры, например, на http://www.colorpicker.com/

Цвета и черно-белая печать

Graphviz позволяет использовать широкую цветовую палитру, однако, стоит не забывать, что контрастно выглядящие на цветном мониторе цвета, могут быть совершенно неразличимы после черно-белой печати. После проделанных экспериментов (Bug:11015), можно рекомендовать следующие палитры цветов (иллюстрированы на цвете ребер графа):

Формы вершин

Перечислим палитру возможных форм вершин (узлов).

Окончания ребер

Можно задавать стиль офомления начала («arrowtail») и конца («arrowhead») дуг (ребер):

Чтобы работал arrowtail, для ребра нужно указать свойство dir=both или dir=back.

Неориентированные графы

Наряду с рисованием ориентированных графов, есть несколько методов для автоматического рисования неориентированных графов (будем рассматривать их на примере несложной ER-диаграммы).

В отличие от автоматического рисования направленных («directed») графов, основанных на ранговой модели, есть несколько подходов к раскладке ненаправленных графов.

Graph

Ненаправленный граф можно нарисовать с помощью рангового подхода (несмотря на ненаправленность ребер) — будет использоваться программа «dot». Как это будет выглядеть для простой ER-диаграммы, можно увидеть ниже.

Очевидна неоптимальность такого подхода для неориентированных графов.

Neato

Метод «neato» использует «энергетическую» (spring) модель, по сути, близкую к методу искуственного отжига — начиная с некоторого состояния вершины перемещаются, чтобы минимизировать некую потенциальную энергию. Рекомендуем для ненаправленных графов общего вида.

FDP

Метод «fdp» по сути, близок к методу «neato», и использует другую разновидность «энергетического» («spring») подхода. Также рекомендуется для ненаправленных графов общего типа.

Twopi

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

CIRCO

Метод «circo» использует «circular layout». Выделяются двусвязные компоненты (каждая вершина имеет по крайней мере два ребра) и вершины этих компонент рисуются на некотором круге. «Дополнительные» ребра рисуются радиально и далее процесс повторяется. Пересечение ребер внутри круга минимизируется максимально возможным выносом ребер с круга за его периметр.

Версии для печати

Как известно, трудно добиться хорошего результата одновременно на экране и на принтере, в силу разных разрешений. Картинка экранного разрешения будет плохо (с «зазубринами») выглядеть на принтере, а картинка печатного разрешения, будет очень плохо выглядеть на экране (к сожалению, современные броузеры выполняют очень примитивный ресайзинг картинок при показе), и будет достаточно много «весить». Все соображения о печатных картинках также относятся к случаю, когда вы переносите (например, копируя вебстраницу из броузера через клипборд) содержимое MediaWiki-статьи в MS Word или другой текстовый редактор. Для такого, «печатного» случая (т. е. если у вас не примитивные графы, и вы собираетесь их печатать или переносить в другую систему верстки), мы сделали «печатную версию» всех перечисленных графов, с разрешением около 200 DPI. Для этого надо использовать те же самые тэги с постфиксом «-print», например «graph-print»,«neato-print», и т.п.:

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

Ссылки и дополнительная документация

Онлайн-документация, +последние изменения, FAQ и прочее можно найти на домашней странице пакета http://www.graphviz.org/Documentation.php

Любые правки этой статьи будут перезаписаны при следующем сеансе репликации. Если у вас есть серьезное замечание по тексту статьи, запишите его в раздел «discussion».

Graphviz как пользоваться

Внимание!
Автор темы совместно с Администрацией портала просит писать в эту тему только относящееся к Graphviz. Благодарности, разговоры о погоде, искромётный юмор будут безжалостно удаляться. Все «чмоки», пожалуйста, во флейме. Надеюсь на понимание.

Пример дерева решений, построенного с помощью Graphviz. Использовалось для иллюстрации в теме.

1. Устанавливаем программу
2. Создаем файл test.dot

digraph dtree < node [fontsize=8, shape=box]; snake [label="Встретили змею?", style=filled, fillcolor=grey89]; snakeNo [label="p=0,9 (Нет)\n---------\nЗмею не встретили!", shape=plaintext]; snakeNo1 [label="p=0,9\n---------\np*2000*50=90000", style=filled, fillcolor=green]; gender [label="p=0,1 (Да)\n---------\nЭто была девочка?", shape=plaintext]; boy [label="p=0,4 (Мальчик)\n---------\nИспугался?", shape=plaintext]; girl [label="p=0,6 (Девочка)\n---------\nИспугалась?", shape=plaintext]; boyRY [label="p=0,1 (Испугался)\n---------\nУбежал в истерике", shape=plaintext]; boyRun[label="p=0.1*0.4*0.1=0.004\n---------\np*(-10000)*50=-2000", style=filled, fillcolor=red]; boyRN [label="p=0,9 (Не испугался)\n---------\nСтал дразнить?", shape=plaintext]; boyStripN [label="p=0,5 (Нет)\n---------\nНе стал дразнить", shape=plaintext]; boyStripN1 [label="p=0.1*0.4*0.9*0.5=0.018\n---------\np*2000*50=1800", style=filled, fillcolor=green]; boyStripY [label="p=0,5 (Да)\n---------\nЗмея кусила?", shape=plaintext]; boyBiteY [label="p=0,5 (Да)\n---------\nУкусила!", shape=plaintext]; boyBiteN [label="p=0,5 (Нет)\n---------\nПобрезговала", shape=plaintext]; boyBiteN1 [label="p=0.1*0.4*0.9*0.5*0.5=0.009\n---------\np*2000*50=900", style=filled, fillcolor=green]; boyBiteY1 [label="p=0.1*0.4*0.9*0.5*0.5=0.009\n---------\np*(-100000)*50=-45000", style=filled, fillcolor=red]; girlRY [label="p=0,9 (Испугалась)\n---------\nУбежала в истерике", shape=plaintext]; girlRun[label="p=0.1*0.6*0.9=0.054\n---------\np*(-10000)*50=-27000", style=filled, fillcolor=red]; girlRN [label="p=0,1 (Не испугалась)\n---------\nСтала дразнить?", shape=plaintext]; girlStripN [label="p=0,5 (Нет)\n---------\nНе стала дразнить", shape=plaintext]; girlStripN1 [label="p=0.1*0.6*0.1*0.5=0.003\n---------\np*2000*50=300", style=filled, fillcolor=green]; girlStripY [label="p=0,5 (Да)\n---------\nЗмея кусила?", shape=plaintext]; girlBiteY [label="p=0,5 (Да)\n---------\nУкусила!", shape=plaintext]; girlBiteN [label="p=0,5 (Нет)\n---------\nПобрезговала!", shape=plaintext]; girlBiteN1 [label="p=0.1*0.6*0.1*0.5*0.5=0.0015\n---------\np*2000*50=150", style=filled, fillcolor=green]; girlBiteY1 [label="p=0.1*0.4*0.9*0.5*0.5=0.0015\n---------\np*(-100000)*50=-7500", style=filled, fillcolor=red]; snake ->snakeNo [arrowhead=none]; snakeNo -> snakeNo1 [arrowhead=none]; snake -> gender [arrowhead=none]; gender -> boy [arrowhead=none]; gender -> girl [arrowhead=none]; boy -> boyRY [arrowhead=none]; boyRY -> boyRun [arrowhead=none]; boy -> boyRN [arrowhead=none]; boyRN -> boyStripN [arrowhead=none]; boyRN -> boyStripY [arrowhead=none]; boyStripY -> boyBiteY [arrowhead=none]; boyStripY -> boyBiteN [arrowhead=none]; boyBiteN -> boyBiteN1 [arrowhead=none]; boyStripN -> boyStripN1 [arrowhead=none]; boyBiteY -> boyBiteY1 [arrowhead=none]; girlRY -> girlRun [arrowhead=none]; girl -> girlRN [arrowhead=none]; girl -> girlRY [arrowhead=none]; girlRN -> girlStripN [arrowhead=none]; girlRN -> girlStripY [arrowhead=none]; girlStripY -> girlBiteY [arrowhead=none]; girlStripY -> girlBiteN [arrowhead=none]; girlBiteN -> girlBiteN1 [arrowhead=none]; girlStripN -> girlStripN1 [arrowhead=none]; girlBiteY -> girlBiteY1 [arrowhead=none]; >

Как видите, язык достаточно простой, если кому-то нужно готов создать отдельную тему по этому.

3. В каталоге, где расположен файл запускаем команду dot test.dot -Tpng -o test.png
4. .
5. PROFIT!

Если есть вопросы, готов попробовать ответить.

DNF is not an option
Silver Member
Регистрация: 12.02.2012
Сообщений: 614

Можно же оформить примерно так:
Graphviz — это разработанный специалистами лаборатории AT&T пакет утилит по автоматической визуализации графов, заданных в виде текстового описания. Пакет распространяется с открытыми исходными файлами и работает на всех операционных системах, включая Windows, Linux/Unix, Mac OS. Самой интересной программой пакета является «dot», автоматический визуализатор направленных графов, который принимает на вход текстовый файл со структурой графа, а на выходе формирует граф в виде графического, векторного или текстового файла.

Ситуация: человек встречает змею и варианты развития событий представлены на блок-схеме по теории вероятности.

А вы что написали?

Первичка профсоюза — наше всё!
Профсоюз контролирует ситуацию)
Gold Member
Регистрация: 08.04.2012
Адрес: Воронеж
Сообщений: 2,043
Сообщение от Котова

Ситуация: человек встречает змею и варианты развития событий представлены на блок-схеме по теории вероятности.

Котова, в теме про пользу педагогического образования (sic!) даны условия задачи.
Грамотей-опричникъ
Сварщик я не настоящий, а сюда просто пописать зашел
Platinum Member
Регистрация: 22.07.2010
Адрес: Санкт-Петербург
Сообщений: 3,257

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

На самом деле, очень многие вещи нарисовать таким образом гораздо проще, чем мышкой в графическом редакторе, а учитывая, что среди результирующих форматов есть SVG, то картина становится вообще радостная. Я использовал этот пакет для иллюстраций к статьям и диссертациям. Мой младенец рисовал топологию сети по курсовику. Но я сильно глубоко внутрь, как в GNU R, не залазил, поскольку задачки решал локальные. Поэтому и написал, что попробую ответить. А компетенции. Компетенции дело наживное, когда то их вообще не было и если только ими ограничиваться, то скоро кота кормить не на что будет.

Во вложении другой вариант того же графа, он сделан горизонтально и ранжирован.

digraph dtree "испуг" -> "убежал" -> "дразнит"-> "дразнит"->"кусила"-> "результат"; > < rank = same; "змея"; "snakeNo"; "gender"; > < rank = same; "испуг"; "boy"; "girl"; > < rank = same; "убежал"; "boyRY"; "girlRY"; > < rank = same; "результат"; "snakeNo1"; "boyRun";"boyStripN1"; "girlRun";"girlStripN1";>snake [label="Встретили змею?", style=filled, fillcolor=antiquewhite;shape=ellipse]; snakeNo [label="p=0,9 (Нет)\n---------\nЗмею не встретили!", shape=plaintext]; snakeNo1 [label="p=0,9\n---------\np*2000*50=90000", style=filled, fillcolor=darkolivegreen1]; gender [label="p=0,1 (Да)\n---------\nЭто была девочка?", shape=plaintext]; boy [label="p=0,4 (Мальчик)\n---------\nИспугался?", shape=plaintext]; girl [label="p=0,6 (Девочка)\n---------\nИспугалась?", shape=plaintext]; boyRY [label="p=0,1 (Испугался)\n---------\nУбежал в истерике", shape=plaintext]; boyRun[label="p=0.1*0.4*0.1=0.004\n---------\np*(-10000)*50=-2000", style=filled, fillcolor=coral]; boyRN [label="p=0,9 (Не испугался)\n---------\nСтал дразнить?", shape=plaintext]; boyStripN [label="p=0,5 (Нет)\n---------\nНе стал дразнить", shape=plaintext]; boyStripN1 [label="p=0.1*0.4*0.9*0.5=0.018\n---------\np*2000*50=1800", style=filled, fillcolor=darkolivegreen1]; boyStripY [label="p=0,5 (Да)\n---------\nЗмея кусила?", shape=plaintext]; boyBiteY [label="p=0,5 (Да)\n---------\nУкусила!", shape=plaintext]; boyBiteN [label="p=0,5 (Нет)\n---------\nПобрезговала", shape=plaintext]; boyBiteN1 [label="p=0.1*0.4*0.9*0.5*0.5=0.009\n---------\np*2000*50=900", style=filled, fillcolor=darkolivegreen1]; boyBiteY1 [label="p=0.1*0.4*0.9*0.5*0.5=0.009\n---------\np*(-100000)*50=-45000", style=filled, fillcolor=coral]; girlRY [label="p=0,9 (Испугалась)\n---------\nУбежала в истерике", shape=plaintext]; girlRun[label="p=0.1*0.6*0.9=0.054\n---------\np*(-10000)*50=-27000", style=filled, fillcolor=coral]; girlRN [label="p=0,1 (Не испугалась)\n---------\nСтала дразнить?", shape=plaintext]; girlStripN [label="p=0,5 (Нет)\n---------\nНе стала дразнить", shape=plaintext]; girlStripN1 [label="p=0.1*0.6*0.1*0.5=0.003\n---------\np*2000*50=300", style=filled, fillcolor=darkolivegreen1]; girlStripY [label="p=0,5 (Да)\n---------\nЗмея кусила?", shape=plaintext]; girlBiteY [label="p=0,5 (Да)\n---------\nУкусила!", shape=plaintext]; girlBiteN [label="p=0,5 (Нет)\n---------\nПобрезговала!", shape=plaintext]; girlBiteN1 [label="p=0.1*0.6*0.1*0.5*0.5=0.0015\n---------\np*2000*50=150", style=filled, fillcolor=darkolivegreen1]; girlBiteY1 [label="p=0.1*0.4*0.9*0.5*0.5=0.0015\n---------\np*(-100000)*50=-7500", style=filled, fillcolor=coral]; snake -> snakeNo [arrowhead=none]; snakeNo -> snakeNo1 [arrowhead=none]; snake -> gender [arrowhead=none]; gender -> boy [arrowhead=none]; gender -> girl [arrowhead=none]; boy -> boyRY [arrowhead=none]; boyRY -> boyRun [arrowhead=none]; boy -> boyRN [arrowhead=none]; boyRN -> boyStripN [arrowhead=none]; boyRN -> boyStripY [arrowhead=none]; boyStripY -> boyBiteY [arrowhead=none]; boyStripY -> boyBiteN [arrowhead=none]; boyBiteN -> boyBiteN1 [arrowhead=none]; boyStripN -> boyStripN1 [arrowhead=none]; boyBiteY -> boyBiteY1 [arrowhead=none]; girlRY -> girlRun [arrowhead=none]; girl -> girlRN [arrowhead=none]; girl -> girlRY [arrowhead=none]; girlRN -> girlStripN [arrowhead=none]; girlRN -> girlStripY [arrowhead=none]; girlStripY -> girlBiteY [arrowhead=none]; girlStripY -> girlBiteN [arrowhead=none]; girlBiteN -> girlBiteN1 [arrowhead=none]; girlStripN -> girlStripN1 [arrowhead=none]; girlBiteY -> girlBiteY1 [arrowhead=none]; >

Вложения

test.pdf (27.0 Кб, 12 просмотров)

Graphviz как пользоваться

Graphviz — утилита командной строки для генерации схем графов на основе текстовых описаний узлов и рёбер. Из статьи вы узнаете, как и языка C++ программно вызвать graphiz с заданными параметрами, прочитать вывод и код возврата вызванной утилиты. Способ можно использовать в отрыве от Graphviz, здесь он взят для примера.

Установка graphviz

Чтобы получить graphviz на Windows, вам нужно:

  • установить Graphviz из MSI-пакета с сайта graphviz.org
  • добавить в системную переменную PATH путь к Graphviz (полный путь к поддиректории Graphviz2.38\bin внутри Program Files )

Для Ubuntu потребуется:

  • установить пакет dot

После установки проверьте работу graphviz на простом примере:

digraph G < 0 [shape="circle"label="S0(a)"]; 1 [shape="box"label="S1(b)"]; 2 [shape="box"label="S2(c)"]; 0->1 [label=" a "]; 0->2 [label=" b "]; 1->1 [label=" b "]; 2->1 [label=" a "]; > 

Автоматизируем вызов Graphviz

Для автоматизации нам потребуется кроссплатформенный способ запуска внешнего процесса из C++. Например, функции popen / pclose из стандарта POSIX.

На Windows название функции popen изменено на _popen . Это легко исправить с помощью препроцессора:

макрозамена popen/pclose
#ifdef _WIN32 #define popen _popen #define pclose _pclose #endif 

Используя пример из документации (msdn.microsoft.com), можно реализовать функцию для запуска произвольной команды, печати вывода в консоль и возврата true/false при успешном/неуспешном выполнении.

объявление класса CUtils
#pragma once #include class CUtils  public: CUtils() = delete; static bool RunProcess(std::string const& command); >; 
запуск внешней команды в CUtils::RunProcess
bool CUtils::RunProcess(const std::string &command)  CPipeHandle pipe(popen(command.c_str(), "r")); if (!pipe)  return false; > std::vectorchar> buffer(256); while (std::fgets(buffer.data(), int(buffer.size()), pipe))  std::fputs(buffer.data(), stderr); > if (std::feof(pipe))  return true; > // process run failed, print error. std::fputs(std::strerror(errno), stderr); return false; > 

Класс CPipeHandle — это простейшая безопасная обёртка над FILE* , в деструкторе которой автоматически вызывается закрытие канала через pclose .

RAII-обёртка для FILE*
namespace  class CPipeHandle  public: CPipeHandle(FILE *pipe) : m_pipe(pipe)  > ~CPipeHandle()  if (m_pipe)  pclose(m_pipe); > > operator FILE*()const  return m_pipe; > private: FILE *m_pipe = nullptr; >; > 

Чтобы воспользоваться API, нужно указать полную команду со всеми аргументами. Если путь к исполняемому файлу команды не добавлен в переменную окружения PATH, то придётся указать полный путь к .exe . Вот пример вызова Graphviz для преобразования диаграммы fsm.dot в изображение fsm.png

CUtils::RunProcess("dot -Tpng -ofsm.png fsm.dot"); 

Класс генерации кода для Graphviz

Допустим, нам захочется сделать сериализацию дерева или графа в язык Graphviz, чтобы автоматизировать их визуализацию. Допустим, мы хотим визуализировать конечный автомат:

Иллюстрация

На выходе мы должны получить примерно такой файл *.dot :

digraph G < 0 [shape="circle"label="S0(a)"]; 1 [shape="box"label="S1(b)"]; 2 [shape="box"label="S2(c)"]; 0->1 [label=" a "]; 0->2 [label=" b "]; 1->1 [label=" b "]; 2->1 [label=" a "]; > 

В файле указаны надписи на вершинах и рёбрах графа, где под вершинами подразумеваются состояния, а под рёбрами — переходы. Для вершин указана форма: для начального состояния круг (circle), для конечных состояний (терминалов) двойной круг (doublecircle), для промежуточных состояний (нетерминалов) прямоугольник (box).

Вспомогательный класс для сериализации назовём CDotWriter. Определение класса:

enum class StateType  Initial, Nonterminal, Terminal >; class CDotWriter  public: CDotWriter(std::ostream & out); ~CDotWriter(); void PrintVertex(size_t index, std::string const& label, StateType type = StateType::Nonterminal); void PrintEdge(size_t from, size_t to, std::string const& label); private: std::string GetShape(StateType type)const; std::ostream & m_out; >; 

Реализации методов выглядят так:

CDotWriter::CDotWriter(std::ostream &out) : m_out(out)  m_out  <"digraph G \n"; > CDotWriter::~CDotWriter()  m_out  <">\n"; > void CDotWriter::PrintVertex(size_t index, const std::string &label, StateType type)  m_out  <index  <" ["; m_out  <"shape=\""  <GetShape(type)  <"\""; m_out  <"label=\""  <label  <"\"];\n"; > void CDotWriter::PrintEdge(size_t from, size_t to, const std::string &label)  m_out  <from  <"->"  <to  <" [label=\" "  <label  <" \"];\n"; > std::string CDotWriter::GetShape(StateType type) const  switch (type)  case StateType::Initial: return "circle"; case StateType::Nonterminal: return "box"; case StateType::Terminal: return "doublecircle"; > return std::string(); > 

PS-Group

  • PS-Group
  • sshambir@gmail.com
  • ps-group
  • image/svg+xml sshambir

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *