using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Msagl.Core.DataStructures; using Microsoft.Msagl.Core.Geometry; using Microsoft.Msagl.Core.Geometry.Curves; using Microsoft.Msagl.Core.Layout; using Microsoft.Msagl.Core.Routing; using Microsoft.Msagl.DebugHelpers; using Microsoft.Msagl.Routing.ConstrainedDelaunayTriangulation; namespace Microsoft.Msagl.Routing.Spline.Bundling { /// /// Wrapper for geometry graph with coinciding edges: /// 'real' nodes stand for edge ends (source,target) /// 'virtual' nodes stand for polyline control points /// /// 'real' edges are original graph edges /// 'virtual' edges are polyline segments /// internal class MetroGraphData { internal Set Stations; /// info on the edges passing through a couple Dictionary, StationEdgeInfo> edgeInfoDictionary; /// current ink double ink; /// Edges List metrolines; /// position -> (node) internal Dictionary PointToStations; readonly EdgeGeometry[] regularEdges; /// objects to check crossings and calculate distances internal Intersections looseIntersections; internal Intersections tightIntersections; /// objects to check crossings and calculate distances internal CdtIntersections cdtIntersections; Dictionary> EdgeLooseEnterable { get; set; } Dictionary> EdgeTightEnterable { get; set; } internal Func LoosePolylineOfPort; /// /// triangulation /// internal Cdt Cdt; internal MetroGraphData(EdgeGeometry[] regularEdges, RectangleNode looseTree, RectangleNode tightTree, BundlingSettings bundlingSettings, Cdt cdt, Dictionary> edgeLooseEnterable, Dictionary> edgeTightEnterable, Func loosePolylineOfPort) { //Debug.Assert(cdt != null); this.regularEdges = regularEdges; if (cdt != null) Cdt = cdt; else Cdt = BundleRouter.CreateConstrainedDelaunayTriangulation(looseTree); EdgeLooseEnterable = edgeLooseEnterable; EdgeTightEnterable = edgeTightEnterable; LoosePolylineOfPort = loosePolylineOfPort; looseIntersections = new Intersections(this, bundlingSettings, looseTree, station => station.EnterableLoosePolylines); tightIntersections = new Intersections(this, bundlingSettings, tightTree, station => station.EnterableTightPolylines); cdtIntersections = new CdtIntersections(this, bundlingSettings); Initialize(false); } internal double Ink { get { return ink; } } internal EdgeGeometry[] Edges { get { return regularEdges; } } internal IEnumerable VirtualNodes() { return Stations.Where(s => !s.IsRealNode); } internal List Metrolines { get { return metrolines; } } internal RectangleNode LooseTree { get { return looseIntersections.obstacleTree; } } internal RectangleNode TightTree { get { return tightIntersections.obstacleTree; } } internal IEnumerable> VirtualEdges() { return edgeInfoDictionary.Keys; } /// /// number of real edges passing the edge uv /// internal int RealEdgeCount(Station u, Station v) { var couple = u < v ? new Tuple(u, v) : new Tuple(v, u); StationEdgeInfo cw; if (edgeInfoDictionary.TryGetValue(couple, out cw)) return cw.Count; return 0; } /// /// real edges passing the node /// internal List MetroNodeInfosOfNode(Station node) { return node.MetroNodeInfos; } /// /// real edges passing the edge uv /// internal StationEdgeInfo GetIjInfo(Station u, Station v) { var couple = u < v ? new Tuple(u, v) : new Tuple(v, u); return edgeInfoDictionary[couple]; } /// /// Move node to the specified position /// internal void MoveNode(Station node, Point newPosition) { Point oldPosition = node.Position; PointToStations.Remove(oldPosition); PointToStations.Add(newPosition, node); node.Position = newPosition; //move curves foreach (MetroNodeInfo metroNodeInfo in MetroNodeInfosOfNode(node)) metroNodeInfo.PolyPoint.Point = newPosition; //update lengths foreach (MetroNodeInfo e in MetroNodeInfosOfNode(node)) { var metroLine = e.Metroline; var prev = e.PolyPoint.Prev.Point; var succ = e.PolyPoint.Next.Point; metroLine.Length += (succ - newPosition).Length + (prev - newPosition).Length - (succ - oldPosition).Length - (prev - oldPosition).Length; } //update ink foreach (var adj in node.Neighbors) { ink += (newPosition - adj.Position).Length - (oldPosition - adj.Position).Length; } //update neighbors order SortNeighbors(node); foreach (var adj in node.Neighbors) SortNeighbors(adj); } internal double GetWidth(Station u, Station v, double edgeSeparation) { var couple = u < v ? new Tuple(u, v) : new Tuple(v, u); StationEdgeInfo cw; if (edgeInfoDictionary.TryGetValue(couple, out cw)) return cw.Width + (cw.Count - 1) * edgeSeparation; return 0; } internal double GetWidth(IEnumerable metrolines, double edgeSeparation) { double width = 0; foreach (var metroline in metrolines) { width += metroline.Width; } int count = metrolines.Count(); width += count > 0 ? (count - 1) * edgeSeparation : 0; Debug.Assert(ApproximateComparer.GreaterOrEqual(width, 0)); return width; } /// /// Initialize data /// internal void Initialize(bool initTightTree) { //TimeMeasurer.DebugOutput("bundle graph data initializing..."); SimplifyRegularEdges(); InitializeNodeData(); InitializeEdgeData(); InitializeVirtualGraph(); InitializeEdgeNodeInfo(initTightTree); InitializeCdtInfo(); // Debug.Assert(looseIntersections.HubPositionsAreOK()); // Debug.Assert(tightIntersections.HubPositionsAreOK()); } /// /// remove self-cycles /// void SimplifyRegularEdges() { foreach (var edge in regularEdges) SimplifyRegularEdge(edge); } /// /// change the polyline by removing cycles /// void SimplifyRegularEdge(EdgeGeometry edge) { Polyline polyline = (Polyline)edge.Curve; var stack = new Stack(); var seen = new Set(); for (var p = polyline.EndPoint; p != null; p = p.Prev) { var v = p.Point; if (seen.Contains(p.Point)) { var pp = p.Next; do { var u = stack.Peek(); if (u != v) { seen.Remove(u); stack.Pop(); pp = pp.Next; } else break; } while (true); pp.Prev = p.Prev; pp.Prev.Next = pp; } else { stack.Push(v); seen.Insert(v); } } } void InitializeNodeData() { Stations = new Set(); //create indexes PointToStations = new Dictionary(); int i = 0; foreach (var edge in regularEdges) { Polyline poly = (Polyline)edge.Curve; i = ProcessPolylinePoints(i, poly); } } int ProcessPolylinePoints(int i, Polyline poly) { var pp = poly.StartPoint; i = RegisterStation(i, pp, true); for (pp = pp.Next; pp != poly.EndPoint; pp = pp.Next) i = RegisterStation(i, pp, false); i = RegisterStation(i, pp, true); return i; } int RegisterStation(int i, PolylinePoint pp, bool isRealNode) { if (!PointToStations.ContainsKey(pp.Point)) { // Filippo Polo: assigning the return value of the assignment operator (i.e. a = b = c) does not work well in Sharpkit. Station station = new Station(i++, isRealNode, pp.Point); PointToStations[pp.Point] = station; Stations.Insert(station); } else { #if DEBUG && TEST_MSAGL var s = PointToStations[pp.Point]; Debug.Assert(s.IsRealNode == isRealNode); #endif } return i; } void InitializeEdgeData() { metrolines = new List(); for (int i = 0; i < regularEdges.Length; i++) { EdgeGeometry geomEdge=regularEdges[i]; InitEdgeData(geomEdge, i); } } void InitEdgeData(EdgeGeometry geomEdge, int index) { var metroEdge = new Metroline((Polyline)geomEdge.Curve, geomEdge.LineWidth, EdgeSourceAndTargetFunc(geomEdge), index); metrolines.Add(metroEdge); PointToStations[metroEdge.Polyline.Start].BoundaryCurve = geomEdge.SourcePort.Curve; PointToStations[metroEdge.Polyline.End].BoundaryCurve = geomEdge.TargetPort.Curve; } internal Func> EdgeSourceAndTargetFunc(EdgeGeometry geomEdge) { return () => new Tuple(LoosePolylineOfPort(geomEdge.SourcePort), LoosePolylineOfPort(geomEdge.TargetPort)); } /// /// Initialize graph comprised of stations and their neighbors /// void InitializeVirtualGraph() { Dictionary> neighbors = new Dictionary>(); foreach (var metroline in metrolines) { Station u = PointToStations[metroline.Polyline.Start]; Station v; for (var p = metroline.Polyline.StartPoint; p.Next != null; p = p.Next, u = v) { v = PointToStations[p.Next.Point]; CollectionUtilities.AddToMap(neighbors, u, v); CollectionUtilities.AddToMap(neighbors, v, u); } } foreach (var s in Stations) { s.Neighbors = neighbors[s].ToArray(); } } StationEdgeInfo GetUnorderedIjInfo(Station i, Station j) { Debug.Assert(i != j); return (i < j ? GetOrderedIjInfo(i, j) : GetOrderedIjInfo(j, i)); } StationEdgeInfo GetOrderedIjInfo(Station i, Station j) { Debug.Assert(i < j); var couple = new Tuple(i, j); StationEdgeInfo cw; if (edgeInfoDictionary.TryGetValue(couple, out cw)) return cw; edgeInfoDictionary[couple] = cw = new StationEdgeInfo(i.Position, j.Position); return cw; } void InitializeEdgeNodeInfo(bool initTightTree) { edgeInfoDictionary = new Dictionary, StationEdgeInfo>(); InitMetroNodeInfos(initTightTree); SortNeighbors(); InitEdgeIjInfos(); ink = 0; foreach (var edge in VirtualEdges()) ink += (edge.Item1.Position - edge.Item2.Position).Length; } void InitMetroNodeInfos(bool initTightTree) { for (int i = 0; i < metrolines.Count; i++) { var metroline = metrolines[i]; InitMetroNodeInfos(metroline); InitNodeEnterableLoosePolylines(metroline, regularEdges[i]); if (initTightTree) InitNodeEnterableTightPolylines(metroline, regularEdges[i]); metroline.UpdateLengths(); } } void InitMetroNodeInfos(Metroline metroline) { for (var pp = metroline.Polyline.StartPoint; pp != null; pp = pp.Next) { Station station = PointToStations[pp.Point]; station.MetroNodeInfos.Add(new MetroNodeInfo(metroline, station, pp)); } } void InitNodeEnterableLoosePolylines(Metroline metroline, EdgeGeometry regularEdge) { //If we have groups, EdgeLooseEnterable are precomputed. var metrolineEnterable = EdgeLooseEnterable != null ? EdgeLooseEnterable[regularEdge] : new Set(); for (var p = metroline.Polyline.StartPoint.Next; p!=null && p.Next != null; p = p.Next) { var v = PointToStations[p.Point]; if (v.EnterableLoosePolylines != null) v.EnterableLoosePolylines *= metrolineEnterable; else v.EnterableLoosePolylines = new Set(metrolineEnterable); } AddLooseEnterableForMetrolineStartEndPoints(metroline); } void AddLooseEnterableForMetrolineStartEndPoints(Metroline metroline) { AddLooseEnterableForEnd(metroline.Polyline.Start); AddLooseEnterableForEnd(metroline.Polyline.End); } void AddTightEnterableForMetrolineStartEndPoints(Metroline metroline) { AddTightEnterableForEnd(metroline.Polyline.Start); AddTightEnterableForEnd(metroline.Polyline.End); } Dictionary> cachedEnterableLooseForEnd = new Dictionary>(); void AddLooseEnterableForEnd(Point point) { Station station = PointToStations[point]; if (!cachedEnterableLooseForEnd.ContainsKey(point)) { foreach (var poly in LooseTree.AllHitItems(point)) if (Curve.PointRelativeToCurveLocation(point, poly) == PointLocation.Inside) station.AddEnterableLoosePolyline(poly); cachedEnterableLooseForEnd.Add(point, station.EnterableLoosePolylines); } else { station.EnterableLoosePolylines = cachedEnterableLooseForEnd[point]; } //foreach (var poly in LooseTree.AllHitItems(point)) // if (Curve.PointRelativeToCurveLocation(point, poly) == PointLocation.Inside) // station.EnterableLoosePolylines.Insert(poly); } void AddTightEnterableForEnd(Point point) { Station station = PointToStations[point]; foreach (var poly in TightTree.AllHitItems(point)) if (Curve.PointRelativeToCurveLocation(point, poly) == PointLocation.Inside) station.AddEnterableTightPolyline(poly); } void InitNodeEnterableTightPolylines(Metroline metroline, EdgeGeometry regularEdge) { //If we have groups, EdgeTightEnterable are precomputed. var metrolineEnterable = EdgeTightEnterable != null ? EdgeTightEnterable[regularEdge] : new Set(); for (var p = metroline.Polyline.StartPoint.Next; p!=null && p.Next != null; p = p.Next) { var v = PointToStations[p.Point]; Set nodeEnterable = v.EnterableTightPolylines; if (nodeEnterable != null) v.EnterableTightPolylines = nodeEnterable * metrolineEnterable; else v.EnterableTightPolylines = new Set(metrolineEnterable); } AddTightEnterableForMetrolineStartEndPoints(metroline); } void SortNeighbors() { //counter-clockwise sorting foreach (var station in Stations) SortNeighbors(station); } void SortNeighbors(Station station) { //nothing to sort if (station.Neighbors.Length <= 2) return; Point pivot = station.Neighbors[0].Position; Point center = station.Position; Array.Sort(station.Neighbors, delegate(Station u, Station v) { return Point.GetOrientationOf3Vectors(pivot - center, u.Position - center, v.Position - center); }); } void InitEdgeIjInfos() { foreach (Metroline metroLine in metrolines) { var poly = metroLine.Polyline; var u = PointToStations[poly.Start]; Station v; for (var p = metroLine.Polyline.StartPoint; p.Next != null; p = p.Next, u = v) { v = PointToStations[p.Next.Point]; var info = GetUnorderedIjInfo(u, v); info.Count++; info.Width += metroLine.Width; info.Metrolines.Add(metroLine); } } } void InitializeCdtInfo() { RectangleNode cdtTree = Cdt.GetCdtTree(); foreach (var station in Stations) { station.CdtTriangle = cdtTree.FirstHitNode(station.Position, IntersectionCache.Test).UserData; Debug.Assert(station.CdtTriangle != null); } } internal bool PointIsAcceptableForEdge(Metroline metroline, Point point) { if (LoosePolylineOfPort == null) return true; var polys = metroline.sourceAndTargetLoosePolylines(); return Curve.PointRelativeToCurveLocation(point, polys.Item1) == PointLocation.Outside && Curve.PointRelativeToCurveLocation(point, polys.Item2) == PointLocation.Outside; } } }