From 8a62b4cace67ba97bc8c732947857193b3819e30 Mon Sep 17 00:00:00 2001 From: darshanr0107 Date: Tue, 29 Jul 2025 12:21:42 +0530 Subject: [PATCH] fix: apply intersection logic for edges in tidy-tree layout on-behalf-of: @Mermaid-Chart --- .../layout-algorithms/tidy-tree/layout.ts | 137 ++++++++++++++---- .../rendering-elements/shapes/bang.ts | 2 +- 2 files changed, 107 insertions(+), 32 deletions(-) diff --git a/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/layout.ts b/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/layout.ts index 1e8a37b8e..6261c52ef 100644 --- a/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/layout.ts +++ b/packages/mermaid/src/rendering-util/layout-algorithms/tidy-tree/layout.ts @@ -3,6 +3,7 @@ import type { MermaidConfig } from '../../../config.type.js'; import { log } from '../../../logger.js'; import type { LayoutData, Node, Edge } from '../../types.js'; import type { LayoutResult, TidyTreeNode, PositionedNode, PositionedEdge } from './types.js'; +import { intersection } from '../../rendering-elements/edges.js'; let intersectionShift = 50; /** @@ -448,15 +449,48 @@ function positionRightTreeBidirectional( }); } +/** + * Calculate the intersection point of a line with a circle + * @param circle - Circle coordinates and radius + * @param lineStart - Starting point of the line + * @param lineEnd - Ending point of the line + * @returns The intersection point + */ +function computeCircleEdgeIntersection( + circle: { x: number; y: number; width: number; height: number }, + lineStart: { x: number; y: number }, + lineEnd: { x: number; y: number } +): { x: number; y: number } { + const radius = Math.min(circle.width, circle.height) / 2; + + const dx = lineEnd.x - lineStart.x; + const dy = lineEnd.y - lineStart.y; + const length = Math.sqrt(dx * dx + dy * dy); + + if (length === 0) { + return lineStart; + } + + // Normalize the direction vector from Start to End + const nx = dx / length; + const ny = dy / length; + + // Start at the circle center and go backwards by the radius along the direction vector + return { + x: circle.x - nx * radius, + y: circle.y - ny * radius, + }; +} + /** * Calculate edge positions based on positioned nodes * Now includes tree membership and node dimensions for precise edge calculations + * Edges now stop at shape boundaries instead of extending to centers */ function calculateEdgePositions( edges: Edge[], positionedNodes: PositionedNode[] ): PositionedEdge[] { - // Create a comprehensive map of node information const nodeInfo = new Map(); positionedNodes.forEach((node) => { nodeInfo.set(node.id, node); @@ -465,33 +499,10 @@ function calculateEdgePositions( return edges.map((edge) => { const sourceNode = nodeInfo.get(edge.start ?? ''); const targetNode = nodeInfo.get(edge.end ?? ''); - log.debug('APA01 calculateEdgePositions', edge, sourceNode, 'targetNode', targetNode); - if (!sourceNode) { - log.error('APA01 Source node not found for edge', edge); - // Return a default edge instead of undefined - return { - id: edge.id, - source: edge.start ?? '', - target: edge.end ?? '', - startX: 0, - startY: 0, - midX: 0, - midY: 0, - endX: 0, - endY: 0, - points: [{ x: 0, y: 0 }], - sourceSection: undefined, - targetSection: undefined, - sourceWidth: undefined, - sourceHeight: undefined, - targetWidth: undefined, - targetHeight: undefined, - }; - } - if (targetNode === undefined) { - log.error('APA01 Target node not found for edge', edge); - // Return a default edge instead of undefined + if (!sourceNode || !targetNode) { + const missingNode = !sourceNode ? 'Source' : 'Target'; + log.error(`APA01 ${missingNode} node not found for edge`, edge); return { id: edge.id, source: edge.start ?? '', @@ -512,11 +523,43 @@ function calculateEdgePositions( }; } - // Fallback positions if nodes not found - const startPos = sourceNode ? { x: sourceNode.x, y: sourceNode.y } : { x: 0, y: 0 }; - const endPos = targetNode ? { x: targetNode.x, y: targetNode.y } : { x: 0, y: 0 }; + const sourceCenter = { x: sourceNode.x, y: sourceNode.y }; + const targetCenter = { x: targetNode.x, y: targetNode.y }; + + const isSourceRound = ['circle', 'cloud', 'bang'].includes( + sourceNode.originalNode?.shape ?? '' + ); + const isTargetRound = ['circle', 'cloud', 'bang'].includes( + targetNode.originalNode?.shape ?? '' + ); + + // Initial intersection approximation + let startPos = isSourceRound + ? computeCircleEdgeIntersection( + { + x: sourceNode.x, + y: sourceNode.y, + width: sourceNode.width ?? 100, + height: sourceNode.height ?? 100, + }, + targetCenter, + sourceCenter + ) + : intersection(sourceNode, sourceCenter, targetCenter); + + let endPos = isTargetRound + ? computeCircleEdgeIntersection( + { + x: targetNode.x, + y: targetNode.y, + width: targetNode.width ?? 100, + height: targetNode.height ?? 100, + }, + sourceCenter, + targetCenter + ) + : intersection(targetNode, targetCenter, sourceCenter); - // Calculate midpoint for edge const midX = (startPos.x + endPos.x) / 2; const midY = (startPos.y + endPos.y) / 2; @@ -546,6 +589,38 @@ function calculateEdgePositions( points.push(endPos); + // Recalculate source intersection + const secondPoint = points.length > 1 ? points[1] : targetCenter; + startPos = isSourceRound + ? computeCircleEdgeIntersection( + { + x: sourceNode.x, + y: sourceNode.y, + width: sourceNode.width ?? 100, + height: sourceNode.height ?? 100, + }, + secondPoint, + sourceCenter + ) + : intersection(sourceNode, secondPoint, sourceCenter); + points[0] = startPos; + + // Recalculate target intersection + const secondLastPoint = points.length > 1 ? points[points.length - 2] : sourceCenter; + endPos = isTargetRound + ? computeCircleEdgeIntersection( + { + x: targetNode.x, + y: targetNode.y, + width: targetNode.width ?? 100, + height: targetNode.height ?? 100, + }, + secondLastPoint, + targetCenter + ) + : intersection(targetNode, secondLastPoint, targetCenter); + points[points.length - 1] = endPos; + return { id: edge.id, source: edge.start ?? '', diff --git a/packages/mermaid/src/rendering-util/rendering-elements/shapes/bang.ts b/packages/mermaid/src/rendering-util/rendering-elements/shapes/bang.ts index b98917f5a..446cd0c58 100644 --- a/packages/mermaid/src/rendering-util/rendering-elements/shapes/bang.ts +++ b/packages/mermaid/src/rendering-util/rendering-elements/shapes/bang.ts @@ -17,7 +17,7 @@ export async function bang(parent: D3Selection, getNodeClasses(node) ); - const w = bbox.width + 2 * halfPadding; + const w = bbox.width + 8 * halfPadding; const h = bbox.height + 2 * halfPadding; const r = 0.15 * w; const { cssStyles } = node;