Чтобы иерархия Graphviz на Mac читалась как план с отступами, задайте каждому узлу строку и координату отступа, а затем выполните отрисовку с помощью neato -n2. Это сохранит связи между родительскими и дочерними узлами и позволит управлять порядком чтения.
В этом руководстве создаётся пример из шести узлов и экспортируется PDF-файл. Цель конкретна: по одному узлу в строке, потомки сразу под своим родителем, а узлы одного уровня — с одинаковым отступом. В документации Graphviz описана отрисовка с фиксированным расположением с помощью параметра командной строки -n.
Почему обычное дерево Graphviz не похоже на план с отступами?
Иерархия описывает связи, а план с отступами также задаёт порядок чтения. В этом примере корень содержит две ветви, и потомки первой ветви должны располагаться перед второй ветвью. Нужен такой порядок:
Node R
Node A
Node C
Node D
Node B
Node E
Связи в графе: от корня к A, от корня к B, от A к C, от A к D и от B к E. Связи от D к B нет, даже если B находится в следующей строке. В DOT направленные рёбра задаются оператором ->; объявления узлов и атрибуты их отображения — отдельные части языка. См. официальное описание языка DOT.
Движок dot в Graphviz строит иерархические, или многоуровневые, схемы. Он направляет рёбра в одну сторону, стараясь уменьшить их пересечения и длину. Эта задача подходит для разветвлённой схемы, но не задаёт план с одним узлом в каждой строке, показанный выше. См. документацию по компоновке dot.
Некоторые знакомые атрибуты решают лишь часть задачи:
| Параметр | Описанное назначение | Что остаётся нерешённым |
|---|---|---|
rankdir=LR |
Меняет направление компоновки на слева направо | Не задаёт вертикальный порядок строк плана |
rank=same |
Размещает узлы подграфа на одном уровне | Не назначает каждому узлу отдельную строку плана |
ordering=out |
Задаёт размещение исходящих рёбер слева направо в порядке ввода | Не определяет координаты строк и отступы в зависимости от глубины |
Эти ограничения описаны в официальной документации для rankdir, rank и ordering. Для точного расположения строк, описанного в этой статье, явные координаты дают более прямой способ, чем сочетание этих атрибутов.
Как подготовить Graphviz на Mac?
Откройте Terminal. Если вы уже пользуетесь Homebrew, установите Graphviz следующей командой. На официальной странице загрузки Graphviz указан этот способ установки на Mac:
brew install graphviz
Если вы пользуетесь MacPorts, на той же странице загрузки указана команда sudo port install graphviz. Выберите пакетный менеджер, которым уже пользуетесь. Если ни один из них не установлен, прежде чем продолжить работу с руководством, перейдите по соответствующей ссылке для установки на этой странице.
Убедитесь, что средство отрисовки доступно:
neato -V
Параметр -V выводит сведения о версии Graphviz. Сохраните указанную версию, если нужно сравнивать результаты на разных компьютерах; в этой статье не требуется конкретный номер версии. Этот параметр описан в справочнике по командной строке Graphviz.
Как создать иерархию с отступами?
Следующий блок команд создаёт новую рабочую папку и записывает в неё файл DOT. Выполните его в Terminal; по завершении будет выведен путь к папке. Кавычки вокруг ограничителя heredoc сохраняют текст DOT в исходном виде.
outline_dir=$(mktemp -d "$HOME/graphviz-outline.XXXXXX")
if [ -n "$outline_dir" ] && cd "$outline_dir"; then
cat > hierarchy.dot <<'DOT'
digraph Hierarchy {
graph [splines=ortho, bgcolor=white];
node [
shape=box,
width=1.2,
height=0.35,
fixedsize=true,
fontname="Courier",
fontsize=12
];
r [label="Node R", pos="100,300"];
a [label="Node A", pos="136,252"];
c [label="Node C", pos="172,204"];
d [label="Node D", pos="172,156"];
b [label="Node B", pos="136,108"];
e [label="Node E", pos="172,60"];
r -> a;
r -> b;
a -> c;
a -> d;
b -> e;
}
DOT
pwd
fi
Координаты узлов задают структуру плана. Каждая следующая строка сдвигается вниз на 48 пунктов, а каждый дополнительный уровень глубины — вправо на 36 пунктов. Это примерные значения интервалов, а не значения по умолчанию в Graphviz. В документации по pos указано, что координаты узлов относятся к их центрам, а при использовании -n параметр neato интерпретирует координаты в пунктах.
| Узел | Глубина | Строка, отсчёт с нуля | Координаты центра |
|---|---|---|---|
| Node R | 0 | 0 | 100,300 |
| Node A | 1 | 1 | 136,252 |
| Node C | 2 | 2 | 172,204 |
| Node D | 2 | 3 | 172,156 |
| Node B | 1 | 4 | 136,108 |
| Node E | 2 | 5 | 172,60 |
Для этой конструкции правила координат имеют вид x = 100 + 36 × depth и y = 300 − 48 × row. Обратите внимание на переход от Node D к Node B: номер строки увеличивается, а глубина уменьшается. Именно этот переход превращает схему в план, а не в цепочку.
Размеры всех блоков одинаковы, поэтому одинаковые координаты центров также выравнивают их левые края. Одинаковая длина условных подписей упрощает сравнение расположения текста. С параметром fixedsize=true Graphviz использует заданные размеры узлов, не увеличивая блоки под содержимое; слишком длинные подписи вызывают предупреждение. См. описание fixedsize.
Как выполнить отрисовку и проверить результат?
В том же окне Terminal и из той же рабочей папки выполните:
neato -n2 -Tpdf hierarchy.dot -o hierarchy.pdf
Здесь -n2 сохраняет заданное расположение узлов без коррекции перекрытий и рассчитывает маршруты для рёбер, у которых нет собственных координат. Поэтому для каждого узла нужно задать значение pos. Параметры -Tpdf и -o указывают формат PDF и путь для сохранения файла. Эти параметры описаны в справочнике по командной строке.
Откройте рабочую папку в Finder, затем откройте hierarchy.pdf. Для вывода в PDF требуется сборка Graphviz с модулем визуализации Cairo, как указано на странице Graphviz о выводе в PDF. Пример сверён с документацией, но его отрисовка здесь не проверялась, поскольку в исследовательской среде Graphviz был недоступен.
Проверьте отдельно три пункта:
- Прочитайте сверху вниз: R, A, C, D, B, E должны занимать последовательные строки.
- Сравните отступы: A и B должны находиться на одном уровне; C, D и E должны располагаться на шаг правее.
- Проверьте связи: R соединён с A и B; A соединён с C и D; B соединён с E.
Первые две проверки оценивают расположение, заданное координатами. Третья проверяет сам граф. Раздельная проверка помогает обнаружить схему, которая выглядит аккуратно, но содержит неверную связь между родительским и дочерним узлом.
Будут ли соединители выглядеть точно как дерево в файловом браузере?
Строки узлов и отступы заданы явно, а маршруты соединителей всё ещё рассчитываются автоматически. В примере запрошен параметр splines=ortho, который прокладывает рёбра сегментами, параллельными осям. В документации Graphviz также указано, что этот способ прокладки не поддерживает порты, поэтому компасные порты не позволяют документированным способом задать определённый рисунок углов. См. описание splines.
Таким образом, эта конструкция создаёт иерархию с отступами и автоматически проложенными прямоугольными соединителями. Она не задаёт единую общую вертикальную линию с точно размещёнными точками соединения ветвей. Если такая геометрия соединителей необходима, потребуется дополнительно задать явные маршруты рёбер; одного splines=ortho для этого недостаточно.
Чтобы получить чистый план без видимых соединителей, измените параметр графа на splines=none и выполните отрисовку повторно. В файле DOT сохранятся связи между родительскими и дочерними узлами, а средство отрисовки не будет выводить их линии. Эта возможность описана в том же справочнике по splines.
Как изменить пример, не меняя его смысл?
Сначала запишите нужный порядок чтения отдельно от списка рёбер. Сначала укажите родителя, затем его первого потомка и потомков этого узла, прежде чем переходить к следующему потомку. Назначьте каждому узлу в этой последовательности отдельную строку. Затем задайте отступ в зависимости от глубины.
Например, чтобы добавить ещё одного потомка под Node A после Node D, внесите его в последовательность чтения перед Node B. Задайте ему глубину два, сдвиньте последующие строки вниз и добавьте ребро от A к новому узлу. Связи между существующими родительскими узлами не меняются только из-за изменения номеров строк.
Если отредактированная схема выглядит неправильно, проверьте её в следующем порядке:
- Схема выстраивается иначе: убедитесь, что команда по-прежнему использует
neato -n2. В примере задействованы указанные координаты, а не обычная автоматическая компоновка. - При отрисовке сообщается об отсутствующей позиции: проверьте каждый идентификатор узла, в том числе указанный только в строках с рёбрами. Для этого способа каждому узлу нужно задать позицию в объявлении.
- Подпись выходит за пределы блока: увеличьте размеры всех блоков или сократите подпись. Фиксированные блоки не увеличиваются автоматически.
- После увеличения блоков строки перекрываются: увеличьте интервал между строками. Выбранный режим отрисовки сохраняет заданные координаты и не исправляет перекрытия.
- Ветка соединена не с тем узлом: исправьте строку с ребром. Перемещение блока не исправит неверную связь.
Условия, связанные с отсутствующими позициями и перекрытиями, соответствуют задокументированному поведению -n2, а условие с подписью — поведению fixedsize. Увеличение интервалов — подходящая корректировка для этого примера с ручным размещением узлов.
Используйте список рёбер как источник связей и таблицу координат как источник расположения. Для небольшой схемы на Mac это упрощает поддержку: меняйте рёбра при изменении иерархии, строки — при изменении порядка чтения, а интервалы — если подписям требуется больше места.