diff --git a/src/components/GraphPanel.ts b/src/components/GraphPanel.ts index 4710611..e52730f 100644 --- a/src/components/GraphPanel.ts +++ b/src/components/GraphPanel.ts @@ -137,8 +137,8 @@ const dagreGraph = new dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({})); // Определяем размеры узлов для корректного расположения. // Может потребоваться корректировка в зависимости от реального размера узла в CSS. -const nodeWidth = 172; -const nodeHeight = 36; +const nodeWidth = 120; +const nodeHeight = 40; /** * Применяет алгоритм расположения Dagre к узлам и рёбрам графа. @@ -148,14 +148,27 @@ const nodeHeight = 36; * @returns {{nodes: any[], edges: any[]}} Объект с обновлёнными узлами и рёбрами, содержащими позиции. */ const getLayoutedElements = (nodes: any[], edges: any[], direction: 'TB' | 'LR' = 'TB') => { - const isHorizontal = direction === 'LR'; // Проверяем, горизонтальное ли расположение - // ДОБАВЛЕНО: ranksep и nodesep для лучшего отступа между узлами - dagreGraph.setGraph({ rankdir: direction, ranksep: 100, nodesep: 50 }); + const isHorizontal = direction === 'LR'; + // Компактные настройки для расположения в 2 ряда + dagreGraph.setGraph({ + rankdir: direction, + ranksep: 40, // Уменьшенное расстояние между рангами (рядами) + nodesep: 10, // Уменьшенное расстояние между узлами в одном ранге + edgesep: 20, // Расстояние между рёбрами + marginx: 20, // Отступы по X + marginy: 20 // Отступы по Y + }); - // Добавляем узлы в граф Dagre с их размерами + // Добавляем узлы в граф Dagre с динамическими размерами nodes.forEach((node) => { - // Убедимся, что узел имеет уникальный id и необходимые размеры для Dagre - dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight }); + // Пытаемся найти элемент узла в DOM для получения реальных размеров + const nodeElement = document.querySelector(`[data-id="${node.id}"]`) as HTMLElement; + const dimensions = getNodeDimensions(nodeElement); + + dagreGraph.setNode(node.id, { + width: Math.min(dimensions.width + 10, 140), // Ограничиваем максимальную ширину + height: Math.min(dimensions.height + 10, 50) // Ограничиваем максимальную высоту + }); }); // Добавляем рёбра в граф Dagre @@ -169,22 +182,34 @@ const getLayoutedElements = (nodes: any[], edges: any[], direction: 'TB' | 'LR' // Обновляем позиции узлов React Flow на основе расчётов Dagre const newNodes = nodes.map((node) => { - const nodeWithPosition: DagreNode = dagreGraph.node(node.id); // Получаем данные узла из Dagre - // Смещаем позицию узла Dagre (якорь в центре) к верхнему левому углу, - // чтобы она соответствовала точке привязки узла React Flow (верхний левый угол). + const nodeWithPosition: DagreNode = dagreGraph.node(node.id); + const nodeDimensions = dagreGraph.node(node.id); + return { ...node, - // Обновляем targetPosition и sourcePosition в зависимости от направления для корректного отображения ручек targetPosition: isHorizontal ? 'left' : 'top', sourcePosition: isHorizontal ? 'right' : 'bottom', position: { - x: nodeWithPosition.x - nodeWidth / 2, // Корректировка X - y: nodeWithPosition.y - nodeHeight / 2 // Корректировка Y + x: nodeWithPosition.x - nodeDimensions.width / 2, + y: nodeWithPosition.y - nodeDimensions.height / 2 } }; }); - return { nodes: newNodes, edges }; // Возвращаем обновлённые узлы и рёбра + return { nodes: newNodes, edges }; +}; + +// Функция для получения реальных размеров узла из DOM +const getNodeDimensions = (nodeElement: HTMLElement | null): { width: number; height: number } => { + if (!nodeElement) { + return { width: 120, height: 40 }; // Fallback размеры + } + + const rect = nodeElement.getBoundingClientRect(); + return { + width: Math.max(Math.min(rect.width, 130), 100), // Ограничиваем ширину: мин 100px, макс 130px + height: Math.max(Math.min(rect.height, 45), 30) // Ограничиваем высоту: мин 30px, макс 45px + }; }; /** @@ -214,29 +239,33 @@ export const GraphPanelComponent: React.FC = ({ graphData, onNo if (graphData.nodes.length > 0 || graphData.edges.length > 0) { console.log('Applying Dagre layout with data:', graphData); - // Преобразуем входящие данные в формат, необходимый для Dagre и React Flow - // Убеждаемся, что каждый узел имеет данные.label для отображения const initialNodesForLayout = graphData.nodes.map((node) => ({ id: node.id, - // ДОБАВЛЕНО: используем node.type, если он есть, иначе 'default' type: node.type || 'default', data: { label: node.data?.label || `Node ${node.id}` }, - position: { x: 0, y: 0 } // Временная позиция, будет перезаписана Dagre + position: { x: 0, y: 0 } })); + const initialEdgesForLayout = graphData.edges.map((edge) => ({ id: edge.id, source: edge.source, target: edge.target, - // Для рёбер можно добавить тип 'default' или ваш кастомный type: edge.type || 'default' })); - // Применяем Dagre layout - const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements(initialNodesForLayout, initialEdgesForLayout); + // Сначала устанавливаем узлы без layout + setNodes(initialNodesForLayout); + setEdges(initialEdgesForLayout); - // Обновляем состояния узлов и рёбер React Flow - setNodes(layoutedNodes); - setEdges(layoutedEdges); + // Применяем layout с задержкой, чтобы DOM успел отрендерить узлы + setTimeout(() => { + const { nodes: layoutedNodes, edges: layoutedEdges } = getLayoutedElements( + initialNodesForLayout, + initialEdgesForLayout + ); + setNodes(layoutedNodes); + setEdges(layoutedEdges); + }, 100); // Задержка 100ms для рендеринга DOM } else { // Если данных нет, очищаем граф setNodes([]); diff --git a/src/css/styles.css b/src/css/styles.css index 84886e9..379eb5f 100644 --- a/src/css/styles.css +++ b/src/css/styles.css @@ -178,6 +178,21 @@ background-color: var(--llm-assistant-background-color); /* Светло-серый фон */ } +/* Компактные стили для узлов React Flow */ +.react-flow__node { + font-size: 12px !important; + padding: 4px 8px !important; + min-width: 100px !important; + max-width: 130px !important; + min-height: 30px !important; + border-radius: 6px !important; +} + +.react-flow__node-default { + width: auto !important; + height: auto !important; +} + /* ----------------------------------------------- Chat Panel Styles -------------------------------------------------------- */ .llm-agent-chat-panel {