Перейти к содержимому
Поиск
Разработка

Cytoscape.js: графы связей — мастерская и 9 практических примеров

Исследуем связи сервисов: соседи, кратчайший путь, пошаговый обход, фильтры и редактирование. Девять работающих сценариев, экспорт JSON/PNG/HTML и готовые исходники.

Александр Некрасов
Cytoscape.js — связи становятся понятными: узлы и графы

Одна заявка проходит через сайт, API, CRM, очередь и несколько обработчиков. Когда меняется контракт или появляется ошибка, плоский список сервисов уже плохо объясняет происходящее. Нужно увидеть связи, направление вызовов, ближайшие зависимости и цепочку до нужного результата. Для этого полезен граф: узлы представляют объекты, а рёбра — отношения между ними.

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

Что даёт Cytoscape.js пользователю сервиса

Cytoscape.js — библиотека для отображения и анализа графов в JavaScript. Она хранит узлы и связи, рассчитывает расположение, рисует схему и предоставляет операции над её структурой. Поэтому одна и та же модель поддерживает не только красивую картинку, но и осмысленные вопросы: что связано с выбранным объектом, куда можно добраться и какой путь имеет минимальную стоимость.

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

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

Когда выбрать граф, таблицу или редактор процессов

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

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

Материал о React Force Graph показывает другое окружение визуализации графов. В этой статье выбран обычный JavaScript без React, а встроенные алгоритмы Cytoscape используются непосредственно. Выбор библиотеки стоит начинать с нужного поведения интерфейса, а не с того, какая демонстрация сильнее движется.

Версия, лицензия и локальная сборка

В проекте закреплена Cytoscape.js 3.34.4 и Bootstrap 5.3.8. Версия библиотеки проверена через официальный npm-реестр при подготовке материала. Точные зависимости записаны в package-lock.json; команда npm ci восстанавливает эту установку. Это позволяет повторить пример и отдельно планировать обновления, вместо случайного получения другой версии через плавающий CDN-адрес.

Ядро Cytoscape распространяется по MIT. Авторский код, тексты и синтетический набор сервисов тоже входят в архив под MIT. Полные notices библиотеки и Bootstrap сохранены в THIRD-PARTY-LICENSES.txt. Встроенных раскладок достаточно для всех сценариев, поэтому дополнительные плагины и их отдельные лицензии здесь не нужны.

Готовый public содержит локальный JavaScript, CSS и исходники для изучения. Браузер посетителя не загружает Cytoscape с чужого CDN. Для разработки используются Node.js 24 и esbuild, но постоянный Node-процесс на публичном сервере не требуется: результат сборки является обычным статическим сайтом.

npm ci --ignore-scripts
npm run build
npm test

Мини-мастерская: девять задач на одной карте

Выберите сценарий в верхнем списке. Каждый вариант объясняет, где он полезен и что попробовать. Начало и цель выбираются обычными HTML-полями; узел также можно нажать на графе или в текстовом списке. Кнопки масштаба и возврата всей схемы помогают восстановить контекст после перемещения.

Исходная карта содержит двенадцать сервисов и семнадцать направленных связей. Есть три команды, два узла с учебным предупреждением, цикл и отдельный узел без связей. Эти особенности нужны для содержательных экспериментов: увидеть отсутствие пути, проверить направление и убедиться, что обход не зацикливается. Никакие реальные системы эта страница не опрашивает.

Переключение задачи возвращает исходный набор. Изменения живут только в памяти вкладки; перед уходом сохраните JSON. Для длительной работы на телефоне удобнее открыть мастерскую отдельно. Встроенная область предназначена для быстрого знакомства, а не заставляет редактировать схему в узком фрагменте статьи.

Модель данных: идентификаторы, узлы и связи

У каждого узла есть устойчивый идентификатор. Название можно менять, не разрывая отношения: рёбра ссылаются на id, а не на текст подписи. В нашем наборе узел также хранит команду, состояние и координаты. Ребро содержит собственный id, начало, конец и положительный вес. Такой минимальный контракт легко получить из базы данных или небольшого API.

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

