Всем привет! Продолжаю развивать прототип идеи инструмента - призванного помогать в изучении сложных/комплексных тем и вопросов. Почитываю идеи Андрея Карпаты - очень нравятся его мысли и идеи. Думаю на тему качественных сценариев использования, на ум приходит что-то вроде:

  • “Изучатор” сложного в графовом представлении с удобством и простотой drawio

  • “Сравнятор” совместной работы человека и LLM, тут нужен некий режим возможности дозагружать новый файл с графом и получать инструментом diff (различия) с подсветкой

  • Гибридный вьювер/редактор, с одной стороны коннекторы различных импортов, с другой стороны соответственно различные формы экспорта строгого и не очень.

Предыдущие статьи писал с использованием относительно сырой версии 0.8. С тех пор вышли 0.9 и 0.9.1, и прилично времени потратил на то как правильно показывать ситуации когда на краях листа - много “соседей”.

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

Лист «Обзор запуска»: вокруг рамка с группами соседних листов, плашками и «Ещё 8 листов»
Лист «Обзор запуска»: вокруг рамка с группами соседних листов, плашками и «Ещё 8 листов»

Лист из учебного проекта: 5 узлов, 24 соседних листа, 50 карточек на рамке. Здесь и дальше — скриншоты PlyLoom 0.9.1.

Бескрайний лист

В учебном проекте «Запуск приложения» на листе «Обзор запуска» представлено всего пять узлов: цель, дата релиза, объём первой версии, бюджет и главные риски. Если смотреть на лист отдельно, это всего пять карточек на пустом поле и не видно, что сущность «Объём первой версии» опирается на требования, дизайн и локализацию, а сущность «Цель» связана с маркетингом, поддержкой и исследованиями.

Тот же лист с выключенной рамкой: пять карточек на пустом поле
Тот же лист с выключенной рамкой: пять карточек на пустом поле

Тот же лист с выключенной рамкой: пять узлов и ни одной связи с остальным проектом.

При этом лист связан с 24 другими листами. Показать их все рядом с листом нельзя: не останется места для самого листа. Не показывать - значит потерять то ради чего мы схему раскладывали по листам.

В 0.8 связь с узлом другого листа показывалась пунктирной стрелкой к карточке за границей листа. Для двух-трёх соседей этого хватало. Но с ростом проекта у края листа собирались десятки карточек, и вопрос как показывать соседей - пришлось решать заново. Ответ на картинке в начале статьи - это рамка. Еще она называется паспарту (привет Форту Бойярд!)

Рамка паспарту и лист внутри

Рамка прибита к краям окна, словно рамка для фотографии. Лист двигается и масштабируется внутри этого «окошка», рамка при этом стоит на месте. На ней лежат узлы других листов. От узлов листа к карточкам на рамке идут пунктирные линии, а щелчок по карточке открывает этот лист

Схема рамки с пронумерованными элементами
Схема рамки с пронумерованными элементами
  1. Открытая группа - узлы одного соседнего листа. Показывает до шести карточек, остальные прокручиваются колесом, счётчик ↑ ↓ говорит, сколько скрыто.

  2. Плашка - свёрнутая группа: название листа и число узлов. Пунктирная обводка — свернула рамка, потому что не хватило места; сплошная - свернули вы.

  3. «Ещё N листов» - на рамке не больше 16 листов: сначала выбранные вручную, потом самые связанные. Остальные ждут здесь.

  4. Стороны. Входящие связи тянут группу влево и вверх, исходящие - вправо и вниз. На стороне не больше двух рядов.

  5. Линии идут от узлов листа к карточкам на рамке; к видимым карточкам - всегда.

  6. Лист занимает не меньше 60 % ширины и высоты окна. В маленьком окне рамка уменьшается целиком - до 90 и 80 %.

Главное правило - лист важнее рамки. Листу отдаются 60 % зоны, а остаток достается рамке. Если групп больше, чем помещается, то начинают срабатывать правила:

  1. Сворачивает в плашки столько групп, сколько нужно;

  2. Оставляет на рамке 16 листов, остальные убирает в «Ещё»;

  3. Если не помещаются группы, то показывает соседей списком справа.

Какая группа на какой стороне

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

Есть и второй приоритет: новая полоса на рамке дороже места в уже открытой. Поэтому рамка не всегда ставит группу на её «идеальную» сторону - иногда выгоднее дописать её в полосу, которая уже есть, и оставить листу больше места.

