SHiNE-server/shine-UI/Dev_Docs/features/interactive-network-graph.md
AidarKC 3e4759a0c9 Связи: полировка карты связей (свечение, прорастание линий, CSS-фильтры)
- Линии: тонкие дуги Безье (градиент неон-центр → цвет роли); связь к «сияющему»
  монолитно светится статичной тенью drop-shadow (без бегущих импульсов).
- Прорастание новых линий из центра: stroke-dasharray/dashoffset синхронно с
  разлётом узла (кончик трекает аватарку); старые линии исчезают мгновенно.
- Ghost-слой: только аватарки (без линий), 1000мс — нет висящих «ошмётков».
- CSS-bloom разлёта на компоновщике (устойчив к троттлингу rAF; завершение по таймеру).
- Сияющие узлы: мягкая медленная пульсация 3.6с (многослойная box-shadow + SVG-ореол);
  тестовые фото-аватарки.
- Фильтры слоёв в лаборатории + фикс перехвата click сценой (stopPropagation на чипах);
  фейд скрываемых на месте (opacity 0 + scale 0.8, 300мс), фиксация без физики (ноль тряски).
- Бамп client.version → 1.2.137; обновлена документация фичи.

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

9.5 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-слой.
  • CSS-bloom (разлёт без тряски): разлёт/перелёт узлов делают нативные CSS-переходы на transform (компоновщик, cubic-bezier(0.16,1,0.3,1), BLOOM_MS со ступенчатой задержкой order × 40мс), а НЕ JS-физика. Работает даже при троттлинге rAF; цикл лишь ведёт лучи за узлами (syncPositionsFromDOM). Завершение — гарантированно по таймеру (endCssBloom).
  • Ghost-слой: снимок только аватарок старого графа (без линий — иначе старые связи висят «ошмётками»). Полноэкранный overlay, застывает на месте, scale 1→0.7 + opacity 0.5→0 за 1000мс, затем удаляется (мягкий породистый шлейф истории).
  • Прорастание линий (Edge Growth): новая линия тянется к ФИНАЛЬНОЙ точке узла и раскрывается stroke-dasharray(=длина пути) + stroke-dashoffset(длина→0), синхронно с разлётом узла (growP = текущая дистанция / финальная) → кончик «вытягивается» из центра вслед за аватаркой. Старые линии при этом исчезают мгновенно. Только для новых узлов; переезжающие — линия следует за ними.
  • Физика (только до-settle): после CSS-разлёта — лёгкая радиальная пружина + отталкивание для органичного покачивания; после фильтра физика НЕ включается (фиксация на равномерных углах).
  • Жёсткая заморозка (kill-switch): когда сумма |vx|+|vy| < 0.03 — скорости обнуляются, координаты округляются, cancelAnimationFrame (sleep). Нет «треска», батарея не страдает.
  • Линии: SVG <path> Q (квадратичные Безье) — тонкие изящные дуги (stroke-width ~1.32.2), градиент неон-центр → цвет роли. Изгиб реагирует на скорость.
  • Сияющие связи: линия к «сияющему» узлу — ярче (градиент в неон) и МОНОЛИТНО светится статичной тенью filter: drop-shadow(...) (тот же приём, что у ободка аватарки). Без бегущих импульсов.
  • Жесты: свайп-pan с инерцией (новое касание прерывает); короткий тап — центрирование + нижний сниппет; долгий тап — контекстное меню (в #modal-root, позиция по getBoundingClientRect); тап по центру — профиль. Нажатия на чипы фильтров гасят pointerdown (stopPropagation), чтобы сцена не перехватила указатель (setPointerCapture) и не «съела» click кнопки.
  • Фильтры слоёв (Все / Семья / Друзья / Сияющие): CSS-переходы 300мс — несоответствующие узлы и их линии гаснут НА МЕСТЕ (opacity 0 + scale 0.8), оставшиеся плавно переплывают на равномерные углы, затем жёсткая фиксация без физики (ноль тряски, мгновенный sleep).
  • Поллиш: «прицел» в центре (+пульс при захвате фокуса); «дыхание» фокуса (бесконечная CSS-анимация размера, GPU, не будит rAF); свечение «сияющих» узлов — мягкая медленная пульсация (3.6с) многослойной box-shadow + размытый ореол через SVG-фильтр #fg-shine-glow; тестовые фото-аватарки (NETWORK_PHOTOS); хард-лимит ~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.80 / 0.94 / 42 базовое трение / стартовая вязкость / длительность (~700мс)
BLOOM_MS / BLOOM_STAGGER 900 / 40 длительность CSS-разлёта / задержка между узлами (каскад)
SLEEP_V 0.03 порог суммарной
FOCUS_SCALE 1.5 базовый масштаб фокуса
MAX_FULL_NODES 90 хард-лимит полных аватарок (далее — точки)

Прочее (вшито в код): Ghost-слой — 1000мс; CSS-переход фильтра — 300мс; пульсация сияния — 3.6с; прорастание линий привязано к прогрессу разлёта узла (а не к отдельному таймеру).

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

  • 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.