Мы сохраняем компактный прикладной формат с version:1. Это удобнее, чем обещать поддержку любого объекта, который когда-либо возвращает библиотека. Неизвестные поля отбрасываются. Когда приложение вырастет, версия формата позволит явно отличать старый экспорт от нового и выполнять контролируемую миграцию данных.

const data = {
  version: 1,
  nodes: [
    { id: 'web', label: 'Web', team: 'web', status: 'ok', position: { x: 100, y: 80 } },
    { id: 'api', label: 'API', team: 'web', status: 'ok', position: { x: 260, y: 80 } }
  ],
  edges: [{ id: 'e1', source: 'web', target: 'api', weight: 2 }]
};

Сначала договоритесь о смысле стрелки и веса

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

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

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

Первый экземпляр и отделение модели от оформления

Cytoscape получает контейнер, элементы и набор стилей. Контейнеру необходима измеримая высота: пустой блок без размера не превращается в видимый граф только после создания JavaScript-объекта. В мастерской высота рабочей области задана CSS, а ResizeObserver сообщает библиотеке об изменении доступного пространства.

Оформление описывается селекторами. Узлам назначены подписи, рёбрам — стрелки и веса, предупреждениям — отдельная форма. Цвет помогает обнаружить состояние, но текстовая карточка повторяет его словами. Подсветка маршрута использует классы и не переписывает бизнес-данные.

В src/model.js находятся данные, проверки и вызовы алгоритмов. В src/app.js — элементы управления и жизненный цикл. Это разделение полезно для тестирования: расчёт пути можно проверить без Canvas, а браузерную отрисовку — отдельно. Один успешный тест алгоритма не доказывает, что кнопка видна на телефоне.

import cytoscape from 'cytoscape';
const cy = cytoscape({
  container: document.querySelector('#graph'),
  elements: [
    { data: { id: 'web', label: 'Web' } },
    { data: { id: 'api', label: 'API' } },
    { data: { id: 'e1', source: 'web', target: 'api', weight: 2 } }
  ],
  layout: { name: 'grid' },
  style: [
    { selector: 'node', style: { label: 'data(label)' } },
    { selector: 'edge', style: { 'target-arrow-shape': 'triangle', 'curve-style': 'bezier' } }
  ]
});

Сценарий 1: карта сервисов и подробности узла

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

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

Не пытайтесь разместить всю карточку в подписи узла. Короткое название помогает ориентироваться, а подробности удобнее читать в обычном HTML. Если нужны действия вроде открытия журнала или создания заявки, явно отделяйте их от выбора объекта. Нажатие на схему не должно неожиданно запускать необратимую операцию.

cy.on('tap', 'node', event => {
  const node = event.target;
  details.textContent = `${node.data('label')}: ${node.degree()} связей`;
});

Сценарий 2: пять раскладок для одних данных

Раскладка меняет расположение узлов, сохраняя сами отношения. Grid удобен для регулярного обзора, circle — для компактного набора, breadthfirst — для последовательностей по слоям. Concentric помогает разнести узлы по концентрическим уровням, а CoSE использует силовой подход. В мастерской все пять вариантов встроены в ядро библиотеки.

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

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

cy.layout({
  name: 'breadthfirst', directed: true,
  roots: '#web', padding: 38, animate: false,
  nodeDimensionsIncludeLabels: true, spacingFactor: 2
}).run();
// Восстановить сохранённые координаты:
cy.layout({ name: 'preset' }).run();

Официальное описание API: встроенные раскладки.

Сценарий 3: ближайшие соседи и направления

Перед изменением API обычно важно увидеть ближайшие связи, а не всю транзитивную сеть. В сценарии соседей выберите CRM и переключите направление. Входящие узлы показывают, кто вызывает CRM; исходящие — кому CRM передаёт запросы или данные. Режим обоих направлений полезен для общего контекста.

В исходном наборе у CRM входящие связи от API и Отчётов, а исходящие — к Оплате, Очереди и Базе. Это конкретный проверяемый результат. Остальная схема приглушается, чтобы связь оставалась в контексте, а не исчезала без объяснения. Текстовый статус перечисляет найденные узлы.

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

