React + TypeScript + Vite приложение для визуализации и управления графом знаний.
Проект организован по принципу разделения ответственности (Separation of Concerns).
src/pages: Страницы приложения. Каждая страница — это отдельный маршрут (Route).ExplorePage.tsx: Главная страница с интерактивным графом (vis-network).AnalyticsPage.tsx: Страница аналитики.RoadmapPage.tsx: Дорожная карта обучения.
src/context: Глобальное управление состоянием (State Management).GraphContext.tsx: Хранилище данных графа, которое живет "над" страницами и сохраняет данные при навигации.
src/components: Переиспользуемые UI компоненты.NodeDetailsSidebar.tsx: Боковая панель с детальной информацией об узле.
src/config: Конфигурационные файлы (UI темы, константы).graphTheme.ts: Цвета узлов, толщина линий, настройки физики.appConfig.ts: Глобальные настройки приложения (например, ID стартового узла).
src/api.ts: Слой общения с Бэкендом (запросы fetch/axios).
Страница ExplorePage отвечает за отрисовку графа знаний. Она использует библиотеку vis-network, которая рисует узлы и связи на Canvas и симулирует физику (отталкивание узлов).
В React при переходе между вкладками (например, "Explore" -> "Аналитика") компоненты страницы полностью уничтожаются. Это значит, что граф пропадает, а при возвращении загружается заново (узлы разлетаются, камера сбрасывается в центр).
Мы реализовали механизм "кеширования" состояния графа, чтобы переходы были мгновенными и незаметными для пользователя.
Как это работает (Пошагово):
-
Глобальное Хранилище (
GraphContext)- Мы создали контекст, который оборачивает все приложение (
main.tsx). - Он хранит:
viewport(сами данные: узлы и связи),camera(координаты камеры и зум) иpositions(координаты каждого узла X,Y). - Поскольку контекст находится на самом верху, он не уничтожается при смене страниц.
- Мы создали контекст, который оборачивает все приложение (
-
Загрузка Страницы (Монтирование)
- Когда вы открываете
ExplorePage, компонент проверяет: "Есть ли данные в Контексте?". - Если ДА: Он берет готовые данные и координаты узлов. Граф создается сразу в нужном виде, без анимации "разлетания".
- Если НЕТ: Он делает запрос к API (
getViewport), загружает данные и рисует их с нуля.
- Когда вы открываете
-
Восстановление Камеры
- Сразу после создания графа мы смотрим в Контекст: "Была ли сохранена позиция камеры?".
- Если была, мы вызываем
network.moveTo(...), чтобы мгновенно перенести взгляд пользователя в ту точку, где он был в прошлый раз.
-
Уход со Страницы (Размонтирование)
- Когда вы нажимаете на другую вкладку, срабатывает функция очистки (
cleanup). - В этот момент мы "замораживаем" текущее состояние: берем текущие координаты камеры и позиции всех узлов и сохраняем их в
GraphContext.
- Когда вы нажимаете на другую вкладку, срабатывает функция очистки (
При взаимодействии с графом пользователь может получить подробности об узле.
- Клик по узлу:
vis-networkгенерирует событиеselectNode.- Мы обновляем состояние
detailsUid(ID выбранного узла).
- Открытие Панели:
- Справа плавно выезжает компонент
NodeDetailsSidebar. - Этот компонент делает отдельный запрос к API (
/v1/graph/node/{uid}), чтобы получить полные данные (метаданные, списки связей, описание). - В панели отображаются:
- Заголовок и Тип узла.
- Входящие связи (кто ссылается на этот узел).
- Исходящие связи (на кого ссылается этот узел).
- Действия ("Спросить AI", "Начать учить").
- Справа плавно выезжает компонент
Весь дизайн графа вынесен в файл src/config/graphTheme.ts. Вам не нужно искать цвета по коду компонентов.
Пример graphTheme.ts:
export const GRAPH_THEME = {
nodes: {
colors: {
Subject: '#ff9f1c', // Цвет предметов
Topic: '#7c5cff', // Цвет тем
},
sizes: {
Subject: 40, // Размер предметов
Topic: 24,
}
},
physics: {
springLength: 200, // Длина связей (пружинок)
}
}Чтобы изменить цвет или размер, просто поменяйте значения в этом файле.
# Установка зависимостей
npm install
# Запуск локального сервера (доступен на http://localhost:5173)
npm run dev- useRef: Используется для хранения ссылок на
network(инстанс графа) иcameraRef, чтобы иметь доступ к актуальным данным внутри замыканийuseEffectи таймеров, не вызывая лишних перерисовок. - Race Conditions: При инициализации
vis-networkиспользуетсяsetTimeout(..., 100), чтобы дать браузеру время отрисовать контейнер (DOM) перед тем, как двигать камеру. Без этого координаты могут быть некорректными.
Реализованы ключевые функции для превращения прототипа в полноценный инструмент анализа.
- Проблема: При переключении вкладок граф пересоздавался, теряя позицию камеры и заставляя узлы "разлетаться".
- Решение:
- Внедрен
GraphContextдля глобального хранения состояния (viewport, camera, node positions). - Реализована логика восстановления позиции камеры и координат узлов при монтировании компонента.
- Устранены лишние ре-рендеры и гонки состояний (Race Conditions).
- Внедрен
- Задача: При клике на узел (событие
selectNode) открывать детальную панель (Sidebar/Drawer) справа. - Решение:
- Создан компонент
NodeDetailsSidebar, который рендерится условно при наличииdetailsUid. - Реализована загрузка расширенных данных узла (incoming/outgoing relations) через API.
- Добавлены кнопки действий ("Спросить AI", "Начать учить").
- Панель имеет анимацию появления и кнопку закрытия.
- Создан компонент