Вступление

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

Как следствие, иногда получаются не самые эстетически привлекательные диаграммы, а поиск ответа на вопрос, как повлиять на автоматическую расстановку элементов, заводит в тупик. Это происходит из-за того, что доступной информации об управлении вёрсткой в PlantUML практически нет, а эксперименты с расстановкой не всегда дают устойчивый эффект.

В данной статье я попытаюсь закрыть этот пробел и заодно поделюсь некоторыми полезными приёмами, чтобы снизить порог вхождения в работу с PlantUML. А, чтобы погружение было более лёгким, изложение материала излагается по принципу: от простого к сложному.

1. Базовые сведения

Начнём с общей информации и базовых подходов к управлению расположением элементов на диаграмме.

1.1. Направление и длина стрелок

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

  • вправо: -r->, -right-> или просто ->

  • вниз: -d->, -down-> или просто -->

  • влево: -l->, -left->

  • вверх: -u->, -up->

Соответственно, когда вы применяете этот синтаксис, то ожидаете, что верх будет верхом, низ низом и т.д.

@startuml
"Актор" -left-> (лево)
"Актор" -right-> (право)
"Актор" -up-> (верх)
"Актор" -down-> (низ)
@enduml
Ожидаемое направление это, когда право справа
Ожидаемое направление это, когда право справа

Однако ожидания иногда не оправдываются. Если вы указали инструкцию left to right direction, рассматриваемую в следующих разделах более детально, то происходит следующее: смысл подсказок «верх» и «лево» меняется местами, «низ» и «право», в свою очередь, также меняются местами. Соответственно, все приведённые выше рассуждения надо как бы перевернуть (к примеру, --> станет указывать вправо, а не вниз).

@startuml
left to right direction
"Актор" -left-> (лево)
"Актор" -right-> (право)
"Актор" -up-> (верх)
"Актор" -down-> (низ)
@enduml
Иллюстрация изменения направлений при left to right direction
Иллюстрация изменения направлений при left to right direction

Это поведение трудно назвать очевидным для начинающего пользователя PlantUML, но далее будет дано объяснение произошедшему. Пока же можно использовать следующую аналогию: ориентация листа формата A4 может быть книжной (короткая сторона в верхней части) или альбомной (длинная сторона в верхней части), и если лист повернуть на 90°, то ранее использованные направления на изображении изменяются.

Другой важный момент, о котором стоит сказать, состоит в том, что PlantUML позволяет задавать длину стрелки. Это делается с помощью добавления дополнительных дефисов. Однако, если посмотреть на приведённое в самом начале описание, то можно увидеть, что 1 дефис используется для направления вправо, а 2 дефиса — для направления вниз. Как же тогда это работает? Ответ в том, что удлинение стрелки возможно только в одном направлении. Если стрелка имеет 2 дефиса, то добавление третьего и последующих не изменит её направление, а только длину. Таким образом, вместо --> можно поставить --->, ---->, ------> и т.д.).

1.2. Направление вёрстки

Элементы диаграммы по умолчанию обычно выстраиваются по вертикали сверху вниз. Это значит, что добавляемые элементы постепенно начинают располагаться всё ниже и ниже. В такой ситуации направления стрелок «верх», «низ», «лево» и «право» соответствуют ожиданиям. Это вертикальная вёрстка.

@startuml
"Актор" -left-> (лево)
"Актор" -right-> (право)
"Актор" -up-> (верх)
"Актор" -down-> (низ)
@enduml

Однако если попробовать воспользоваться инструкцией left to right direction, то будет включена горизонтальная вёрстка. Зачем это может быть полезно? Затем что результат для некоторых видов диаграмм (например, для диаграмм вариантов использования) так будет выглядеть лучше и/или привычней. Вот сравните.

До:

@startuml
"Актор 1" --> (ВИ 1)
"Актор 2" --> (ВИ 2)
@enduml

После:

@startuml
left to right direction
"Актор 1" --> (ВИ 1)
"Актор 2" --> (ВИ 2)
@enduml
До и после: вертикальная и горизонтальная вёрстка
До и после: вертикальная и горизонтальная вёрстка