const node = cy.getElementById('crm');
const callers = node.incomers('node');
const calls = node.outgoers('node');
const neighborhood = node.closedNeighborhood();

Сценарий 4: кратчайший путь по весам

Поиск маршрута отвечает на вопрос, как добраться между двумя узлами с минимальной суммой весов. Выберите Web и Архив, оставьте учёт стрелок включённым и запустите поиск. Результат проходит через API, Вход, Базу и Отчёты; итоговая стоимость равна одиннадцати. Он вычисляется алгоритмом Dijkstra на текущих данных.

Теперь поменяйте начало и конец. Направленного пути от Архива к Web нет. Если отключить учёт стрелок, появляется другой маршрут стоимостью семь. Это полезный опыт: визуально соединённые узлы не всегда образуют допустимый направленный путь. Отдельный узел Аудит позволяет проверить сообщение об отсутствии связи.

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

const result = cy.elements().dijkstra({
  root: cy.getElementById('web'),
  weight: edge => edge.data('weight'),
  directed: true
});
const destination = cy.getElementById('archive');
const distance = result.distanceTo(destination);
if (Number.isFinite(distance)) {
  const path = result.pathTo(destination);
  path.addClass('highlight');
}

Границы алгоритма: веса, отсутствие пути и циклы

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

Отсутствие пути — нормальный результат, а не ошибка работы страницы. Интерфейс должен объяснить его и оставить возможность изменить начало, цель или направление. Мы не выводим Infinity как пользовательскую стоимость. Если начало совпадает с концом, путь имеет нулевую стоимость; отдельный пустой маршрут к недостижимому узлу обрабатывается иначе.

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

Официальное описание API: Dijkstra и результат поиска пути.

Сценарий 5: пошаговый BFS и достижимость

BFS обходит достижимые узлы по слоям: сначала начало, затем непосредственные соседи, далее следующий уровень. Это удобно для объяснения структуры и проверки того, до каких объектов вообще можно добраться. В нашей карте обход из Web посещает одиннадцать узлов; изолированный Аудит остаётся вне результата.

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

Цикл в исходных данных проверяет важную деталь: алгоритм не посещает один узел бесконечно. Для обучения полезно сравнить BFS с маршрутом по весам. BFS описывает слои по числу переходов, тогда как Dijkstra оптимизирует сумму стоимости. Эти ответы могут совпасть на простом наборе, но это разные вопросы.

const order = [];
cy.elements().bfs({
  roots: cy.getElementById('web'), directed: true,
  visit(node, edge, previous, index, depth) {
    order.push({ id: node.id(), via: edge?.id(), depth });
  }
});
// Кнопки мастерской показывают order по одному шагу.

Сценарий 6: поиск и комбинированные фильтры

Фильтр помогает выделить рабочий участок без уничтожения данных. Введите название или id, выберите команду и состояние. Условия сочетаются: команда Бизнес и предупреждение оставят CRM и Письма. Поиск по CRM дополнительно сузит результат до одного узла. Пустая выборка сопровождается понятным предложением изменить условия.

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

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

const matches = cy.nodes().filter(node =>
  node.data('team') === 'business' && node.data('status') === 'warn'
);
const ids = new Set(matches.map(node => node.id()));
cy.edges().forEach(edge => {
  const visible = ids.has(edge.source().id()) && ids.has(edge.target().id());
  edge.toggleClass('hidden', !visible);
});

Сценарий 7: потенциальная зона внимания при инциденте

Когда выбранный сервис недоступен, полезно получить список компонентов, которые стоит проверить дальше. Для этого сценарий подсвечивает successors — узлы, достижимые по направленным связям. Выберите CRM и посмотрите, какие участки оказываются в потенциальной зоне внимания. Исходный узел сохраняется в контексте.

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

Для практического мониторинга добавляйте тип зависимости, актуальность метрик и правила влияния. Хороший первый результат — список вопросов дежурному: что проверить, кому сообщить и где открыть журнал. Граф может ускорить такую работу, но не должен выдавать догадку по форме стрелок за подтверждённое состояние инфраструктуры.

