Improve rendering
This commit is contained in:
201
src/__Libraries/StellaOps.ElkSharp/ElkEdgeChannelBands.cs
Normal file
201
src/__Libraries/StellaOps.ElkSharp/ElkEdgeChannelBands.cs
Normal file
@@ -0,0 +1,201 @@
|
||||
namespace StellaOps.ElkSharp;
|
||||
|
||||
internal static class ElkEdgeChannelBands
|
||||
{
|
||||
internal static void AllocateDirectForwardChannelBands(
|
||||
IReadOnlyCollection<ElkEdge> edges,
|
||||
IReadOnlyDictionary<string, ElkPositionedNode> positionedNodes,
|
||||
IReadOnlyDictionary<string, LayerBoundary> layerBoundariesByNodeId,
|
||||
Dictionary<string, EdgeChannel> channels,
|
||||
ElkLayoutDirection direction)
|
||||
{
|
||||
if (direction != ElkLayoutDirection.LeftToRight)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var candidates = edges
|
||||
.Select(edge => TryCreateDirectChannelCandidate(edge, positionedNodes, layerBoundariesByNodeId, channels))
|
||||
.Where(candidate => candidate is not null)
|
||||
.Select(candidate => candidate!.Value)
|
||||
.GroupBy(candidate => candidate.GapKey, StringComparer.Ordinal);
|
||||
|
||||
foreach (var gapGroup in candidates)
|
||||
{
|
||||
var ordered = gapGroup
|
||||
.OrderBy(candidate => candidate.TargetCenterY)
|
||||
.ThenBy(candidate => candidate.TargetX)
|
||||
.ThenBy(candidate => candidate.SourceCenterY)
|
||||
.ThenBy(candidate => candidate.FamilyPriority)
|
||||
.ThenBy(candidate => candidate.EdgeId, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
if (ordered.Length == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var gapMinX = ordered.Max(candidate => candidate.GapMinX);
|
||||
var gapMaxX = ordered.Min(candidate => candidate.GapMaxX);
|
||||
if (gapMaxX - gapMinX < 24d)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var edgePadding = Math.Min(28d, Math.Max(16d, (gapMaxX - gapMinX) * 0.12d));
|
||||
var usableMinX = gapMinX + edgePadding;
|
||||
var usableMaxX = gapMaxX - edgePadding;
|
||||
if (usableMaxX <= usableMinX)
|
||||
{
|
||||
usableMinX = gapMinX + 12d;
|
||||
usableMaxX = gapMaxX - 12d;
|
||||
}
|
||||
|
||||
for (var index = 0; index < ordered.Length; index++)
|
||||
{
|
||||
var preferredX = ordered.Length == 1
|
||||
? (usableMinX + usableMaxX) / 2d
|
||||
: usableMinX + ((usableMaxX - usableMinX) * (index / (double)(ordered.Length - 1)));
|
||||
preferredX = ElkLayoutHelpers.Clamp(preferredX, ordered[index].GapMinX + 8d, ordered[index].GapMaxX - 8d);
|
||||
|
||||
if (!channels.TryGetValue(ordered[index].EdgeId, out var channel))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
channels[ordered[index].EdgeId] = channel with
|
||||
{
|
||||
PreferredDirectChannelX = preferredX,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static DirectChannelCandidate? TryCreateDirectChannelCandidate(
|
||||
ElkEdge edge,
|
||||
IReadOnlyDictionary<string, ElkPositionedNode> positionedNodes,
|
||||
IReadOnlyDictionary<string, LayerBoundary> layerBoundariesByNodeId,
|
||||
IReadOnlyDictionary<string, EdgeChannel> channels)
|
||||
{
|
||||
if (!channels.TryGetValue(edge.Id, out var channel) || channel.RouteMode != EdgeRouteMode.Direct)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var source = positionedNodes[edge.SourceNodeId];
|
||||
var target = positionedNodes[edge.TargetNodeId];
|
||||
var sourceCenterX = source.X + (source.Width / 2d);
|
||||
var targetCenterX = target.X + (target.Width / 2d);
|
||||
if (targetCenterX <= sourceCenterX + 1d)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var sourceCenterY = source.Y + (source.Height / 2d);
|
||||
var targetCenterY = target.Y + (target.Height / 2d);
|
||||
if (Math.Abs(targetCenterY - sourceCenterY) < 56d)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var sourceBoundary = ElkLayoutHelpers.ResolveLayerBoundary(edge.SourceNodeId, layerBoundariesByNodeId, source);
|
||||
var targetBoundary = ElkLayoutHelpers.ResolveLayerBoundary(edge.TargetNodeId, layerBoundariesByNodeId, target);
|
||||
var gapMinX = sourceBoundary.MaxX + 12d;
|
||||
var gapMaxX = targetBoundary.MinX - 12d;
|
||||
if (gapMaxX - gapMinX < 48d)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var gapKey = $"{Math.Round(gapMinX, 2):0.##}|{Math.Round(gapMaxX, 2):0.##}";
|
||||
return new DirectChannelCandidate(
|
||||
edge.Id,
|
||||
gapKey,
|
||||
gapMinX,
|
||||
gapMaxX,
|
||||
ResolveLaneFamilyPriority(edge.Label),
|
||||
sourceCenterY,
|
||||
targetCenterY,
|
||||
target.X);
|
||||
}
|
||||
|
||||
internal static string ResolveLaneFamilyKey(string? label)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(label))
|
||||
{
|
||||
return "default";
|
||||
}
|
||||
|
||||
var normalized = label.Trim().ToLowerInvariant();
|
||||
if (normalized.Contains("failure", StringComparison.Ordinal))
|
||||
{
|
||||
return "failure";
|
||||
}
|
||||
|
||||
if (normalized.Contains("timeout", StringComparison.Ordinal))
|
||||
{
|
||||
return "timeout";
|
||||
}
|
||||
|
||||
if (normalized.StartsWith("repeat ", StringComparison.Ordinal)
|
||||
|| normalized.Equals("body", StringComparison.Ordinal))
|
||||
{
|
||||
return "repeat";
|
||||
}
|
||||
|
||||
if (normalized.StartsWith("when ", StringComparison.Ordinal))
|
||||
{
|
||||
return "success";
|
||||
}
|
||||
|
||||
if (normalized.Contains("otherwise", StringComparison.Ordinal)
|
||||
|| normalized.Contains("default", StringComparison.Ordinal))
|
||||
{
|
||||
return "default";
|
||||
}
|
||||
|
||||
if (normalized.Contains("missing condition", StringComparison.Ordinal))
|
||||
{
|
||||
return "missing-condition";
|
||||
}
|
||||
|
||||
return "default";
|
||||
}
|
||||
|
||||
internal static int ResolveLaneFamilyPriority(string? label)
|
||||
{
|
||||
return ResolveLaneFamilyKey(label) switch
|
||||
{
|
||||
"failure" => 0,
|
||||
"timeout" => 1,
|
||||
"repeat" => 2,
|
||||
"default" => 3,
|
||||
"success" => 4,
|
||||
"missing-condition" => 5,
|
||||
_ => 6,
|
||||
};
|
||||
}
|
||||
|
||||
internal static int ResolveSinkLanePriority(string? label)
|
||||
{
|
||||
return ResolveLaneFamilyKey(label) switch
|
||||
{
|
||||
"default" => 0,
|
||||
"success" => 1,
|
||||
"repeat" => 2,
|
||||
"timeout" => 3,
|
||||
"failure" => 4,
|
||||
"missing-condition" => 5,
|
||||
_ => 6,
|
||||
};
|
||||
}
|
||||
|
||||
internal static double ResolveSinkBandOffset(int bandIndex, double firstSpacing = 36d, double subsequentSpacing = 28d)
|
||||
{
|
||||
if (bandIndex <= 0)
|
||||
{
|
||||
return 0d;
|
||||
}
|
||||
|
||||
return firstSpacing + ((bandIndex - 1) * subsequentSpacing);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user