Что ещё. Как уже отмечалось выше, изменение направления вёрстки приводит к тому, что меняются местами понятия «верх» и «лево», а также «низ» и «право». Но это не единственное, о чём следует знать.

Существует также инструкция top to bottom direction (сверху вниз), и обычно её использовать не приходится, т.к. это и так значение по умолчанию для большинства поддерживаемых PlantUML диаграмм. Хотя для ментальных карт (Mind Map) умолчания другие, и в этом случае инструкция сработает, направив диаграмму по вертикали вниз вместо горизонтальной ориентации.

@startmindmap
skin rose
top to bottom direction
* Транспортное средство
** Автомобиль
***_ Легковой
***_ Грузовой
** Автобус
** Троллейбус
@endmindmap
До и после: вертикальная и горизонтальная вёрстка
Вертикальная вёрстка для Mind Map: непривычно, но можно

Иногда в коде примеров можно встретить skin rose. Для рассматриваемой темы это не существенно, это всего лишь способ добавить красок к иллюстрациям.

1.3. Порядок элементов

Если ранее мы в большей степени рассматривали вопрос задания направлений, то сейчас плавно переходим к тому, как повлиять на PlantUML, чтобы он отображал элементы в нужном нам виде. Ведь не секрет, что по мере добавления элементов они иногда «расползаются» по экрану. Самое просто в этом случае это попробовать изменить порядок объявления объектов в коде.

Так, в рассмотренном выше примере с вариантами использования второй вариант с горизонтальной ориентацией выглядит точно лучше, чем первый вариант, но в нём непривычно то, что Актор 2 и ВИ 2 «забрались» наверх. Это можно исправить, если в коде поменять местами строчки 3 и 4. Должно выйти так.

@startuml
left to right direction
"Актор 2" --> (ВИ 2)
"Актор 1" --> (ВИ 1)
@enduml
При горизонтальной вёрстке пришлось переставить местами 2 строки
При горизонтальной вёрстке пришлось переставить местами 2 строки

Из этого следует вывод: порядок объявления объектов имеет значение. И этим можно пользоваться.

1.4. Группировка элементов

Идея, заложенная в этом подходе, проста: если объекты на диаграмме сгруппировать, то движок PlantUML будет стараться их расположить на экране ближе друг к другу.

Группировать объекты можно разными способами. Например, можно использовать:

  • пакеты — ключевое слово package. Так поступают обычно в PDF-версии документации к PlantUML, но с точки зрения синтаксиса языка UML это не всегда уместно;

  • прямоугольники — ключевое слово rectangle. Этот способ выглядит как самый подходящий для группировки вариантов использования («эллипсов») в диаграммах вариантов использования;

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

Рассмотрим один из примеров из стандартной документации по PlantUML.

@startuml
skin rose
left to right direction
actor "Food Critic" as fc
package Restaurant {
  usecase "Eat Food" as UC1
  usecase "Pay for Food" as UC2
  usecase "Drink" as UC3
}
fc --> UC1
fc --> UC2
fc --> UC3
@enduml
Выполнение группировки с помощью графического изображения пакета
Выполнение группировки с помощью графического изображения пакета

Если в приведённый пример добавить инструкцию skinparam packageStyle rectangle, то вместо символа пакета получим рамку системы, как того и требует спецификация UML. Но я лично предпочитаю другой подход: вместо добавления skinparam packageStyle rectangle достаточно заменить ключевое слово package на rectangle. Внешне результат получится таким же, но без дополнительных ухищрений. Да и вообще, рамка, группирующая варианты использования, в UML символизирует систему, а не пакет.

@startuml
skin rose
left to right direction
actor "Food Critic" as fc
rectangle Restaurant {
  usecase "Eat Food" as UC1
  usecase "Pay for Food" as UC2
  usecase "Drink" as UC3
}
fc --> UC1
fc --> UC2
fc --> UC3
@enduml
Выполнение группировки с помощью прямоугольника или пакета в стиле прямоугольника
Выполнение группировки с помощью прямоугольника или пакета в стиле прямоугольника