Как это посчитано

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

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

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

Если результат не нравится, группу можно перетащить за заголовок вдоль стороны или на другую сторону. Пока тянете, рамка показывает, куда группа встанет, а остальные остаются на своих сторонах.

Анимация: группу «Релиз» перетаскивают с верхней стороны рамки на правую
Анимация: группу «Релиз» перетаскивают с верхней стороны рамки на правую

Группу тянут за заголовок с верхней стороны на правую; рамка перестраивается на ходу.

Ручной выбор сохраняется в файле. Вот настоящая карта из статьи про сравнение редакций: Правила продажи, ПП № 2463 → ПП № 657, обе редакции нарезаны скриптом по пунктам. На листе «Изменения» я перенёс группы «было» налево, а «стало» — направо, и лист стал читаться слева направо: старая редакция → изменение → новая редакция и требования.

Лист «Изменения»: слева пункты ПП 2463, справа пункты ПП 657 и требования
Лист «Изменения»: слева пункты ПП 2463, справа пункты ПП 657 и требования

Группы, расставленные вручную, отмечены значком ⟲ — он возвращает автоматическое место. Пять изменений раздела о претензиях заполнены для иллюстрации по схеме карты: пункт 2020 → «было» → изменение → «стало» → пункт 2026.

Когда соседей больше, чем места

Свёрнутая плашка - не тупик. Щелчок по ней раскрывает группу прямо на рамке, если остальные группы могут сохранить свои стороны. Свернуться при этом могут только автоматические группы, а в «Ещё» уйдут не больше трёх листов - и подсказка над листом их назовёт. Если места нет совсем, группа откроется поверх листа, и кнопка «Развернуть в рамке» закрепит её открытой.

Плашка «Требования» раскрыта на нижней стороне рамки
Плашка «Требования» раскрыта на нижней стороне рамки

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

«Ещё N листов» открывает список справа: сверху листы, которые сейчас в «Ещё», а ниже те, что на рамке. Кнопка «на рамку» ставит лист плашкой и закрепляет его; если места нет, его уступит самый слабо связанный лист - и вернётся первым, как только место появится. Подсказка над листом говорит, кто уступил место и кто вернулся.

Список соседних листов справа: «В «Ещё»» и «На рамке»
Список соседних листов справа: «В «Ещё»» и «На рамке»

8 листов в «Ещё» и 16 на рамке. Кнопка «Рамкой» возвращает обычный вид.

Линии тоже пришлось дозировать. К каждой видимой карточке линия идёт всегда. К плашкам и прокрученным карточкам - пока таких линий не больше 80, от каждого узла. Дальше - по одной линии к плашке от её главного узла, а все линии — при наведении и у выделенного узла. При совсем большом числе - только у выделенного.

Раскладка, которая не прыгает

Самое важное свойство рамки - без ваших действий она ничего не двигает. Новый соседний лист, изменение размера окна или открытая боковая панель не перекидывают группы на другие стороны, а лист, ушедший в «Ещё», помнит свою сторону. Заново рамку раскладывают только три команды: «вписать», «Оптимизировать расположение» и «Сбросить ручные».

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

Что сохраняется в файле, а что живёт только на экране, разделено явно. Ручной выбор пишется в лист, в поле externals, по ключу соседнего листа:

"externals": {
  "s2020_general": { "side": "left",  "along": 0.3 },
  "s2026_general": { "side": "right", "along": 0.18 },
  "s2020_goods":   { "state": "parked" }
}

side - сторона рамки, along — место вдоль стороны в долях её длины (от 0 до 1, поэтому выбор переживает другой размер окна), state - open, folded или parked («в «Ещё»»). Список справа, прокрутка групп и память раскладки живут только на экране. «Оптимизировать расположение» сбрасывает места, но оставляет состояния: они говорят, что показывать, а не где. Старые файлы, где место хранилось в пикселях, читаются без ошибок: такие значения просто игнорируются.

Баг, которого не было в тестах

Во время работы над 0.9.1 кнопки на рамке - «Ещё», «Рамкой», плашки в списке - в настоящем браузере не нажимались. Холст захватывал указатель для панорамы (pointer capture), и щелчок до кнопки не доходил.

