261 lines
10 KiB
C#
261 lines
10 KiB
C#
namespace StellaOps.ElkSharp;
|
|
|
|
internal static class ElkEdgeRouterAnchors
|
|
{
|
|
internal static ElkPoint ResolveAnchorPoint(
|
|
ElkPositionedNode node,
|
|
ElkPositionedNode otherNode,
|
|
string? portId,
|
|
ElkLayoutDirection direction,
|
|
string? forcedSide = null)
|
|
{
|
|
if (!string.IsNullOrWhiteSpace(portId))
|
|
{
|
|
var port = node.Ports.FirstOrDefault(x => string.Equals(x.Id, portId, StringComparison.Ordinal));
|
|
if (port is not null)
|
|
{
|
|
return new ElkPoint
|
|
{
|
|
X = port.X + (port.Width / 2d),
|
|
Y = port.Y + (port.Height / 2d),
|
|
};
|
|
}
|
|
}
|
|
|
|
var nodeCenterX = node.X + (node.Width / 2d);
|
|
var nodeCenterY = node.Y + (node.Height / 2d);
|
|
var otherCenterX = otherNode.X + (otherNode.Width / 2d);
|
|
var otherCenterY = otherNode.Y + (otherNode.Height / 2d);
|
|
|
|
if (Math.Abs(otherCenterX - nodeCenterX) < 0.001d
|
|
&& Math.Abs(otherCenterY - nodeCenterY) < 0.001d)
|
|
{
|
|
return new ElkPoint
|
|
{
|
|
X = nodeCenterX,
|
|
Y = nodeCenterY,
|
|
};
|
|
}
|
|
|
|
return ResolvePreferredAnchorPoint(node, otherCenterX, otherCenterY, forcedSide, direction);
|
|
}
|
|
|
|
internal static (ElkPoint SourcePoint, ElkPoint TargetPoint) ResolveStraightChainAnchors(
|
|
ElkPositionedNode sourceNode,
|
|
ElkPositionedNode targetNode,
|
|
ElkPoint sourcePoint,
|
|
ElkPoint targetPoint,
|
|
string sourceSide,
|
|
string targetSide,
|
|
EdgeChannel channel,
|
|
ElkLayoutDirection direction)
|
|
{
|
|
if (direction == ElkLayoutDirection.LeftToRight)
|
|
{
|
|
if (channel.ForwardCount != 1 || channel.TargetIncomingCount != 1 || targetPoint.X < sourcePoint.X)
|
|
{
|
|
return (sourcePoint, targetPoint);
|
|
}
|
|
|
|
var sharedY = sourceNode.Y + (sourceNode.Height / 2d);
|
|
return (
|
|
ResolvePreferredAnchorPoint(sourceNode, targetNode.X, sharedY, sourceSide, direction),
|
|
ResolvePreferredAnchorPoint(targetNode, sourceNode.X + sourceNode.Width, sharedY, targetSide, direction));
|
|
}
|
|
|
|
if (channel.ForwardCount != 1 || channel.TargetIncomingCount != 1 || targetPoint.Y < sourcePoint.Y)
|
|
{
|
|
return (sourcePoint, targetPoint);
|
|
}
|
|
|
|
var sharedX = sourceNode.X + (sourceNode.Width / 2d);
|
|
return (
|
|
ResolvePreferredAnchorPoint(sourceNode, sharedX, targetNode.Y, sourceSide, direction),
|
|
ResolvePreferredAnchorPoint(targetNode, sharedX, sourceNode.Y + sourceNode.Height, targetSide, direction));
|
|
}
|
|
|
|
internal static (string SourceSide, string TargetSide) ResolveRouteSides(
|
|
ElkPositionedNode sourceNode,
|
|
ElkPositionedNode targetNode,
|
|
ElkLayoutDirection direction)
|
|
{
|
|
var sourceCenterX = sourceNode.X + (sourceNode.Width / 2d);
|
|
var sourceCenterY = sourceNode.Y + (sourceNode.Height / 2d);
|
|
var targetCenterX = targetNode.X + (targetNode.Width / 2d);
|
|
var targetCenterY = targetNode.Y + (targetNode.Height / 2d);
|
|
var deltaX = targetCenterX - sourceCenterX;
|
|
var deltaY = targetCenterY - sourceCenterY;
|
|
|
|
if (direction == ElkLayoutDirection.LeftToRight)
|
|
{
|
|
if (Math.Abs(deltaX) >= 24d || Math.Abs(deltaX) >= Math.Abs(deltaY) * 0.35d)
|
|
{
|
|
return deltaX >= 0d
|
|
? ("EAST", "WEST")
|
|
: ("NORTH", "NORTH");
|
|
}
|
|
|
|
return deltaY >= 0d
|
|
? ("SOUTH", "NORTH")
|
|
: ("NORTH", "SOUTH");
|
|
}
|
|
|
|
if (Math.Abs(deltaY) >= 24d || Math.Abs(deltaY) >= Math.Abs(deltaX) * 0.35d)
|
|
{
|
|
return deltaY >= 0d
|
|
? ("SOUTH", "NORTH")
|
|
: ("NORTH", "NORTH");
|
|
}
|
|
|
|
return deltaX >= 0d
|
|
? ("EAST", "WEST")
|
|
: ("WEST", "EAST");
|
|
}
|
|
|
|
internal static ElkPoint ResolvePreferredAnchorPoint(
|
|
ElkPositionedNode node,
|
|
double targetX,
|
|
double targetY,
|
|
string? forcedSide,
|
|
ElkLayoutDirection direction)
|
|
{
|
|
var nodeCenterX = node.X + (node.Width / 2d);
|
|
var nodeCenterY = node.Y + (node.Height / 2d);
|
|
var deltaX = targetX - nodeCenterX;
|
|
var deltaY = targetY - nodeCenterY;
|
|
var insetX = Math.Min(18d, node.Width / 4d);
|
|
var insetY = Math.Min(18d, node.Height / 4d);
|
|
|
|
var preferredSide = forcedSide;
|
|
if (string.IsNullOrWhiteSpace(preferredSide))
|
|
{
|
|
preferredSide = direction == ElkLayoutDirection.LeftToRight
|
|
? (Math.Abs(deltaX) >= Math.Abs(deltaY) * 0.35d
|
|
? (deltaX >= 0d ? "EAST" : "WEST")
|
|
: (deltaY >= 0d ? "SOUTH" : "NORTH"))
|
|
: (Math.Abs(deltaY) >= Math.Abs(deltaX) * 0.35d
|
|
? (deltaY >= 0d ? "SOUTH" : "NORTH")
|
|
: (deltaX >= 0d ? "EAST" : "WEST"));
|
|
}
|
|
|
|
var preferredTargetX = preferredSide switch
|
|
{
|
|
"EAST" => node.X + node.Width + 256d,
|
|
"WEST" => node.X - 256d,
|
|
_ => ElkLayoutHelpers.Clamp(targetX, node.X + insetX, node.X + node.Width - insetX),
|
|
};
|
|
var preferredTargetY = preferredSide switch
|
|
{
|
|
"SOUTH" => node.Y + node.Height + 256d,
|
|
"NORTH" => node.Y - 256d,
|
|
_ => ElkLayoutHelpers.Clamp(targetY, node.Y + insetY, node.Y + node.Height - insetY),
|
|
};
|
|
|
|
var adjustedDeltaX = preferredTargetX - nodeCenterX;
|
|
var adjustedDeltaY = preferredTargetY - nodeCenterY;
|
|
|
|
var candidate = new ElkPoint
|
|
{
|
|
X = preferredSide switch
|
|
{
|
|
"EAST" => node.X + node.Width,
|
|
"WEST" => node.X,
|
|
_ => ElkLayoutHelpers.Clamp(preferredTargetX, node.X + insetX, node.X + node.Width - insetX),
|
|
},
|
|
Y = preferredSide switch
|
|
{
|
|
"SOUTH" => node.Y + node.Height,
|
|
"NORTH" => node.Y,
|
|
_ => ElkLayoutHelpers.Clamp(preferredTargetY, node.Y + insetY, node.Y + node.Height - insetY),
|
|
},
|
|
};
|
|
|
|
return ElkShapeBoundaries.ResolveGatewayBoundaryPoint(node, candidate, adjustedDeltaX, adjustedDeltaY);
|
|
}
|
|
|
|
internal static ElkPoint ComputeSmartAnchor(
|
|
ElkPositionedNode node,
|
|
ElkPoint? approachPoint,
|
|
bool isSource,
|
|
double spreadY,
|
|
int groupSize,
|
|
ElkLayoutDirection direction)
|
|
{
|
|
if (direction != ElkLayoutDirection.LeftToRight || approachPoint is null)
|
|
{
|
|
var fallback = isSource
|
|
? new ElkPoint { X = node.X + node.Width, Y = ElkLayoutHelpers.Clamp(spreadY, node.Y + 6d, node.Y + node.Height - 6d) }
|
|
: new ElkPoint { X = node.X, Y = ElkLayoutHelpers.Clamp(spreadY, node.Y + 6d, node.Y + node.Height - 6d) };
|
|
return ElkShapeBoundaries.ResolveGatewayBoundaryPoint(
|
|
node,
|
|
fallback,
|
|
fallback.X - (node.X + (node.Width / 2d)),
|
|
fallback.Y - (node.Y + (node.Height / 2d)));
|
|
}
|
|
|
|
var nodeCenterX = node.X + (node.Width / 2d);
|
|
var nodeCenterY = node.Y + (node.Height / 2d);
|
|
var deltaX = approachPoint.X - nodeCenterX;
|
|
var deltaY = approachPoint.Y - nodeCenterY;
|
|
|
|
if (isSource)
|
|
{
|
|
if (Math.Abs(deltaY) > Math.Abs(deltaX) * 1.5d && deltaY < 0d)
|
|
{
|
|
var topCandidate = new ElkPoint
|
|
{
|
|
X = ElkLayoutHelpers.Clamp(approachPoint.X, node.X + 8d, node.X + node.Width - 8d),
|
|
Y = node.Y,
|
|
};
|
|
return ElkShapeBoundaries.ResolveGatewayBoundaryPoint(node, topCandidate, topCandidate.X - nodeCenterX, topCandidate.Y - nodeCenterY);
|
|
}
|
|
|
|
if (Math.Abs(deltaY) > Math.Abs(deltaX) * 1.5d && deltaY > 0d)
|
|
{
|
|
var bottomCandidate = new ElkPoint
|
|
{
|
|
X = ElkLayoutHelpers.Clamp(approachPoint.X, node.X + 8d, node.X + node.Width - 8d),
|
|
Y = node.Y + node.Height,
|
|
};
|
|
return ElkShapeBoundaries.ResolveGatewayBoundaryPoint(node, bottomCandidate, bottomCandidate.X - nodeCenterX, bottomCandidate.Y - nodeCenterY);
|
|
}
|
|
|
|
var eastCandidate = new ElkPoint
|
|
{
|
|
X = node.X + node.Width,
|
|
Y = groupSize > 1 ? ElkLayoutHelpers.Clamp(spreadY, node.Y + 6d, node.Y + node.Height - 6d)
|
|
: ElkLayoutHelpers.Clamp(approachPoint.Y, node.Y + 6d, node.Y + node.Height - 6d),
|
|
};
|
|
return ElkShapeBoundaries.ResolveGatewayBoundaryPoint(node, eastCandidate, eastCandidate.X - nodeCenterX, eastCandidate.Y - nodeCenterY);
|
|
}
|
|
|
|
if (Math.Abs(deltaY) > Math.Abs(deltaX) * 0.8d && deltaY < 0d)
|
|
{
|
|
var topCandidate = new ElkPoint
|
|
{
|
|
X = ElkLayoutHelpers.Clamp(approachPoint.X, node.X + 8d, node.X + node.Width - 8d),
|
|
Y = node.Y,
|
|
};
|
|
return ElkShapeBoundaries.ResolveGatewayBoundaryPoint(node, topCandidate, topCandidate.X - nodeCenterX, topCandidate.Y - nodeCenterY);
|
|
}
|
|
|
|
if (Math.Abs(deltaY) > Math.Abs(deltaX) * 0.8d && deltaY > 0d)
|
|
{
|
|
var bottomCandidate = new ElkPoint
|
|
{
|
|
X = ElkLayoutHelpers.Clamp(approachPoint.X, node.X + 8d, node.X + node.Width - 8d),
|
|
Y = node.Y + node.Height,
|
|
};
|
|
return ElkShapeBoundaries.ResolveGatewayBoundaryPoint(node, bottomCandidate, bottomCandidate.X - nodeCenterX, bottomCandidate.Y - nodeCenterY);
|
|
}
|
|
|
|
var westCandidate = new ElkPoint
|
|
{
|
|
X = node.X,
|
|
Y = groupSize > 1 ? ElkLayoutHelpers.Clamp(spreadY, node.Y + 6d, node.Y + node.Height - 6d)
|
|
: ElkLayoutHelpers.Clamp(approachPoint.Y, node.Y + 6d, node.Y + node.Height - 6d),
|
|
};
|
|
return ElkShapeBoundaries.ResolveGatewayBoundaryPoint(node, westCandidate, westCandidate.X - nodeCenterX, westCandidate.Y - nodeCenterY);
|
|
}
|
|
}
|