Использование 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