Тесты при этом были зелёными: в DOM-тестах захват указателя заглушён, поэтому щелчок «проходил». Поймал при ручной проверке в Chromium и исправил до выпуска. В проекте 68 тестов в Node и 107 DOM-тестов в Chromium, но этот случай - хорошее напоминание: заглушка в тесте может спрятать ровно то, что ломается у пользователя.

Меню «Боковые» и фильтр «Листы»

Кнопка «Боковые N ▾» в заголовке листа управляет рамкой целиком: левая часть включает и выключает карточки соседних листов, стрелка открывает меню. В меню - галочки листов на рамке, «Свернуть все», «Сбросить ручные» и «Списком справа». Снятый лист уходит с рамок всех листов и освобождает место. Те же галочки есть в «Фильтре», в разделе «Листы», а сохранённый вид помнит этот выбор.

Меню «Боковые»
Меню «Боковые»

Связи - прямо к узлу в рамке

Раз соседние узлы лежат на рамке, к ним тоже можно тянуть связи. При наведении на карточке появляются четыре точки по краям. Потяните за любую к другому узлу - на том же листе, в соседнем окне разворота или к карточке на рамке и связь создана. Отпустите на пустом месте - появится новый связанный узел. Точка только «ручка»: линия сама выбирает сторону и перестраивается, когда узлы двигаются.

Анимация: связь тянут от карточки «Метрика» к карточке «API-01» на рамке
Анимация: связь тянут от карточки «Метрика» к карточке «API-01» на рамке

Связь теперь выделяется щелчком по линии: рядом открывается панель с типом, подписью, стрелкой и кнопкой удаления. Del удаляет выделенную связь или узел; узел, стоящий на нескольких листах, удаляется только после вопроса. «Отменить» возвращает всё.

Выделенная связь и её панель
Выделенная связь и её панель

Что ещё нового в 0.9–0.9.1

  • Отменить и повторить — кнопки и Ctrl+Z, Ctrl+Shift+Z, Ctrl+Y (на Mac — ⌘), до 30 шагов.

  • «+» на карточке разворачивает её для чтения, соседи на экране отодвигаются; карандаш ✎ открывает редактор поверх карты.

  • Обзор листов: «Только для выделенного» показывает связи одного узла или листа, листы сворачиваются до заголовка.

  • Разворот: новые композиции — два и три листа друг под другом; на телефоне окна идут друг под другом.

  • Палитра узлов по группам — основные, свои типы, блок-схема, BPMN, Canvas — с поиском; тип перетаскивается прямо на лист.

  • Руководство внутри приложения: 34 статьи с поиском и ссылками «См. также». Тот же текст - на сайте.

Руководство внутри приложения: поиск по слову «плашка»
Руководство внутри приложения: поиск по слову «плашка»

Что из комментариев к прошлым статьям

Семантика связей. Спрашивали, как отличить «упоминает» от «выводится из» и «заменяет». Связь в PlyLoom имеет тип, подпись и направление; кроме встроенных типов («зависит от», «подтверждает», «противоречит», «реализует»…) можно завести свои - в карте Правил продажи это «было», «стало», «порождает», «затрагивает», «проверяет». Фильтр отбирает по типам связей, а каждая связь «противоречит» попадает в проверку проекта. Атрибутов у самих связей - владельца, статуса, даты - пока нет: они есть только у узлов.

Сотни узлов на экране. Рамка как раз про то, что видеть и осознавать - это разные вещи. На краю листа не больше 16 листов, в группе не больше шести карточек, линии редеют после 80. А большие листы на мелком масштабе показывают карточки без текста (больше 200 узлов) или упрощённую картинку (больше 500).

Уровни абстракции, как в C4. Пока нет. Отчасти их роль играют листы, «Стопка» и «Оглавление», но вложенности «система → контейнер → компонент» в PlyLoom нет - это открытый вопрос на подумать про коннекторы.

Что пока не решено

  • «Ещё» - это снова список. Рамка честно показывает до 16 листов, но если у листа сорок соседей, большая часть живёт в списке. Возможно, такому листу стоит разделиться.

  • Пороги - 60 % окна, 16 листов, 6 карточек, 80 линий - подобраны на учебных картах и прототипе. На других картах они могут оказаться не лучшими.

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

Попробовать

PlyLoom — один HTML-файл на 3,2 МБ: открывается в браузере, работает без сети, без установки и аккаунта, карта не покидает компьютер.

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