SHiNE-server/shine-UI/Dev_Docs/features/interactive-network-graph.md
AidarKC f56e531384 Связи: интерактивная карта связей (force-directed graph)
Переработка экрана «Связи» в интерактивный нод-граф с премиальными переходами.

Движок (js/pages/network/force-graph.js):
- diffing-переходы: общие узлы перелетают, новые расцветают каскадом, исчезнувшие — Ghost-слой (800мс, на месте);
- мягкая радиальная пружина + отталкивание (органичная орбита), упругий влёт фокуса;
- динамическая вязкость на старте (трение 0.92→0.82, отталкивание ослаблено) — мягкий разлёт без тряски;
- жёсткая заморозка (kill-switch) при затухании — нет «треска», экономия батареи;
- линии — SVG <path> Безье (изогнутые нити), прорастание; жесты pan с инерцией;
- хард-лимит DOM-аватарок (остальное — SVG-точки).

Интеграция и UX:
- adapter.js: getUserConnectionsGraph → модель движка (сервер не трогаем, read-only);
- фильтры (Все/Семья/Друзья/Сияющие), контекстное меню (node-menu.js), нижний сниппет, профиль;
- прицел в центре, дыхание фокуса, свечение сияющих;
- лаборатория network-view/lab на мок-данных (networkGraphUsers) для тестов без бэкенда.

Документация: shine-UI/Dev_Docs/features/interactive-network-graph.md.
Бамп client.version 1.2.135 -> 1.2.136.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 12:43:56 +03:00

7.2 KiB
Raw Blame History

Интерактивная карта связей (force-directed graph)

Экран «Связи» (network-view) — интерактивная нод-граф карта вместо статичного списка: фокусный пользователь в центре, связи на орбите, навигация тапом/свайпом, премиальные переходы в духе нативного iOS.

Где код

  • js/pages/network/force-graph.jsдвижок (физика, рендер, жизненный цикл узлов, жесты).
  • js/pages/network/adapter.js — реальные данные → нейтральная модель движка.
  • js/pages/network/node-menu.js — общее контекстное меню узла.
  • js/pages/network/lab.js — лаборатория (network-view/lab) на мок-данных, без бэкенда.
  • js/pages/network-view.js — страница: шапка, поиск, фильтры, история, склейка с движком.
  • js/mock-data.jsnetworkGraphUsers (связанный мульти-граф из 10 человек для лаборатории).
  • styles/network-graph.css — все стили .fg-*.

Данные (read-only, сервер не трогаем)

Единый источник — authService.getUserConnectionsGraph(login) (один запрос: логин → прямые связи). network-view.jsbuildGraphModel() нормализует роли (parent/child/sibling/spouse/friend/contact), направление и метки; adapter.engineModelFromGraphModel() превращает это в модель движка: { focusId, nodes:[{ id, login, name, avatar, relationType, strength, shining, tier }] }.

Модель движка и API

createForceGraph({ stage, model, onNodeTap, onCenterTap, onNodeLongPress }){ setModel(model), setFilter(pred), recenter(id), getFocusNode(), destroy() }.

Ключевые механики

  • Diffing-переходы (непрерывность состояний): при смене фокуса общие узлы (тот же id) не пересоздаются, а перелетают пружиной на новые места; новые «расцветают» (bloom) каскадом из центра; исчезнувшие уходят в Ghost-слой.
  • Ghost-слой: снимок всего старого графа (узлы + линии) на полноэкранном overlay, застывает на месте, scale 1→0.7 + opacity 0.5→0 за 800мс, затем удаляется (красивый шлейф истории).
  • Физика: мягкая радиальная пружина к орбите + взаимное отталкивание (charge) → органичная, слегка неровная орбита; фокус влетает в центр упруго. Координаты узлов на трансформах (GPU).
  • Каскадный bloom: новые узлы скрыты в центре и «выстреливают» по очереди (order × 40мс).
  • Динамическая вязкость: первые ~600мс после перестроения трение завышено (0.92), отталкивание ослаблено (×0.45) → гасит «взрыв», затем плавно к базе (0.82) — мягкое «резиновое» появление.
  • Жёсткая заморозка (kill-switch): когда сумма |vx|+|vy| < 0.03 — скорости обнуляются, координаты округляются до целых пикселей, cancelAnimationFrame (sleep). Нет «треска», батарея не страдает.
  • Линии: SVG <path> Q (квадратичные Безье) — изящные изогнутые нити, тонкие/полупрозрачные; при движении изгиб реагирует на скорость; новые линии прорастают (stroke-dashoffset).
  • Жесты: свайп-pan с инерцией (новое касание прерывает); короткий тап — центрирование + нижний сниппет; долгий тап — контекстное меню (в #modal-root, позиция по getBoundingClientRect); тап по центру — профиль.
  • Фильтры слоёв: Все / Семья / Друзья / Сияющие (плавное скрытие/перераспределение).
  • Поллиш: «прицел» в центре (+пульс при захвате фокуса), «дыхание» фокуса (бесконечная CSS-анимация размера 1.481.52x, GPU, не будит rAF), свечение «сияющих», хард-лимит ~90 DOM-аватарок (остальное — SVG-точки).

Параметры тюнинга (константы в начале force-graph.js)

Константа Значение Назначение
ORBIT_MIN / ORBIT_MAX 150 / 240 радиус орбиты (защитный отступ от центра — подписи не наезжают)
K_RADIAL 0.035 жёсткость орбитальной пружины (мягко)
K_FOCUS 0.12 жёсткость пружины фокуса к центру
CHARGE / CHARGE_START_FACTOR 1400 / 0.45 отталкивание (на старте ослаблено)
FRICTION / FRICTION_BOOST / BOOST_FRAMES 0.82 / 0.92 / 36 базовое трение / стартовая вязкость / длительность (~600мс)
SLEEP_V 0.03 порог суммарной
FOCUS_SCALE 1.5 базовый масштаб фокуса
MAX_FULL_NODES 90 хард-лимит полных аватарок (далее — точки)

Локальный запуск / проверка

  • Dev-сервер: .claude/shine-ui-dev-server.cjs (Node, порт 7321, SPA-fallback + инжект <base href="/">).
  • Лаборатория (без бэкенда): http://localhost:7321/network-view/lab — мок networkGraphUsers, тап по узлам переключает сети.
  • Реальный путь (/network-view) требует живого wss://shineup.me/ws (локально — ws_open_error, это норма).

Ограничения / на будущее

  • Многоуровневая глубина (друзья друзей мельче, 3-й уровень — точки), кластеры, «общие связи» упираются в API (отдаёт только прямые связи) — требуют доработки сервера.
  • lerpX/lerpY в движке больше не используются для отрисовки — кандидат на чистку.
  • Превью в простое троттлит requestAnimationFrame (физика не идёт между вызовами) — для замеров прокачивать кадры; в активном табе всё работает на 60 FPS.