На самом деле, можно пойти ещё дальше и вообще убрать рамку, воспользовавшись третьим способом. Но лично я предпочитаю рамку оставлять.

@startuml
skin rose
left to right direction
actor "Food Critic" as fc
together {
  usecase "Eat Food" as UC1
  usecase "Pay for Food" as UC2
  usecase "Drink" as UC3
}
fc --> UC1
fc --> UC2
fc --> UC3
@enduml
Выполнение группировки без дополнительного визуального выделения
Выполнение группировки без дополнительного визуального выделения

При рассмотрении первых двух способов всегда использовалось название группирующего элемента (package Restaurant и rectangle Restaurant) для обозначения имени моделируемой системы, а в третьем способе его не было (было просто together). В действительности же PlantUML позволяет убрать название и в первых двух случаях (указав просто package или rectangle), а вот добавить название при третьем способе уже не получится.

2. Погружение в механику

По умолчанию движок PlantUML пытается расположить все объекты на минимальном пространстве с минимальным числом пересечений линий. Но жизнь сильно разнообразней, чем самый изощрённый алгоритм, поэтому иногда результат расстановки может не отвечать нашим ожиданиям.

Чтобы победить проблему, важно понимать внутреннюю механику, а значит и первопричины того, почему что-то работает так, а не иначе. И тогда уже добиться результата будет проще. Начнём издалека, с математики.

2.1. Теория графов

Существует раздел дискретной математики, изучающий специальные виды структур, именуемые графами. Этот раздел называется теорией графов, и для дальнейшего изложения потребуется дать несколько базовых определений.

  • Граф — это совокупность вершин (узлов) и рёбер (дуг), соединяющих эти вершины.

  • Диграф (ориентированный граф, или попросту орграф) — это граф, состоящий из множества вершин, соединённых направленными рёбрами.

  • Взвешенный граф — граф, каждому ребру которого поставлено в соответствие некое значение (вес ребра).

Пример диграфа с тремя вершинами
Пример диграфа с тремя вершинами

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

Это обстоятельство позволило разработчикам PlantUML задействовать «под капотом» графическую библиотеку GraphViz для отрисовки объектов/элементов своих диаграмм.

2.2. Библиотека GraphViz

GraphViz является opensource-решением, позволяющим создавать различные графы с возможностью влиять на расположение вершин и рёбер (более подробно здесь). В GraphViz по умолчанию используется вертикальная вёрстка, но возможно использование и горизонтальной (rankdir=LR). По своему смыслу оба варианта вёрстки являются взаимоисключающими.

Одним из центральных понятий реализованного в библиотеке подхода является понятие ранга (rank). Ранг присваивается каждой вершине и в зависимости от его величины осуществляется упорядочение этих вершин на экране (условной координатной плоскости). Механика назначения рангов сложна: ранг вершины рассчитывается на основе рангов соседних вершин и весов рёбер, входящих в эту вершину. Детально с алгоритмом можно познакомиться в публикации, но для пояснения логики представленных соображений уже будет достаточно.

Одинаковый ранг у вершин в практическом плане означает, что эти вершины будут расположены на одной линии — на одной горизонтальной линии (условной координате y) при вертикальной вёрстке или на одной вертикальной линии (условной координате x) при горизонтальной вёрстке.

Если ранги вершин различаются, то логика следующая. При вертикальной вёрстке вершины с более высоким рангом располагаются ниже, чем вершины с более низким рангом (т.е. у них значение координаты y больше); при горизонтальной вёрстке вершины с более высоким рангом располагаются правее, чем вершины с более низким рангом (т.е. у них значение координаты x больше).

Для удобства можно использовать следующее правило: чем ближе к началу (левому верхнему углу), тем ниже ранг; чем дальше от начала, тем выше ранг.

Принцип увеличения ранга при движении по условной координатной плоскости
Принцип увеличения ранга при движении по условной координатной плоскости

