using System; using System.Collections.Generic; using System.Linq; using System.Diagnostics; using Microsoft.Msagl.Core.Geometry; using Microsoft.Msagl.Core.Layout; using Microsoft.Msagl.Core.Routing; using Microsoft.Msagl.Core; namespace Microsoft.Msagl.Routing.Rectilinear{ /// /// public static class RectilinearInteractiveEditor { /// /// Create a RectilinearEdgeRouter from the passed obstacleNodes, with one port at the center of each obstacle, /// and route between the obstacles. /// /// The radius of the arc inscribed into path corners /// The minimum padding from an obstacle's curve to its enclosing polyline /// The nodes of the graph /// Edges defining the nodes to route between, and receiving the resultant paths /// Mode of the edges (Rectilinear or RectilinearToCenter). /// Use a more memory-efficient but possibly path-suboptimal visibility graph /// Use obstacle bounding box rectangles in visibility graph /// The cost penalty for a bend in the path, as a percentage of the Manhattan distance /// between the source target ports. static public void CreatePortsAndRouteEdges(double cornerFitRadius, double padding , IEnumerable obstacleNodes, IEnumerable geometryEdges , EdgeRoutingMode edgeRoutingMode, bool useSparseVisibilityGraph , bool useObstacleRectangles, double bendPenaltyAsAPercentageOfDistance) { var r = FillRouter(cornerFitRadius, padding, obstacleNodes, geometryEdges, edgeRoutingMode, useSparseVisibilityGraph , useObstacleRectangles, bendPenaltyAsAPercentageOfDistance); r.Run(); CreateSelfEdges(geometryEdges.Where(e => e.SourcePort.Location == e.TargetPort.Location), cornerFitRadius); } /// /// Create a RectilinearEdgeRouter from the passed obstacleNodes, with one port at the center of each obstacle, /// and route between the obstacles, with default bend penalty. /// /// The radius of the arc inscribed into path corners /// The minimum padding from an obstacle's curve to its enclosing polyline /// The nodes of the graph /// Edges defining the nodes to route between, and receiving the resultant paths /// Mode of the edges (Rectilinear or RectilinearToCenter). /// Use obstacle bounding box rectangles in visibility graph /// Use a more memory-efficient but possibly path-suboptimal visibility graph static public void CreatePortsAndRouteEdges(double cornerFitRadius, double padding , IEnumerable obstacleNodes, IEnumerable geometryEdges , EdgeRoutingMode edgeRoutingMode, bool useSparseVisibilityGraph , bool useObstacleRectangles) { CreatePortsAndRouteEdges(cornerFitRadius, padding, obstacleNodes, geometryEdges, edgeRoutingMode , useSparseVisibilityGraph, useObstacleRectangles, SsstRectilinearPath.DefaultBendPenaltyAsAPercentageOfDistance); } /// /// Create a RectilinearEdgeRouter from the passed obstacleNodes, with one port at the center of each obstacle, /// and route between the obstacles, with default bend penalty. /// /// The radius of the arc inscribed into path corners /// The minimum padding from an obstacle's curve to its enclosing polyline /// The nodes of the graph /// Edges defining the nodes to route between, and receiving the resultant paths /// Mode of the edges (Rectilinear or RectilinearToCenter). /// Use a more memory-efficient but possibly path-suboptimal visibility graph static public void CreatePortsAndRouteEdges(double cornerFitRadius, double padding , IEnumerable obstacleNodes, IEnumerable geometryEdges , EdgeRoutingMode edgeRoutingMode, bool useSparseVisibilityGraph) { CreatePortsAndRouteEdges(cornerFitRadius, padding, obstacleNodes, geometryEdges, edgeRoutingMode , useSparseVisibilityGraph, false, SsstRectilinearPath.DefaultBendPenaltyAsAPercentageOfDistance); } /// /// Create a RectilinearEdgeRouter populated with the passed obstacles. /// /// The populated RectilinearEdgeRouter static RectilinearEdgeRouter FillRouter(double cornerFitRadius, double padding, IEnumerable obstacleNodes, IEnumerable geomEdges , EdgeRoutingMode edgeRoutingMode, bool useSparseVisibilityGraph, bool useObstacleRectangles, double bendPenaltyAsAPercentageOfDistance) { Debug.Assert((EdgeRoutingMode.Rectilinear == edgeRoutingMode) || (EdgeRoutingMode.RectilinearToCenter == edgeRoutingMode) , "Non-rectilinear edgeRoutingMode"); var nodeShapeMap = new Dictionary(); foreach (Node node in obstacleNodes) { Shape shape = CreateShapeWithRelativeNodeAtCenter(node); nodeShapeMap.Add(node, shape); } var router = new RectilinearEdgeRouter(nodeShapeMap.Values, padding, cornerFitRadius, useSparseVisibilityGraph, useObstacleRectangles) { RouteToCenterOfObstacles = (edgeRoutingMode == EdgeRoutingMode.RectilinearToCenter), BendPenaltyAsAPercentageOfDistance = bendPenaltyAsAPercentageOfDistance }; foreach (var geomEdge in geomEdges) { var edgeGeom = geomEdge.EdgeGeometry; edgeGeom.SourcePort = nodeShapeMap[geomEdge.Source].Ports.First(); edgeGeom.TargetPort = nodeShapeMap[geomEdge.Target].Ports.First(); router.AddEdgeGeometryToRoute(edgeGeom); } return router; } static void CreateSelfEdges(IEnumerable selfEdges, double cornerFitRadius) { foreach (var edge in selfEdges) { CreateSimpleEdgeCurveWithGivenFitRadius(edge, cornerFitRadius); } } /// /// /// /// /// public static void CreateSimpleEdgeCurveWithGivenFitRadius(Edge edge, double cornerFitRadius) { ValidateArg.IsNotNull(edge, "edge"); var a = edge.Source.Center; var b = edge.Target.Center; if (edge.Source == edge.Target) { var dx = edge.Source.BoundaryCurve.BoundingBox.Width / 2; var dy = edge.Source.BoundingBox.Height / 4; edge.UnderlyingPolyline = CreateUnderlyingPolylineForSelfEdge(a, dx, dy); for (var site = edge.UnderlyingPolyline.HeadSite.Next; site.Next != null; site = site.Next) CalculateCoefficiensUnderSite(site, cornerFitRadius); edge.Curve = edge.UnderlyingPolyline.CreateCurve(); } else { edge.UnderlyingPolyline = SmoothedPolyline.FromPoints(new[] { a, b }); edge.Curve = edge.UnderlyingPolyline.CreateCurve(); } if (!Arrowheads.TrimSplineAndCalculateArrowheads(edge.EdgeGeometry, edge.Source.BoundaryCurve, edge.Target.BoundaryCurve, edge.Curve, true, false)) Arrowheads.CreateBigEnoughSpline(edge); } /// /// creates an edge curve based only on the source and target geometry /// /// public static void CreateSimpleEdgeCurve(Edge edge) { ValidateArg.IsNotNull(edge, "edge"); var a = edge.Source.Center; var b = edge.Target.Center; if (edge.Source == edge.Target) { var dx = edge.Source.BoundaryCurve.BoundingBox.Width / 2; 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(); } if (!Arrowheads.TrimSplineAndCalculateArrowheads(edge.EdgeGeometry, edge.Source.BoundaryCurve, edge.Target.BoundaryCurve, edge.Curve, true, false)) Arrowheads.CreateBigEnoughSpline(edge); } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "Microsoft.Msagl.Core.Geometry.Site")] 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; } /// /// Create a Shape with a single relative port at its center. /// /// The node from which the shape is derived /// public static Shape CreateShapeWithRelativeNodeAtCenter(Node node) { ValidateArg.IsNotNull(node, "node"); var shape = new RelativeShape(() => node.BoundaryCurve); shape.Ports.Insert(new RelativeFloatingPort(() => node.BoundaryCurve, () => node.Center)); return shape; } private static void CalculateCoefficiensUnderSite(Site site, double radius){ double l = radius/(site.Point - site.Previous.Point).Length; l = Math.Min(0.5, l); site.PreviousBezierSegmentFitCoefficient = l; l = radius / (site.Next.Point-site.Point).Length; l = Math.Min(0.5, l); site.NextBezierSegmentFitCoefficient = l; } } }