SHiNE-server/shine-UI/Dev_Docs/features/interactive-network-graph.md
Pixel 9ee6bf4380 Связи: полировка карты связей (свечение, прорастание линий, 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 21:23:17 +03:00

89 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Интерактивная карта связей (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.js``networkGraphUsers` (связанный мульти-граф из 10 человек для лаборатории).
- `styles/network-graph.css` — все стили `.fg-*`.
## Данные (read-only, сервер не трогаем)
Единый источник — `authService.getUserConnectionsGraph(login)` (один запрос: логин → прямые связи).
`network-view.js``buildGraphModel()` нормализует роли (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 | порог суммарной |v| для заморозки |
| `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.