В типовой ситуации вершине присваивается более высокий ранг, чем у всех вершин, указывающих рёбрами на него. Пример: если на вершину A3 указывают рёбра, исходящие из вершин A1 и A2, причём у вершины A1 ранг=2, а у вершины A2 ранг=10, то в общем случае вершине A3 будет присвоен ранг больший, чем max {rank(A1), rank(A2)} = max {2, 10} = 10. Это может быть 11, 12 и т.д. Конкретная величина будет определяться ещё рядом других факторов, в частности весом рёбер: чем больший вес присвоен рёбрам, входящим в вершину, тем больший ранг будет присвоен вершине A3.

В соответствии с правилом выше можно утверждать, что самой близкой к началу вершиной будет A1, а самой дальней — A3.

Но здесь есть 2 нюанса:

  • библиотека позволяет пользователю указать, какие вершины должны иметь одинаковый между собой ранг (вне зависимости от соседних вершин, весов рёбер, связывающих вершины, и др.), также доступна логическая группировка вершин графа в подграфы; в таком случае ранги остальных вершин потенциально могут быть пересчитаны, чтобы удовлетворить такому «хинту»;

  • если несколько вершин имеет одинаковый ранг, то, как отмечалось выше, они обычно выстраиваются в одну строку (или столбец — в зависимости от направления вёрстки). Но это не всё. Вершины по умолчанию выстраиваются в порядке объявления их в коде, при этом GraphViz позволяет вручную переопределить взаимное расположение вершин одного ранга.

2.3. А что с PlantUML?

Приведённые выше сведения позволяют по-новому взглянуть на поведение PlantUML и привычные нам операции. Имеющиеся у PlantUML возможности и ограничения часто проистекают из использования GraphViz. К примеру, при рассмотрении управления длиной стрелок говорилось, что:

  • этот механизм работает только по вертикали или горизонтали (в зависимости от направления вёрстки). А это всё потому, что только одна из двух координат на плоскости в GraphViz используется для ранжирования узлов. Вторая координата содержит объекты одного ранга;

  • количество дефисов (или точек, если мы хотим сделать пунктирную линию вместо сплошной) в стрелке/линии позволяет задать длину. Это оттого, что каждый дефис, начиная со второго, на самом деле увеличивает вес ребра графа (стрелки или линии) на единицу (см. здесь). Но это только если он соответствует направлению вёрстки. Вес ребра, в свою очередь, влияет на то, какой ранг присвоить вершине графа (прямоугольнику класса на диаграмме классов, эллипсу варианта использования на диаграмме ВИ и пр.), на которую указывает ребро. А чем ранг выше, тем дальше на диаграмме эта вершина будет расположена.

Иными словами, удалённость объектов (вершин) это величина производная от весов. Веса, при этом, позволяют располагать элементы более предсказуемо.

Важно также отметить, что согласно документации не все диаграммы в PlantUML строятся на основе GraphViz, поэтому в ряде случаев приведённые соображения не будут работать. Так для каких видом диаграмм это точно работает? Список следующий (взято отсюда):

Означает ли это, что для других диаграмм приведённые соображения вообще не актуальны? Видится, что нет. Причина в том, что мы как потребители оперируем инструкциями PlantUML, которые уже в том или ином случае транслируются в вызовы GraphViz. Но иногда эти инструкции обеспечивают тот же результат, но без обращения к GraphViz. Можно сказать, что мы оперируем контрактом PlantUML, а реализация в каждом конкретном случае может варьироваться, приводя, при этом, к ожидаемому эффекту.

Напомню, ранее в п.1.2 был рассмотрен пример, когда направление вёрстки изменялось в Mind Map, притом что в этом виде диаграмм GraphViz не используется.

2.4. Разбор на примере

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

@startuml
class Первый
class Второй
@enduml

Поэтому, когда вы добавляете class Третий расположение классов претерпевает изменение. И так постепенно доходим до 6 классов.

@startuml
class Первый
class Второй
class Третий
class Четвёртый
class Пятый
class Шестой
@enduml
Автоматическое расположение классов в зависимости от их числа
Автоматическое расположение классов в зависимости от их числа

