diff --git a/packages/apollo-react/src/canvas/components/Edges/shared/geometry.test.ts b/packages/apollo-react/src/canvas/components/Edges/shared/geometry.test.ts index 2c1abf9b8..dceaf35a7 100644 --- a/packages/apollo-react/src/canvas/components/Edges/shared/geometry.test.ts +++ b/packages/apollo-react/src/canvas/components/Edges/shared/geometry.test.ts @@ -136,6 +136,16 @@ describe('buildPathVertices', () => { expect(isOrthogonal(result)).toBe(true); }); + it('keeps a slight endpoint displacement orthogonal instead of falling back to a diagonal', () => { + const result = buildPathVertices(0, 0, Position.Right, 200, 8, Position.Left, []); + expect(isOrthogonal(result)).toBe(true); + }); + + it.each([8, 15, 16])('keeps an offset of %ipx orthogonal', (offset) => { + const result = buildPathVertices(0, 0, Position.Right, 200, offset, Position.Left, []); + expect(isOrthogonal(result)).toBe(true); + }); + it('always draws through a single off-line waypoint (regression)', () => { const result = buildPathVertices(0, 0, Position.Right, 200, 0, Position.Left, [ wp('a', 100, 50), diff --git a/packages/apollo-react/src/canvas/components/Edges/shared/geometry.ts b/packages/apollo-react/src/canvas/components/Edges/shared/geometry.ts index 7839b7b57..9e5ed5903 100644 --- a/packages/apollo-react/src/canvas/components/Edges/shared/geometry.ts +++ b/packages/apollo-react/src/canvas/components/Edges/shared/geometry.ts @@ -30,6 +30,29 @@ export function isHorizontalPosition(position: Position): boolean { return getDirection(position).dx !== 0; } +/** The axis a source/target pair already shares, when both faces exit along it + * and the cross-axis offset is within {@link EDGE_CONSTANTS.COLLINEAR_TOLERANCE}. + * `null` when they are offset enough to need a mid-axis jog, or when the faces + * exit on different axes (an L-shape, which never needs one). */ +function getCollinearAxis( + sourceX: number, + sourceY: number, + sourcePosition: Position, + targetX: number, + targetY: number, + targetPosition: Position +): 'horizontal' | 'vertical' | null { + if (isHorizontalPosition(sourcePosition) && isHorizontalPosition(targetPosition)) { + return Math.abs(sourceY - targetY) <= TOL ? 'horizontal' : null; + } + + if (!isHorizontalPosition(sourcePosition) && !isHorizontalPosition(targetPosition)) { + return Math.abs(sourceX - targetX) <= TOL ? 'vertical' : null; + } + + return null; +} + /** * Auto-route a path between source and target with orthogonal segments * when no manual waypoints are provided. @@ -52,18 +75,26 @@ export function calculateAutoWaypoints( const isSourceHorizontal = isHorizontalPosition(sourcePosition); const isTargetHorizontal = isHorizontalPosition(targetPosition); + const collinearAxis = getCollinearAxis( + startX, + startY, + sourcePosition, + endX, + endY, + targetPosition + ); const waypoints: Point[] = []; if (isSourceHorizontal && isTargetHorizontal) { const midX = snapToGrid((startX + endX) / 2); - if (Math.abs(startY - endY) > MIN_SEGMENT_LENGTH / 2) { + if (collinearAxis !== 'horizontal') { waypoints.push({ x: midX, y: startY }); waypoints.push({ x: midX, y: endY }); } } else if (!isSourceHorizontal && !isTargetHorizontal) { const midY = snapToGrid((startY + endY) / 2); - if (Math.abs(startX - endX) > MIN_SEGMENT_LENGTH / 2) { + if (collinearAxis !== 'vertical') { waypoints.push({ x: startX, y: midY }); waypoints.push({ x: endX, y: midY }); }