using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Msagl.Core; using Microsoft.Msagl.Core.Geometry; using Microsoft.Msagl.Core.Geometry.Curves; using Microsoft.Msagl.Core.Layout; namespace Microsoft.Msagl.Routing { /// /// Basic edge router for producing straight edges. /// public class StraightLineEdges : AlgorithmBase { private readonly IEnumerable edges; private readonly double padding; /// /// Constructs a basic straight edge router. /// public StraightLineEdges(IEnumerable edges, double padding) { this.edges = edges; this.padding = padding; } /// /// Executes the algorithm. /// protected override void RunInternal() { this.StartListenToLocalProgress(edges.Count()); SplineRouter.CreatePortsIfNeeded(edges); foreach (Edge edge in edges) { RouteEdge(edge, this.padding); this.ProgressStep(); } } /// /// populate the geometry including curve and arrowhead positioning for the given edge using simple /// straight line routing style. Self edges will be drawn as a loop, padding is used to control the /// size of the loop. /// /// edge to route /// controls size of loop public static void RouteEdge(Edge edge, double padding) { ValidateArg.IsNotNull(edge, "edge"); var eg = edge.EdgeGeometry; if (eg.SourcePort == null) { #if SHARPKIT // Lambdas bind differently in JS eg.SourcePort = ((Func)(ed => new RelativeFloatingPort(() => ed.Source.BoundaryCurve, () => ed.Source.Center)))(edge); #else eg.SourcePort = new RelativeFloatingPort(() => edge.Source.BoundaryCurve, () => edge.Source.Center); #endif } if (eg.TargetPort == null) { #if SHARPKIT // Lambdas bind differently in JS eg.TargetPort = ((Func)(ed => new RelativeFloatingPort(() => ed.Target.BoundaryCurve, () => ed.Target.Center)))(edge); #else eg.TargetPort = new RelativeFloatingPort(() => edge.Target.BoundaryCurve, () => edge.Target.Center); #endif } if (!ContainmentLoop(eg, padding)) { eg.Curve = GetEdgeLine(edge); } Arrowheads.TrimSplineAndCalculateArrowheads(eg, eg.SourcePort.Curve, eg.TargetPort.Curve, edge.Curve, false, false); } internal static bool ContainmentLoop(EdgeGeometry eg, double padding) { var sourceCurve = eg.SourcePort.Curve; var targetCurve = eg.TargetPort.Curve; if (sourceCurve == null || targetCurve == null) return false; Rectangle targetBox = sourceCurve.BoundingBox; Rectangle sourceBox = targetCurve.BoundingBox; bool targetInSource = targetBox.Contains(sourceBox); bool sourceInTarget = (!targetInSource) && sourceBox.Contains(targetBox); if (targetInSource || sourceInTarget) { eg.Curve = CreateLoop(targetBox, sourceBox, sourceInTarget, padding); return true; } return false; } private static Curve CreateLoop(Rectangle targetBox, Rectangle sourceBox, bool sourceContainsTarget, double padding) { return sourceContainsTarget ? CreateLoop(targetBox, sourceBox, padding, false) : CreateLoop(sourceBox, targetBox, padding, true); } /// /// creates a loop from sourceBox center to the closest point on the targetBox boundary /// /// /// contains sourceBox /// /// reverse the loop if true /// internal static Curve CreateLoop(Rectangle sourceBox, Rectangle targetBox, double howMuchToStickOut, bool reverse) { var center=sourceBox.Center; var closestPoint = FindClosestPointOnBoxBoundary(sourceBox.Center, targetBox); var dir = closestPoint - center; var vert=Math.Abs(dir.X) /// Returns a line segment for the given edge. /// /// The line segment representing the given edge. public static LineSegment GetEdgeLine(Edge edge) { ValidateArg.IsNotNull(edge, "edge"); Point sourcePoint; ICurve sourceBox; if (edge.SourcePort == null) { sourcePoint = edge.Source.Center; sourceBox = edge.Source.BoundaryCurve; } else { sourcePoint = edge.SourcePort.Location; sourceBox = edge.SourcePort.Curve; } Point targetPoint; ICurve targetBox; if (edge.TargetPort == null) { targetPoint = edge.Target.Center; targetBox = edge.Target.BoundaryCurve; } else { targetPoint = edge.TargetPort.Location; targetBox = edge.TargetPort.Curve; } LineSegment line = new LineSegment(sourcePoint, targetPoint); IList intersects = Curve.GetAllIntersections(sourceBox, line, false); if (intersects.Count > 0) { var trimmedLine = (LineSegment)line.Trim(intersects[0].Par1, 1.0); if(trimmedLine != null) { line = trimmedLine; intersects = Curve.GetAllIntersections(targetBox, line, false); if (intersects.Count > 0) { trimmedLine = (LineSegment)line.Trim(0.0, intersects[0].Par1); if(trimmedLine != null) { line = trimmedLine; } } } } return line; } /// /// creates an edge curve based only on the source and target geometry /// /// public static void CreateSimpleEdgeCurveWithUnderlyingPolyline(Edge edge) { ValidateArg.IsNotNull(edge, "edge"); var a = edge.Source.Center; var b = edge.Target.Center; if (edge.Source == edge.Target) { var dx = 2.0 / 3 * edge.Source.BoundaryCurve.BoundingBox.Width; var dy = edge.Source.BoundingBox.Height / 4; edge.UnderlyingPolyline = CreateUnderlyingPolylineForSelfEdge(a, dx, dy); edge.Curve = edge.UnderlyingPolyline.CreateCurve(); } else { edge.UnderlyingPolyline = SmoothedPolyline.FromPoints(new[] { a, b }); edge.Curve = edge.UnderlyingPolyline.CreateCurve(); } Arrowheads.TrimSplineAndCalculateArrowheads(edge.EdgeGeometry, edge.Source.BoundaryCurve, edge.Target.BoundaryCurve, edge.Curve, false, false); } [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "Microsoft.Msagl.Core.Geometry.Site")] internal static SmoothedPolyline CreateUnderlyingPolylineForSelfEdge(Point p0, double dx, double dy) { var p1 = p0 + new Point(0, dy); var p2 = p0 + new Point(dx, dy); var p3 = p0 + new Point(dx, -dy); var p4 = p0 + new Point(0, -dy); var site = new Site(p0); var polyline = new SmoothedPolyline(site); site = new Site(site, p1); site = new Site(site, p2); site = new Site(site, p3); site = new Site(site, p4); new Site(site, p0); return polyline; } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "Microsoft.Msagl.Site")] static internal void SetStraightLineEdgesWithUnderlyingPolylines(GeometryGraph graph) { SplineRouter.CreatePortsIfNeeded(graph.Edges); foreach (Edge edge in graph.Edges) CreateSimpleEdgeCurveWithUnderlyingPolyline(edge); } } }