From fac3fa887df421dcb441badcda838c7ec46f0000 Mon Sep 17 00:00:00 2001 From: Atharva Sawant Date: Fri, 19 Apr 2024 14:17:38 +0530 Subject: [PATCH] Improved node layout in the map interface --- src/utils/nodeLayoutGenerator.ts | 99 ++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 42 deletions(-) diff --git a/src/utils/nodeLayoutGenerator.ts b/src/utils/nodeLayoutGenerator.ts index 96fef129..1729f3c0 100644 --- a/src/utils/nodeLayoutGenerator.ts +++ b/src/utils/nodeLayoutGenerator.ts @@ -24,48 +24,40 @@ export function generateNodeLayout(nodes: OrgNode[]): { nodes: FlowNode[]; edges const flowEdges: FlowEdge[] = [] let nodeCounter = 0 - const levelPositions: Map = new Map() // Track position within each level + const nodeMinWidth = 280 // Minimum width needed for each node card + const siblingPadding = 50 // Padding between sibling nodes + // First, calculate the width requirements for each subtree from bottom up + function calculateSubtreeWidth(node: OrgNode): number { + if (node.children.length === 0) { + return nodeMinWidth + } + + // Calculate total width needed for all children plus padding between them + const childrenWidths = node.children.map(child => calculateSubtreeWidth(child)) + const totalChildrenWidth = childrenWidths.reduce((sum, width) => sum + width, 0) + const paddingWidth = Math.max(0, (node.children.length - 1) * siblingPadding) + const totalWidthWithPadding = totalChildrenWidth + paddingWidth + + // Node needs at least its own width or the width of all its children with padding + return Math.max(nodeMinWidth, totalWidthWithPadding) + } + + // Calculate positions using the width requirements function processNode( - node: OrgNode, - level: number, - parentId: string | null, - parentX: number = 400, - parentY: number = 100 + node: OrgNode, + level: number, + parentId: string | null, + availableX: number, + availableWidth: number ): string { const nodeId = `node-${nodeCounter++}` - // Hierarchical positioning: levels go top to bottom - const yPosition = 100 + (level - 1) * 200 // 200px between levels + // Y position based on level + const yPosition = 100 + (level - 1) * 200 - // Get current position for this level - let xPosition: number - if (level === 1) { - // Root nodes: center them horizontally - const rootIndex = flowNodes.filter(n => n.data.level === 1).length - const rootSpacing = 350 - const totalRootWidth = (nodes.length - 1) * rootSpacing - xPosition = 400 + rootIndex * rootSpacing - totalRootWidth / 2 - } else { - // Child nodes: position relative to parent - const currentLevelCount = levelPositions.get(level) || 0 - levelPositions.set(level, currentLevelCount + 1) - - // Calculate children positions spread around parent - const parentNode = flowNodes.find(n => n.id === parentId) - if (parentNode) { - const siblingCount = node.children?.length || 1 - const parentChildren = flowNodes.filter(n => - flowEdges.some(e => e.source === parentId && e.target === n.id) - ).length - - const childSpacing = Math.max(250, 400 / Math.max(siblingCount, 1)) - const totalWidth = (siblingCount - 1) * childSpacing - xPosition = parentX + (parentChildren * childSpacing) - (totalWidth / 2) - } else { - xPosition = parentX + (currentLevelCount * 300) - } - } + // X position centered within available width + const xPosition = availableX + availableWidth / 2 - nodeMinWidth / 2 flowNodes.push({ id: nodeId, @@ -77,7 +69,7 @@ export function generateNodeLayout(nodes: OrgNode[]): { nodes: FlowNode[]; edges } }) - // Add edge from parent - center to center diagonal + // Add edge from parent if (parentId) { flowEdges.push({ id: `edge-${parentId}-${nodeId}`, @@ -87,17 +79,40 @@ export function generateNodeLayout(nodes: OrgNode[]): { nodes: FlowNode[]; edges }) } - // Process children - node.children.forEach((child, index) => { - processNode(child, level + 1, nodeId, xPosition, yPosition) - }) + // Process children with proper spacing + if (node.children.length > 0) { + const childWidths = node.children.map(child => calculateSubtreeWidth(child)) + const totalChildWidth = childWidths.reduce((sum, width) => sum + width, 0) + const paddingWidth = Math.max(0, (node.children.length - 1) * siblingPadding) + const totalWidthWithPadding = totalChildWidth + paddingWidth + + let currentX = availableX + (availableWidth - totalWidthWithPadding) / 2 + + node.children.forEach((child, index) => { + const childWidth = childWidths[index] + processNode(child, level + 1, nodeId, currentX, childWidth) + currentX += childWidth + siblingPadding // Add padding after each child except the last + }) + } return nodeId } + // Calculate total width needed for all root nodes + const rootWidths = nodes.map(node => calculateSubtreeWidth(node)) + const totalRootWidth = rootWidths.reduce((sum, width) => sum + width, 0) + const rootPaddingWidth = Math.max(0, (nodes.length - 1) * siblingPadding) + const totalRootWidthWithPadding = totalRootWidth + rootPaddingWidth + + // Start positioning from the left, centered in viewport + const startX = Math.max(50, (1400 - totalRootWidthWithPadding) / 2) // Increased viewport assumption + let currentX = startX + // Process all root nodes nodes.forEach((node, index) => { - processNode(node, 1, null) + const rootWidth = rootWidths[index] + processNode(node, 1, null, currentX, rootWidth) + currentX += rootWidth + siblingPadding // Add padding between root nodes too }) return { nodes: flowNodes, edges: flowEdges }