Появление второй строки с классами говорит о том, что PlantUML «жонглирует» рангами классов (вершинами графа), чтобы разместить их более компактно на экране. Но мы из своих соображений можем хотеть другое расположение.

Если нам нужно, чтобы «Второй» был левее «Первого», то в простейшем случае будет достаточно перенести объявление class Второй перед class Первый (т.к. оба объекта, имеющие один ранг, обычно отрисовываются в порядке их объявления). Но если мы планируем взять расположение классов целиком в свои руки, то придётся поступить следующим образом: указать явно, что «Второй» стоит слева от «Первого»: Первый -left- Второй.

Если мы хотим, чтобы «Пятый» был ниже «Второго», да ещё и на 2 строки ниже, то надо не просто добавить связь с направлением вниз (-down-), но и увеличить вес этой связи (ребра графа) на дополнительную единицу (т.е. нужно обеспечить +2 ранга). Это делается через дополнительный дефис: Второй --down- Пятый. Так как для направлений вниз и вправо есть сокращённая запись, можно было бы написать и так: Второй --- Пятый.

Начинаем расставлять классы
Начинаем расставлять классы

Предположим, что мы решили расположить «Третий» непосредственно справа от «Второго». Да, благодаря ранее добавленной связи у нас справа от «Второго» уже расположен «Первый», но это ничего. После того, как мы добавляем следующую инструкцию: Второй - Третий (что равносильно Второй -right- Третий), мы помещаем «Третий» между «Вторым» и «Первым». Остальные классы на строке подвинутся.

Класс можно «втиснуть» между двумя другими
Класс можно «втиснуть» между двумя другими

Далее мы можем решить разместить «Шестой» над «Третьим» и связать их отношением зависимости. Для этого понадобится пунктирная линия со стрелкой. Это делается простым добавлением инструкции: Третий .up.> Шестой. После всех этих действий «Шестой» и «Четвёртый» находятся в самом верху, а «Пятый» в самом низу диаграммы.

Допустим мы хотим сделать связь «Четвёртого» и «Пятого». Если мы напишем так: Четвёртый --> Пятый, то это укажет, что «Пятый» должен находиться на 1 строку ниже относительно «Четвёртого» (разница в 1 ранг обеспечивается вторым дефисом). Но у нас «Четвёртый» явно не был ни с кем связан (т.е. его расположение никак не фиксировалось), а «Пятый» ранее был намеренно смещён на 2 строки ниже основной группы классов. В совокупности это приведёт к тому, что «Четвёртый» изменит своё положение, сместившись вниз на 2 строки, дабы оказаться на расстоянии 1 строки от «Пятого». Но если мы не хотим изменения его положения, то достаточно указать опцию[norank], которую можно интерпретировать как «исключи эту связь из расчёта рангов, оставь эти объекты в покое». Получаем: Четвёртый --[norank]> Пятый.

В результате всех манипуляций должно получиться так.

@startuml
class Первый
class Второй
class Третий
class Четвёртый
class Пятый
class Шестой
Первый -left- Второй
Второй --down- Пятый
Второй - Третий
Третий .up.> Шестой
Четвёртый --[norank]> Пятый
@enduml

А теперь задумаемся. Мы ранее управляли взаимным расположением классов, прибегая к указанию связей (линий, стрелок, пунктиров и пр. доступных вариантов). Связи получились искусственными, они могут не отражать тех логических связей, которые мы бы реально хотели представить на диаграмме. В этом случае нам нужно скрыть лишние связи. Для простоты допустим, что лишняя у нас только одна связь, это связь между «Первым» и «Вторым» классами. Значит добавляем к ней опцию [hidden] и получаем: Первый -left[hidden]- Второй. После этого линия исчезнет, но расположение классов никак не изменится, чего и требовалось.

До и после добавления [hidden]
До и после добавления [hidden]

