Improve rendering
This commit is contained in:
260
src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterAnchors.cs
Normal file
260
src/__Libraries/StellaOps.ElkSharp/ElkEdgeRouterAnchors.cs
Normal file
@@ -0,0 +1,260 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user