Однажды, собираясь писать документацию - я задумался. Проект, который я собирался документировать, динамически развивался и было очевидно, что все схемы, которые я буду рисовать ещё не раз придётся дополнять и перерисовывать. "Вот бы было что-то типа Wiki, но с возможностью также легко рисовать схемы" - подумал тогда я. "Но неужели до меня никто не додумался до столь простой мысли и не сделал столь полезного изобретения?" - меня посетили сомнения. "Не может быть!" Я отправился в поисковые системы, где во множестве предлагались различные платные графические редакторы, позволяющие рисовать прямо в браузере. Но тут мой взгляд зацепился за знакомое название GraphViz! Вспомнив, что я уже не раз слышал об этом продукте, а также не раз видел его в составе моего дистрибутива, я решил познакомится поближе и неожиданно увлёкся... Автор статьи: Виктор ВислобоковРазмещается под лицензией: CC-BY-NC-ND УстановкаУстановка до безобразия проста. GraphViz входит в состав практически всех извесных мне дистрибутивов, ориентированных на пользователя. Так что всё что остаётся, например, людям использующим Fedora, CentOS и прочие rpm-дистрибутивы с yum, это выполнить команду: yum install graphviz Также в дистрибутивах есть пакеты, позволяющие использовать GraphViz со многими языками прогаммирования: Perl, Python, PHP, Tcl/TK и и.д. Возможно после ознакомления с GraphViz вы захотите поставить и использовать также и эти пакеты. Первое использованиеИспользовать GraphViz очень просто. Как всегда всё в стиле Unix-way, т.е. через командную строку. Вы делаете файл, в котором, на некоем мета-языке содержится описание требуемого вам графа, а затем запускаете утилиту из комплекта GraphViz, которая из этого файла делает другой файл, хранящий в себе уже сам отрисованный граф. Простейший пример. Сделаем файл example1.gv вида: digraph HelloWorld { "Hello" -> "World"; }и получим из него картинку в формате PNG: dot -Tpng example1.gv -oexample1.png Думаю, теперь вы уже можете без труда составить простейшие вертикальные графы. Разумеется, отрисованный граф можно получать не только в виде PNG-картинки. Это может быть и SVG-графика, встраиваемая в веб-страницу, и PDF, и PostScript, VML и т.д. и т.д. Разумеется, GraphViz может много больше, чем просто отрисовка столь примитивного графа, который был продемонстрирован в предыдущем разделе. Возможностей очень много и их полное описание выходит за пределы данной статьи. Если вы очень любите изучать всё с начала и до конца, то я рекомендую посетить официацльный сайт на предмет чтения официальной документации: http://www.graphviz.org. Я же рассмотрю лишь некоторые полезные возможности, которые помогут вам начать рисовать достаточно продвинутые графы и схемы. Немного теорииХотя с помощью GraphViz можно рисовать схемы, которые лишь отдалённо напоминают графы, всё же GraphViz - это именно средство для отрисовки графов! Поэтому не ждите от неё возможностей Visio или чего-то подобного, у этого продукта (как впрочем и любого другого) есть свои ограничения и недостатки, о чём будет сказано ниже. И вот вам кстати ссылка на Галерею графов, где вы можете посмотреть, что можно получить с помощью GraphViz. Прежде чем пойти дальше, я введу некоторые термины, которыми мы будем оперировать. Они пригодятся ниже, когда я буду давать пояснения, а также если вы вдруг всё же начнёте изучать полную документацию. Итак:
Имена узловGraphViz по умолчанию заточен на использование UTF-8 и вы можете преспокойно использовать русские буквы в названии узлов графа. Единственное, что необходимо помнить, что лучше заключайте любые имена в двойные кавычки, хотя GraphViz и позволяет использовать имена из одного слова, не содержащие разделительных знаков без кавычек. Т.е. вы можете использовать следующие имена узлов: node1 Понедельник "Новый год" "self-made"Обратите внимание, в двух последних случаях использование двойных кавычек обязательно, потому что в имени "Новый год" два слова, разделённые через пробел, а имя "self-made" содержит служебный символ "-". В общем мой совет - используйте кавычки всегда и не ошибётесь, а напротив избежите досадных ошибок, связанных с невнимательностью. В любом месте файла вы можете использовать комментарии также как в языке C++, т.е. либо вот так: /* это комментарий в несколько строк */либо вот так: // это комментарий до конца строки УмолчанияЕсли вы используете набор однотипных фигур и стрелок, но не тот, который установлен по умолчанию, гораздо удобней будет выставить вначале необходимые умолчания, которые будут действовать для всего графа. Например умолчание: node [shape="box"] edge [dir="both"]приведёт к тому, что вместо эллипсов, внешний вид узлов примет вид прямоугольников, а все стрелки будут иметь два кончика, указывающие в обоих направлениях. ПримерыЧтобы не описывать каждый раз подробно один из атрибутов, я просто приведу здесь несколько примеров, комбинируя которые вы и сможете в большинстве случаев получить то, что вам нужно. Примеры, я привожу исходя из того, что когда-то искал сам, так что надеюсь, что вы тоже оцените их полезность Пример 1. Предположим вы хотите составить себе план действий на неделю, потому что периодически, что-либо вылетает из головы. Например, во вторник и субботу вы моете свой автомобиль, а по понедельникам, четвергам и субботам у вас тренировка в спортзале. А в среду, пятницу и понедельник вам нужно зайти в магазин за продуктами. А в четверг вам ещё нужно побывать в паспортном столе, чтобы забрать документы, а если они ещё будут не готовы, то подождать их сидя в кафе неподалёку. Теперь, сразу решаем, что внешний вид узлов графа должен быть круги вместо эллипсов, которые залиты синим цветом, а надписи в них будут белые. Поскольку документы - это наиболее важное в нашем плане, то мы используем красный цвет. А стрелки мы хотим, чтобы были не сплошные, а пунктирные. Да и граф нужно ориентировать не по вертикали, а по горизонтали, потому что так нам наглядней. В итоге получаем: digraph MyPlan { node [shape="circle", style="filled", fillcolor="blue", fontcolor="#FFFFFF", margin="0.01"]; edge [style="dashed"]; rankdir="LR"; "Документы" [fillcolor="red"]; "Понедельник"->"Тренировка"->"Продукты"; "Вторник"->"Мойка авто"; "Среда"->"Продукты"; "Четверг"->"Документы"->"Тренировка"; "Документы"->"Кафе"->"Документы"; "Пятница"->"Продукты"; "Суббота"->"Мойка авто"->"Тренировка"; "Воскресенье"; }И картинку: Уместно дать некоторые пояснения. Как видите, цвет можно задавать не только его названием, но и 16-ричным кодом, как в HTML (#RRGGBB). Единожды появившееся описание узла, используется повсеместно, не вызывая конфликтов. Несвязанные узлы, например "Воскресенье" так и остаются в графе без стрелочек. Атрибут margin="0.01" используется, чтобы уменьшить отступ внутри кругов, иначе они будут больше, что не всегда удобно. Атрибут rankdir="LR" меняет направление графа с "TB" (сверху вниз), на "LR" (слева направо). Посмотрев на граф, вы видите, что он получился не таким уж и удобным. Дни недели разбросаны по всему графу, ориентироваться в нём неудобно. Может быть попробовать всё же изобразить его вертикально? Пример 2. Давайте попробуем изобразить ранее изученный пример вертикально. Ну а чтобы было интересней, заключим дни недели в кластер, выделим его цветом и добавим метку. А вместо кругов в качестве фигур для дней недели будем использовать прямоугольники, залитые зелёным цветом. А шрифт у названий дней недели сделаем чёрного цвета и помельче. Заодно ещё и добавим в воскресенье сперва поход в театр, а затем в кафе: digraph MyPlan { node [shape="circle", style="filled", fillcolor="blue", fontcolor="#FFFFFF", margin="0.01"]; edge [style="dashed"]; "Документы" [fillcolor="red"]; subgraph cluster_week { node [shape="box", style="filled", fillcolor="green", fontcolor="black", fontsize="9"]; label = "Дни недели"; "Понедельник"; "Вторник"; "Среда"; "Четверг"; "Пятница"; "Суббота"; "Воскресенье"; } "Понедельник"->"Тренировка"->"Продукты"; "Вторник"->"Мойка авто"; "Среда"->"Продукты"; "Четверг"->"Документы"->"Тренировка"; "Документы"->"Кафе"->"Документы"; "Пятница"->"Продукты"; "Суббота"->"Мойка авто"->"Тренировка"; "Воскресенье"->"Театр"->"Кафе"; }и картика: Думаю, вы уже поняли, почему дни недели вверху выстроены не по порядку. Если нет, то присмотритесь внимательней - GraphViz распределил на графе узлы так, чтобы обеспечить наименьшее пересечение стрелок друг с другом. Пересечение стрелок может запутать, поэтому GraphViz оптимизирует свою работу при построении графа именно так. Ещё обратите внимание, чтобы была возможность сделать рамочку и подпись к кластеру, имя у подграфа должно начинаться с "cluster" и никак иначе .Пример 3. Теперь давайте возьмём так сказать один день из жизни и сделаем из него блок-схему алгоритма. Пусть это будет четверг. Делаем практически то, что уже пробовали, лишь добавляем к стрелочкам, соединяющим узлы подписи "Да" и "Нет": digraph MyPlan { node [shape="rectangle"]; "Начало" [shape="ellipse"] "Конец" [shape="ellipse"] "Если готовы документы" [shape="diamond"] "Начало"->"Четверг"->"Если готовы документы"->"Тренировка"[label="Да"] "Тренировка"->"Конец"; "Если готовы документы"->"Кафе"[label="Нет"] "Кафе"->"Если готовы документы"; }получаем картинку: И... разочаровано на неё смотрим! Коряво получилось. "Кафе" должно быть сбоку от условия "Если готовы документы", а у нас оно внизу, а стрелка к "Кафе" и обратно к "Если готовы документы" идёт совсем не по стандарту. Если изменить уровнь у "Кафе" ещё можно, использовав распределение узлов по уровням, то со стрелками уже ничего поделать нельзя. Вот вам и первый недостаток GraphViz - он не ориентирован на создание прямолинейных блок-схем алгоритмов. Пример 4. Давайте теперь ознакомимся с возможностью GraphViz распределять узлы графа по определённым уровням высоты. Реализована эта возможность, через создание в качестве некой "линейки уровней" ещё одного невидимого графа в стороне от основного и закрепление узлов нашего графа на том же уровне, что узлов линейки. Итак, снова берём наш план и меняем его вот так: digraph MyPlan { node [shape="circle", style="filled", fillcolor="blue", fontcolor="#FFFFFF", margin="0.01"]; edge [style="dashed"]; { node [shape="plaintext",style="invisible"]; edge [color="white"]; "1" -> "2" -> "3" -> "4" -> "5" -> "6" -> "7"; } subgraph week { node [shape="box", style="filled", fillcolor="green", fontcolor="black", fontsize="9"]; label = "Дни недели"; "Понедельник"; "Вторник"; "Среда"; "Четверг"; "Пятница"; "Суббота"; "Воскресенье"; { rank="same"; "1"; "Понедельник"; } { rank="same"; "2"; "Вторник"; } { rank="same"; "3"; "Среда"; } { rank="same"; "4"; "Четверг"; } { rank="same"; "5"; "Пятница"; } { rank="same"; "6"; "Суббота"; } { rank="same"; "7"; "Воскресенье"; } } "Документы" [fillcolor="red"]; "Понедельник"->"Тренировка"->"Продукты"; "Вторник"->"Мойка авто"; "Среда"->"Продукты"; "Четверг"->"Документы"->"Тренировка"; "Документы"->"Кафе"->"Документы"; "Пятница"->"Продукты"; "Суббота"->"Мойка авто"->"Тренировка"; "Воскресенье"; }В итоге получим вот такую смешную картинку: Обратите внимание, у узлов линейнки, установлен атрибут: style="invisible";который запрещает отрисовывать эти узлы, хотя и OpenViz резервирует место под них, а атрибут color="white"у стрелок, приводит к тому, что они отрисовываются, но белым цветом, таким же как цвет фона, поэтому их не видно! Как вы можете видеть, теперь дни недели упорядочены по высоте, но к сожалению не находятся друг под другом. И увы, мне неизвесно способа как их можно заставить выстроится именно так! Конструкция: { rank="same"; "2"; "Вторник"; }говорит, что узлы "2" и "Вторник" должны находится на одном уровне. Вы можете, например, добавить ещё какой-либо узел после "Вторник" также через ";" и этот узел также будет отрисован на этом же уровне. Ещё один недостаток такого распределения по уровням становится ясен, когда начинаете работать с кластерами. Я пытался создать в кластере ещё один кластер и несколько узлов, распределив их по высоте, но сколь я не бился - у меня ничего не вышло. По неизвестной мне причине, уровни начинали вставать попарно друг с другом рядом. Если кто знает почему так и как это решить - напишите мне и моё "большое спасибо" вам обеспечено. Заодно дополню статью. Пример 5. А что если хочется странного, например, чтобы название узла было в две строки? Это как раз довольно просто. А если вдруг хочется чтобы вторая строка имела другой цвет и размер шрифта? Вот это уже гораздо сложнее! А бывает вообще хочется некий прямоугольник, разбитый по секциям с разными надписями. Вот вам пример, реализующий всё вышесказанное: digraph MyPlan { node [margin="0.01"]; "box1" [shape="box", label="Надпись в\nдве строки"]; "box2" [shape="box", style="filled", fillcolor="lightcyan2", label=<Обычная надпись<BR /><FONT COLOR="blue" POINT-SIZE="8">вторая строка другим шрифтом и цветом</FONT>>]; "box3" [shape="record", label = "Слева|Справа"]; "box4" [shape="record", label = "{Сверху|Снизу}"]; "box5" [shape="record", label = "Слева|{Сверху|Центр|Снизу}|Справа"]; "box1" -> "box2"; "box3" -> "box4" -> box5; }И картинка: Как видите, если "box1" и "box2" используют тип узла "box" и HTML-форматирование, то "box3", "box4" и "box5" используют специальный тип узла: "record". К сожалению у "record" есть недостаток - расположение меток зависит от направленности графа, т.е. если бы у этого графа атрибут "rankdir" был выставлен в "LR" а не как по умолчанию в "TB", то метки "слева" и "справа" стали бы отрисоваться не слева и справа, а сверху и снизу и наоборот метки "снизу" и "сверху" стали бы отрисовываться вместо снизу и свеху, справа и слева. Это не очень-то удобно, если вы по каким-то причинам решить поменять направление роста графа. Пример 6. Тем не менее, GraphViz неплохо подходит для отображения структуры баз данных и связей между таблицами. Приведём пример, заодно пользуясь случаем показать, что можно изменить внешний вид наконечников стрелок: digraph MyPlan { node [margin="0.01"]; rankdir="LR"; "users_tbl" [shape="record", label="пользователи|и картинка: ЗаключениеВ общем-то пожалуй и всё, что я хотел рассказать. Думаю, для решения множества задач - этих примеров достаточно. Хочется заметить, что во всех примерах я использовал направленный граф и утилиту dot, однако GraphViz позволяет использовать и другие виды отрисовки графов, такие как "circo", "neato" и т.д. Так вот, чтобы попробовать отобразить свой граф в таком режиме, нужно вместо dot использовать соответствующую утилиту: circo, neato и т.д. Не все атрибуты, которые поддерживаются dot, поддерживаются другими способами отображения - нужно иметь это в виду.
|
|||||||||||||||||||