Но это ещё не всё. Мы можем захотеть проработать модель более глубоко и указать на диаграмме дополнительные сведения о связях: имя ассоциации и её направление, имена концов ассоциации, их кратность и видимость. PlantUML позволяет добиться и этого, хотя и не без хитростей. Рассмотрим возможный вариант решения.

@startuml
class Первый
class Второй
class Третий
class Четвёртый
class Пятый
class Шестой
Первый -left[hidden]- Второй
Второй "+Владелец \t1" --down- "+Вещь \t*" Пятый : "Владеет >"
Второй "Конец1\n0..1" x-> "+Конец2\n2..*" Третий : "\t\t\t\t"
Третий .up.> Шестой
Четвёртый --[norank]> Пятый
@enduml
Демонстрация дополнительных элементов на диаграмме классов
Демонстрация дополнительных элементов на диаграмме классов


Вдаваться в подробности того, что означает тот или иной символ здесь не буду (лучше обратиться к описанию языка UML), но на что хочу обратить внимание. При горизонтальных связях пришлось прибегнуть к символу перевода строки \n, а при вертикальных связях — к символу табуляции \t. За счёт этого получилось разрядить представленную информацию, чтобы она не сливалась друг с другом и была визуально различима. Можете попробовать убрать эти символы и оценить изменения.

Подход можно ещё немного улучшить, если использовать инструкции !skinparam nodesep X и !skinparam ranksep Y, где X и Y — целые числа. Первая инструкция устанавливает расстояние между вершинами одного ранга, вторая — между вершинами разных рангов. При вертикальной вёрстке (умолчательное значение для диаграммы классов) эти параметры определяют расстояние между графическими изображениями классов по горизонтали (ось x) и вертикали (ось y) соответственно. Эти инструкции оказываются особенно полезными, когда текст связей на диаграмме наезжает друг на друга (с этим порой автоматика не справляется) либо расположенные близко элементы смотрятся не очень опрятно.

Резюме

PlantUML для рендеринга ряда диаграмм использует GraphViz. В таких ситуациях для управления расположением объектов (классов, пакетов и др.) надо придерживаться правил.

  • Объявляйте объекты в том порядке, в каком нужно, чтобы они отрисовывались. Но, если порядок не устраивает, можно переставить строки местами.

  • Используйте направление связей/стрелок для позиционирования объекта относительно других.

  • Помните, что направление вёрстки влияет на то, будет ли возможность задавать степень отдаления объектов (за счёт добавления дефисов/точек), и куда на самом деле указывают стрелки.

  • Прячьте с помощью [hidden] те связи, которые не несут семантики, а лишь вводились на диаграмму для расстановки объектов. При этой учитывайте, что такие связи будут участвовать в расчёте рангов.

  • Используйте [norank] для связей, которые не должны участвовать в расчёте ранга.

  • Добавляйте пространство с помощью инструкций !skinparam nodesep X и !skinparam ranksep Y.


Представленные статья основана на 4-м разделе моей большой работы «Неочевидный PlantUML», которую я ранее опубликовал для своего телеграм-канала. Если интересуют другие полезные приёмы, рекомендую обратиться к первоисточнику.