const origin = cy.getElementById('crm');
const downstream = origin.successors();
downstream.union(origin).addClass('highlight');
// Это достижимость, а не расчёт фактической недоступности.

Сценарий 8: изменение схемы и отмена

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

Удаление сервиса также убирает его входящие и исходящие рёбра. Перед изменением сохраняется снимок данных, поэтому кнопка отмены возвращает узлы, отношения и координаты. История ограничена двадцатью операциями. Это собственный небольшой механизм приложения, а не подключённый undo-плагин Cytoscape.

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

const before = serialize(cy);
const draft = structuredClone(before);
draft.edges.push({ id: 'newLink', source: 'web', target: 'crm', weight: 2 });
const checked = validateGraph(draft); // сначала проверяем всю модель
history.push(before);
replace(checked); // затем обновляем интерфейс

Сценарий 9: группы ответственности и compound nodes

Родительские узлы помогают показать принадлежность объектов командам. В последнем сценарии Веб, Бизнес и Платформа становятся контейнерами, а сервисы располагаются внутри них. Связи по-прежнему могут пересекать границы. Такое представление полезно для обсуждения ответственности и поиска межкомандных интеграций.

Группа не является дополнительным сервисом. Она не добавляет стоимость пути и не должна случайно попадать в бизнес-данные как исполняемый объект. В нашем экспортном формате хранится поле team у каждого сервиса; контейнеры создаются при отображении. Поэтому импорт не требует вручную поддерживать отдельный набор служебных родителей.

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

const elements = [
  { data: { id: 'group_web', label: 'Веб' } },
  { data: { id: 'web', label: 'Web', parent: 'group_web' } },
  { data: { id: 'api', label: 'API', parent: 'group_web' } }
];

Официальное описание API: родительские и дочерние узлы.

Импорт JSON: проверка до изменения интерфейса

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

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

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

if (file.size > 100 * 1024) throw new Error('Файл больше 100 КиБ');
const candidate = JSON.parse(await file.text());
const checked = validateGraph(candidate);
// Проверить актуальность операции чтения, затем применить checked.

JSON, PNG и HTML: три разных результата

JSON сохраняет данные и координаты для дальнейшего редактирования. PNG фиксирует реальную картинку Canvas и подходит для обсуждения или документации. Изображение не содержит редактируемую модель. После подготовки появляется постоянная ссылка с именем и размером файла: её можно нажать повторно, если автоматическое скачивание не началось. В мастерской его размеры ограничены, чтобы один экспорт не создавал неоправданно большое полотно.

HTML сохраняет выбранный сценарий и текущий набор данных. Положите скачанный файл в public полного проекта рядом с app.js, style.css и vendor, затем откройте его через локальный сервер. Это не автономный документ без ресурсов: библиотека и интерфейс остаются соседними локальными файлами. Фильтры, подсветка и текущий шаг обхода начинаются заново.

Исходники app.js и model.js доступны отдельными ссылками и внутри ZIP. Это точные файлы, из которых собрана работающая мастерская. Такой набор удобен для переноса: можно сначала проверить целый проект, а затем выделить нужную логику, не пытаясь восстановить её по одному короткому фрагменту статьи.

const png = cy.png({
  full: true, bg: '#ffffff',
  maxWidth: 1600, maxHeight: 1200,
  output: 'blob'
});
// JSON модели сохраняется отдельной командой, не внутри PNG.

Жизненный цикл, таймеры и возврат назад

При смене сценария старый экземпляр Cytoscape уничтожается. Иначе в странице могут остаться Canvas, обработчики и ссылки на уже неактуальные данные. ResizeObserver также требует отключения при окончательном уходе. Эти детали особенно заметны в административной панели, где пользователь многократно открывает и закрывает один виджет.

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

У pagehide важно учитывать persisted. Если браузер сохраняет страницу для возврата назад, уничтожать интерфейс нельзя: иначе пользователь вернётся к пустой области. В этом случае достаточно остановить таймер. Полная очистка выполняется при обычном окончательном уходе; такую ветку стоит проверять отдельно от первоначального открытия страницы.

addEventListener('pagehide', event => {
  stopPlayback();
  if (!event.persisted) {
    cy.destroy();
    resizeObserver.disconnect();
  }
});

Доступность и мобильная работа без точных жестов

Canvas хорошо рисует сеть, но не становится автоматически полноценным семантическим интерфейсом. Поэтому выбор узлов, начало и цель маршрута, кнопки алгоритмов и результат представлены обычным HTML. Человек может выполнить основные действия, не попадая пальцем в маленькую вершину и не используя перетаскивание.

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

Такая альтернатива улучшает удобство, но не является сертификацией доступности. Для продукта отдельно проверяйте выбранный скринридер, физический телефон, клавиатурный порядок, масштабирование текста и длинные названия. Проверка ширины в настольном браузере не воспроизводит все особенности сенсорного ввода.

Производительность: что измерять до большого графа

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

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

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

cy.batch(() => {
  cy.nodes().removeClass('highlight');
  cy.getElementById('crm').addClass('highlight');
  cy.getElementById('jobs').addClass('highlight');
});

Как подключить PHP или Node.js backend

Готовый public можно встроить в PHP-сайт или Node.js-приложение. Сам граф работает в браузере. Сервер нужен, когда карта должна получать реальные данные, сохранять изменения, учитывать права и вести журнал. Не требуется исполнять JavaScript пользователя: клиент передаёт документированную модель, а сервер решает, какие действия разрешены.

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

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

const response = await fetch('/api/maps/42');
if (!response.ok) throw new Error('Не удалось загрузить карту');
const model = validateGraph(await response.json());
// /api/maps/42 — пример вашего маршрута; в мастерской его нет.
// Примените model только если выбранный проект всё ещё актуален.

Запуск на Windows без изменения политики PowerShell

Распакуйте ZIP и откройте PowerShell внутри cytoscape-project. Для разработки нужен Node.js 24. Используйте npm.cmd, если оболочка блокирует npm.ps1: ради запуска мастерской не требуется менять системную ExecutionPolicy. После установки зависимостей соберите проект, выполните тесты и откройте локальный адрес.

По умолчанию сервер слушает только 127.0.0.1. Он не публикует ваш компьютер в интернете и не принимает загрузку файлов. Для другого порта задайте переменную PORT перед запуском. Остановка выполняется сочетанием Ctrl+C в терминале, где работает процесс.

Готовый public уже входит в архив. При установленном PHP можно просто раздать его встроенным локальным сервером. PHP здесь не вычисляет маршрут и не сохраняет данные. Открытие index.html через file:// не равно HTTP-раздаче: генератор экспортного HTML читает шаблон запросом, поэтому запускайте страницу через сервер.

node --version
npm.cmd ci --ignore-scripts
npm.cmd run build
npm.cmd test
npm.cmd start
# http://127.0.0.1:8153/
# Другой порт: $env:PORT = '8159'
# Альтернатива для готовой public: php -S 127.0.0.1:8153 -t public

Запуск на Linux и удалённая разработка

На Linux используются тот же lock-файл и те же исходники. Установите поддерживаемый Node.js 24 способом, принятым в вашей среде, и работайте обычным пользователем. npm внутри рабочей папки не требует sudo. Готовая сборка переносится между системами как набор статических файлов.

Если проект запускается на удалённой машине для разработки, удобен SSH-туннель. Он позволяет оставить сервер на loopback и открыть его локально без публикации дополнительного порта. Для постоянной доступности коллегам настройте штатный веб-сервер с HTTPS и нужной авторизацией, вместо фонового dev-процесса без обслуживания.

Перед переносом проверьте путь document root и MIME JavaScript. В архиве используются обычные .js, а не расширение, которое некоторый хостинг отдаёт как двоичный файл. Статус HTTP 200 ещё не доказывает, что браузер смог выполнить bundle и отрисовать Canvas.