Комментарии (15)


  1. itGuevara
    09.12.2024 18:39

    Может быть будут интересны примеры генерации схем бизнес-процессов через graphviz:

    https://habr.com/ru/articles/810851/

    https://bpmbpm.github.io/jsDOTsmartDesign/jsDOTsmartDesign.html (выбор ComboBox EPC\VAD и кнопка "схема")

    dreampuf.github.io
    Аналогичное можно повторить на PlantUML, и смотреть \ редактировать, например, через https://www.drawio.com/


    1. RomanSeleznev Автор
      09.12.2024 18:39

      Да, это интересно. Почитаю, спасибо!


  1. Andrey_Solomatin
    09.12.2024 18:39

    Сделать диаграмму красивой занимает 90% времени.

    Я ожидал в группировке объектов скрытые связи. Но в статье они помещенны в другое место.


    1. RomanSeleznev Автор
      09.12.2024 18:39

      Сделать диаграмму красивой занимает 90% времени

      90 или нет, сказать трудно, но точно много. И если до этого навёл красоту, то с добавлением новых деталей по мере развития диаграммы всё может "поплыть". Поэтому сперва предпочитаю максимально проработать детали, а уже потом, если результат автоматики не впечатляет, начинаю влиять на расстановку элементов.

      Я ожидал в группировке объектов скрытые связи. Но в статье они помещенны в другое место.

      Одно другому не противоречит. Все способы можно комбинировать. А то, что конкретно в раздел с группировкой я не поместил скрытые связи, объясняется тем, что я хотел представить материалы по мере усложнения: сначала то, что лежит на поверхности, что можно пробовать, не вникая во "внутрянку", а потом постепенно усложняя детали.

      Если интересен пример, где сочетаются скрытые связи, norank и ractangle, могу предложить обратиться к примеру №2 из упомянутой в конце работы (прямая ссылка, самый последний раздел). Когда готовил данную публикацию, решил не включать этот пример, чтобы статья совсем не разрасталась.


      1. olku
        09.12.2024 18:39

        Прыгающие элементы которые нельзя прибить к определенному месту - самая большая ложка дегтя в бочку меда планта. Для одной компании делал экспорт в drawio благо оба можно привести к объектам svg. Так как внутренняя структура drawio в XML, то можно не переписывать всю диаграмму, а программно добавлять или обновлять ее элементы. В итоге можно получить лучшее от обоих.


        1. RomanSeleznev Автор
          09.12.2024 18:39

          К определённому месту не прибить, а относительно других упорядочить можно. Часто этого достаточно.


    1. Dm_Dm
      09.12.2024 18:39

      Поэтому нарисовать квадратиками в wysiwyg-режиме обычно выходит гораздо быстрее. В схему на планте добавил новый квадратик и всё, случился взрыв на схеме...


  1. Real3L0
    09.12.2024 18:39

    Деды рассказывали, что PlantUML нужен, чтоб быстро накидать схему и её было просто поддерживать. :)

    ожидание-реальность.jpg


    1. RomanSeleznev Автор
      09.12.2024 18:39

      Не вижу противоречий. Я тоже его использую за быстроту и то, что не надо много времени уделять тому, как эстетично расставить блоки на экране. Но ведь иногда схема становится большой и/или возникает желание переставить элементы так, чтобы лучше донести какую-то мысль, и вот тут надо переходить на следующий уровень.

      Могу ответить иначе: можно заставить PlantUML расставить элементы как надо, а вот заставить draw.io или что-то иное расставить автоматически -- не особо:)


      1. Real3L0
        09.12.2024 18:39

        Я вам умных мыслей накину. :)

        1. Любая схема из простой может превратиться в сложную.

        2. Если всю жизнь кого-то заставлять что-то делать, то сам ты это никогда не научишься делать.

        3. Расставать элементы как надо в PlantUML - невозможно. Расставить как надо в draw.io - ну, может час от силы. (Следствие из предыдущего пункта.)


        1. RomanSeleznev Автор
          09.12.2024 18:39

          Мы уже уходим в область философии и личных предпочтений))


          1. Real3L0
            09.12.2024 18:39

            Вообще-то нет. Каждый мой пункт можно доказать. :)

            1. На моей текущей работе минимум половина моих схем стали усложняться со временем. Просто потому, что проект развивается. Остальная половина схем — это схемы для "показать и выкинуть". Т.е. по факту - все схемы превращаются в сложные по мере развития проекта.

            2. Очевидно, по-моему.

            3. У меня нет под рукой схемы, которую я не смог в PlantUML отобразить так, как надо. PlantUML же, в свою очередь, схему постоянно отображал просто страшно. Это было давно и мне тупо не хочется сейча пытаться это воспроизвести. Но факт невозможности был. Да, может уже исправили. Да, может у меня было помутнение, и я что-то делал не так. Но моё помутнение не отменяет факта невозможности: никого просто не было, кто бы смог. А то, что такие люди есть - мне это не помогло. :)

            К последнему пункту добавлю отдельно: WYSIWYG не зря придумали. Время разбора и борьбы с синтаксисом PlantUML несоизмеримо больше, чем "просто нарисовать". Поддержка этого синтаксиса - аналогично.

            Например, наш архитектор при отрисовки схем в PlantUML использовал макросы (или что там?). В простенькой схеме из 10 элементов - большая часть кода PlantUML состояла из текста этих макросов! Представляю, сколько времени он потратил на написание этого кода (пусть и копипастом). А я перерисовал её в draw.io за несколько минут. :)

            Схемы "показать и выкинуть" - ещё один яркий пример. Вы собрались на встрече, на которой кому-то что-то хотите рассказать-объяснить. В процессе встречи решили показать более наглядно с помощью отрисовки схемы в realtime с объяснением каждого элемента. Представлю, как бы это выглядело с помощью PlantUML. :)

            P.S. Я был за PlantUML, когда нужно было отрисовать диаграмму последовательности. Но сейчас повспоминал свои последние пару лет и понял, что даже в этом я уже отказался от использования PlantUML.


            1. RomanSeleznev Автор
              09.12.2024 18:39

              Я не могу согласиться, что всё плохо, что долго разбираться и что архитектору для 10 элементов обязательно использовать функции препроцессора (видимо, про них речь). Каждой задаче свой инструмент.

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

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


  1. ivvi
    09.12.2024 18:39

    За статью спасибо, будучи системным аналитиком, прочитал и освежил в памяти про плантюмл с большим удовольствием.

    Емнип, стрелки можно писать не только слева направо, но и в обратном направлении, то есть B <–– A должно быть эквивалентно A ––> B. Правда, не помню, можно ли это использовать в целях позиционирования, но по крайней мере, в статье этот вопрос не покрыт.

    Также, по прочтении осталось непонятно, можно ли в одной стрелке совместить опции [hidden] и [norank] (полагаю, что можно).


    1. RomanSeleznev Автор
      09.12.2024 18:39

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

      Емнип, стрелки можно писать не только слева направо, но и в обратном направлении, то есть B <–– A должно быть эквивалентно A ––> B. Правда, не помню, можно ли это использовать в целях позиционирования, но по крайней мере, в статье этот вопрос не покрыт.

      Стрелки можно ставить справа налево, слева направо, делать их двусторонними или вообще без стрелок. Смысл в том, что наконечник — это дополнительный графический символ. Важно само ребро, то есть это дефисы или точки, используемые при задании связи. Что изображено на конце (или обоих концах) ребра — не принципиально. Для позиционирования важно то, что ранг передаётся от левого узла правому (пардон за не очень строгую формулировку). Поэтому запись B<--A сформирует то же отношение между узлами, что и A-->B, но ранг будет выше в первом случае для A, во втором — для B. Ниже привожу модификацию ранее рассмотренного примера.

      @startuml
      (ВИ 1) <-- "Актор 1"
      "Актор 2" --> (ВИ 2)
      @enduml

      Также, по прочтении осталось непонятно, можно ли в одной стрелке совместить опции [hidden] и [norank] (полагаю, что можно).

      Если подходить к вопросу с точки зрения синтаксиса, то можно увидеть, что PlantUML принимает связи вида A - [norank,hidden] - B, но я бы не делал по двум соображениям: такая инструкция выглядит как потенциально рискованная для рендеринга плюс встаёт вопрос семантики. Остановлюсь на втором соображении более подробно.

      [hidden] и [norank] совмещать не стоит потому, что эти опции направлены на решение разных задач. Первая опция прячет связь, введённую специально для добавления ранга, т.е. в данном случае ранг связи важен и ради него связь и добавлялась (чтобы искусственно пододвинуть узел на нужное нам место). Вторая опция указывает на то, что ранг от этой связи считать не надо, но семантика этой связи важна и её отображение должно сохраняться.

      Если же мы смешиваем эти обе инструкции, то стоит задуматься: а точно ли мне эта связь нужна, зачем я её вообще вводил, ведь мне не нужен ни вес связи, ни её визуальное отображение? Ответ, скорее всего, будет отрицательным.