unzip cytoscape-project.zip
cd cytoscape-project
npm ci --ignore-scripts
npm run build
npm test
npm start
# На локальном компьютере для удалённого стенда:
# ssh -L 8153:127.0.0.1:8153 user@server

Linux VPS: HTTPS, доступ и эксплуатация

Для публикации достаточно каталога public. В README показано размещение отдельного релиза в /srv/cytoscape, настройка прав и символьной ссылки current. Предполагается установленный Caddy, настроенный DNS и доступные порты 80 и 443. Добавляйте блок сайта к существующей конфигурации, сохраняя другие проекты на сервере.

Относительная ссылка ../cytoscape-project.zip рассчитана на размещение мастерской внутри WordPress. Если Caddy раздаёт только public, соседний архив автоматически недоступен. Замените ссылку на /downloads/cytoscape-project.zip и настройте отдельный маршрут выдачи файла либо укажите свой существующий раздел загрузок. Проверьте скачивание после развёртывания.

Публичной учебной карте вход не нужен. Реальную схему инфраструктуры защищайте целиком: через авторизацию приложения или basic_auth веб-сервера с хешем пароля. Секреты и конфигурация доступа не должны попадать в public или скачиваемый архив. Подробная настройка сервера разобрана в материале о Caddy.

После проверки конфигурации перезагрузите сервис и изучите журнал. Затем пройдите пользовательский путь: выбор узла, отсутствие маршрута, ошибочный импорт, экспорт PNG и HTML. Сохраняйте настройки сайта и архив релиза отдельно. Резервная копия статики не сохранит несохранённый JSON, который остался только во вкладке посетителя.

graph.example.com {
    root * /srv/cytoscape/current/public
    encode zstd gzip
    file_server
    header X-Content-Type-Options nosniff
    header Cache-Control no-cache
    log
}
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
curl -I https://graph.example.com/app.js
sudo journalctl -u caddy --since '10 minutes ago' --no-pager

Обновление и быстрый откат согласованной версии

Сначала обновляйте зависимости в отдельной рабочей копии. После изменения lock-файла выполните установку, сборку, тесты и аудит, затем повторите браузерные сценарии. Особое внимание уделите сериализации, группам, расчёту пути и экспорту: именно на границах данных часто проявляется несовместимость.

На сервере храните отдельные релизы. Новый public помещается в новый каталог, а current переключается после проверки. Это позволяет вернуть целый согласованный набор JavaScript, CSS и HTML. Замена отдельных файлов на живой странице может дать посетителю смесь двух версий, даже если каждая отдельно работала правильно.

Временная ссылка перед командой должна отсутствовать, а предыдущий релиз необходимо сохранить до приёмки. Для постоянных имён app.js и style.css не включайте долгий immutable-кэш без системы версионирования ресурсов. После переключения проверьте не только заголовки HTTP, но и скачанный пример рядом с обновлёнными зависимостями.

sudo ln -s /srv/cytoscape/releases/r2 /srv/cytoscape/current.next
sudo mv -Tf /srv/cytoscape/current.next /srv/cytoscape/current
# Откат к сохранённому r1:
sudo ln -s /srv/cytoscape/releases/r1 /srv/cytoscape/current.rollback
sudo mv -Tf /srv/cytoscape/current.rollback /srv/cytoscape/current

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

Тесты запускают настоящую Cytoscape в headless-режиме. Проверяются стоимость и направление маршрута, отсутствие пути, BFS с циклом, прямые и транзитивные связи, фильтры, строгий импорт, удаление с отменой, compound-узлы и пять раскладок. Отдельные проверки контролируют HTTP, приватные файлы, подписи полей и совпадение опубликованных исходников со сборкой.

Headless не рисует Canvas и не проверяет реальный жест пользователя. Поэтому мобильная компоновка, работа PNG, скачивание HTML и ввод файлов требуют браузерной приёмки. Физический телефон и экранный диктор остаются отдельными средами испытания. Такой предел доказательств полезнее общего заявления, что всё работает везде.

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

Официальные источники: API Cytoscape.js, исходники и лицензия, статический сайт Caddy.