using System; using System.Collections.Generic; using Microsoft.Msagl.Core; using Microsoft.Msagl.Core.DataStructures; using Microsoft.Msagl.Core.GraphAlgorithms; namespace Microsoft.Msagl.Layout.Layered { /// /// The implementation follows "Fast and Simple Horizontal Coordinate Assignment" of Ulrik Brandes and Boris K¨opf /// The paper has two serious bugs that this code resolves. /// internal partial class XCoordsWithAlignment { LayerArrays la; ProperLayeredGraph graph; int nOfOriginalVertices; int[] root; int[] align; //int[] sink; //double[] shift; int nOfVertices; Anchor[] anchors; double nodeSep; object[] lowMedians; object[] upperMedians; //each element or int or IntPair Set markedEdges = new Set(); int h;//number of layers //We pretend, when calculating the alignment, that we traverse layers from left to right and from bottom to top. //The actual directions are defined by variables "LR" and "BT". /// /// from left to right /// bool LR; /// /// from bottom to top /// bool BT; int EnumRightUp { get { return (LR ? 0 : 1) + 2 * (BT ? 0 : 1); } } /// /// Returns true if v is a virtual vertex /// /// /// bool IsVirtual(int v) { return v >= nOfOriginalVertices; } //four arrays for four different direction combinations double[][] xCoords = new double[4][]; double[] x; int Source(LayerEdge edge) { return BT ? edge.Source : edge.Target; } int Target(LayerEdge edge) { return BT ? edge.Target : edge.Source; } /// /// /// static internal void CalculateXCoordinates(LayerArrays layerArrays, ProperLayeredGraph layeredGraph, int nOfOriginalVs, Anchor[] anchors, double nodeSeparation) { XCoordsWithAlignment x = new XCoordsWithAlignment(layerArrays, layeredGraph, nOfOriginalVs, anchors, nodeSeparation); x.Calculate(); } void Calculate() { SortInAndOutEdges(); RightUpSetup(); CalcBiasedAlignment(); LeftUpSetup(); CalcBiasedAlignment(); RightDownSetup(); CalcBiasedAlignment(); LeftDownSetup(); CalcBiasedAlignment(); HorizontalBalancing(); } //We need to find a median of a vertex neighbors from a specific layer. That is, if we have a vertex v and edges (v,coeff), (v,side1), (v,cornerC) // going down, and X[coeff](this.graph.InEdges(i) as LayerEdge[], edgeComparerBySource); // Array.Sort(this.graph.OutEdges(i) as LayerEdge[], edgeComparerByTarget); //} } private void FillUpperMedins() { upperMedians = new object[graph.NodeCount]; for (int i = 0; i < graph.NodeCount; i++) FillUpperMediansForNode(i); } int CompareByX(int a, int b) { return this.la.X[a] - this.la.X[b]; } private void FillUpperMediansForNode(int i) { int count = this.graph.InEdgesCount(i); if (count > 0) { int[] predecessors = new int[count]; count = 0; foreach (LayerEdge e in this.graph.InEdges(i)) predecessors[count++] = e.Source; Array.Sort(predecessors, new System.Comparison(CompareByX)); int m = count / 2; if (m * 2 == count) { this.upperMedians[i] = new IntPair(predecessors[m - 1], predecessors[m]); } else this.upperMedians[i] = predecessors[m]; } else this.upperMedians[i] = -1; } private void FillLowMedians() { lowMedians = new object[graph.NodeCount]; for (int i = 0; i < graph.NodeCount; i++) FillLowMediansForNode(i); } private void FillLowMediansForNode(int i) { int count = this.graph.OutEdgesCount(i); if (count > 0) { int[] successors = new int[count]; count = 0; foreach (LayerEdge e in this.graph.OutEdges(i)) successors[count++] = e.Target; Array.Sort(successors, new System.Comparison(CompareByX)); int m = count / 2; if (m * 2 == count) { this.lowMedians[i] = new IntPair(successors[m - 1], successors[m]); } else this.lowMedians[i] = successors[m]; } else this.lowMedians[i] = -1; } void HorizontalBalancing() { int leastWidthAssignment = -1; double[] a = new double[4]; double[] b = new double[4]; double leastWidth = Double.MaxValue; for (int i = 0; i < 4; i++) { AssignmentBounds(i, out a[i], out b[i]); double w = b[i] - a[i]; if (w < leastWidth) { leastWidthAssignment = i; leastWidth = w; } } for (int i = 0; i < 4; i++) { double delta; if (IsLeftMostAssignment(i)) //need to align left ends according to the paper delta = a[leastWidthAssignment] - a[i]; else delta = b[leastWidthAssignment] - b[i]; x = xCoords[i]; if (delta != 0) for (int j = 0; j < nOfVertices; j++) x[j] += delta; } double[] arr = new double[4]; for (int v = 0; v < nOfVertices; v++) { arr[0] = xCoords[0][v]; arr[1] = xCoords[1][v]; arr[2] = xCoords[2][v]; arr[3] = xCoords[3][v]; Array.Sort(arr); anchors[v].X = (arr[1] + arr[2]) / 2; } // Layout.ShowDataBase(dataBase); } static bool IsLeftMostAssignment(int i) { return i == 0 || i == 2; } void AssignmentBounds(int i, out double a, out double b) { if (nOfVertices == 0) { a = 0; b = 0; } else { x = xCoords[i]; a = b = x[0]; for (int j = 1; j < nOfVertices; j++) { double r = x[j]; if (r < a) a = r; else if (r > b) b = r; } } } void CalcBiasedAlignment() { ConflictElimination(); Align(); #if DEBUGGLEE //for (int i = 0; i < nOfVertices; i++) // anchors[i].X = x[i]; //Layout.ShowDataBase(dataBase); #endif } void LeftUpSetup() { LR = false; BT = true; } void LeftDownSetup() { LR = false; BT = false; } void RightDownSetup() { LR = true; BT = false; } void RightUpSetup() { LR = true; BT = true; } /// /// The code is written as if we go left up, but in fact the settings define the directions. /// /// We need to create a subgraph for alignment: /// where no edge segments intersect, and every vertex has /// at most one incoming and at most one outcoming edge. /// This function marks edges to resolve conflicts with only one inner segment. /// An inner segment is a segment between two dummy nodes. /// We mark edges that later will not participate in the alignment. /// Inner segments are preferred to other ones. So, in a conflict with one inner and one /// non-inner edges we leave the inner edge to participate in the alignment. /// At the moment we mark as not participating both of the two intersecting inner segments /// void ConflictElimination() { RemoveMarksFromEdges(); MarkConflictingEdges(); } /* * Type 0 conflicts are those where inner edges do not participate. * They are resolved not by marking but just when we calculate the alignment in CreateBlocks. * A quote from "Fast and ..." with some typo corrections: * Type 0 conflicts are resolved greedily in a leftmost fashion, * i.e. in every layer we process the vertices from left to right and * for each vertex we consider its median upper neighbor (its left and right * median upper neighbor, in this order, if there are two). * The pair is aligned, if no conflicting alignment is to the left of this one. * The resulting bias is mediated by the fact that the symmetric bias is applied * in one of the other three assignments. */ IEnumerable UpperEdgeMedians(int target) { object medians = this.BT ? upperMedians[target] : lowMedians[target]; IntPair ip = medians as IntPair; if (ip != null) { if (this.LR) { yield return ip.First; yield return ip.Second; } else { yield return ip.Second; yield return ip.First; } } else { int i = (int)medians; if (i >= 0) yield return i; } } /// /// here we eliminate all constraints /// void MarkConflictingEdges() { int i = LowerOf(0, h - 1); int lowest = i; int upperBound = UpperOf(0, h - 1); int nextBelowUpperBound = NextLower(upperBound); //our top layer has index h-1, our bottom layer has index 0 //inner segments can appear only between layers with indices i+1 and i where i>0 and i /// parameterized next upper /// /// /// int NextUpper(int i) { return BT ? i + 1 : i - 1; } /// /// parameterized next lower /// /// /// int NextLower(int i) { return BT ? i - 1 : i + 1; } /// /// parameterize highest of two numbers /// /// /// /// int UpperOf(int i, int j) { return BT ? Math.Max(i, j) : Math.Min(i, j); } /// /// parameterized lowest of a pair /// /// /// /// int LowerOf(int i, int j) { return BT ? Math.Min(i, j) : Math.Max(i, j); } /// /// returns parameterized below /// /// /// /// bool IsBelow(int i, int j) { return BT ? i < j : j < i; } /// /// returns the "parameterized" left of the two positions /// /// /// /// int LeftMost(int pos0, int pos1) { return LR ? Math.Min(pos0, pos1) : Math.Max(pos0, pos1); } double LeftMost(double pos0, double pos1) { return LR ? Math.Min(pos0, pos1) : Math.Max(pos0, pos1); } /// /// returns the "parameterized" right of the two positions /// /// /// /// int RightMost(int pos0, int pos1) { return LR ? Math.Max(pos0, pos1) : Math.Min(pos0, pos1); } /// /// returns the "parameterized" right of the two positions /// /// /// /// double RightMost(double pos0, double pos1) { return LR ? Math.Max(pos0, pos1) : Math.Min(pos0, pos1); } /// /// Return true if i is to the left or equal to pos in a "parameterized" fasion /// /// /// /// bool IsNotRightFrom(int i, int pos) { return LR ? i <= pos : pos <= i; } /// /// Parameterized left relation /// /// /// /// bool IsLeftFrom(int i, int j) { return LR ? i < j : j < i; } /// /// parameterized next right /// /// /// int NextRight(int i) { return LR ? i + 1 : i - 1; } /// /// parameterized next left /// /// /// int NextLeft(int i) { return LR ? i - 1 : i + 1; } ///// ///// Eliminates conflicts with at least one inner edge inside of one layer ///// ///// void ConflictsWithAtLeastOneInnerEdgeForALayer(int layerIndex) { if (layerIndex >= 0 && layerIndex < this.la.Layers.Length) { int[] lowerLayer = this.la.Layers[layerIndex]; LayerEdge innerEdge = null; //start looking for the first inner edge from the left of lowerLayer int targetPos = LeftMost(0, lowerLayer.Length - 1); int lastTargetPos = RightMost(0, lowerLayer.Length - 1); ; for (; IsNotRightFrom(targetPos, lastTargetPos) && innerEdge == null; targetPos = NextRight(targetPos)) innerEdge = InnerEdgeByTarget(lowerLayer[targetPos]); //now targetPos points to the right of the innerEdge target at lowerLayer if (innerEdge != null) { int positionOfInnerEdgeSource = Pos(Source(innerEdge)); //We are still not in the main loop. //We mark conflicting edges with targets to the left of targetPos, //That of course means //that the sources of conflicting edges lie to the right of Source(innerEdge) for (int j = LeftMost(0, lowerLayer.Length - 1); IsLeftFrom(j, targetPos); j = NextRight(j)) { foreach (LayerEdge ie in InEdges(lowerLayer[j])) if (IsLeftFrom(positionOfInnerEdgeSource, Pos(Source(ie)))) MarkEdge(ie); } int innerSourcePos = Pos(Source(innerEdge)); //starting the main loop while (IsNotRightFrom(targetPos, lastTargetPos)) { //Now we look for the next inner edge in the alignment to the right of the current innerEdge, //and we mark the conflicts later. Marking the conflicts later makes sense. //We would have to go through positions between innerEdge and newInnerEdge targets //again anyway to resolve conflicts with not inner edges and newInnerEdge LayerEdge newInnerEdge = AlignmentToTheRightOfInner(lowerLayer, targetPos, positionOfInnerEdgeSource); targetPos = NextRight(targetPos); if (newInnerEdge != null) { int newInnerSourcePos = Pos(Source(newInnerEdge)); MarkEdgesBetweenInnerAndNewInnerEdges(lowerLayer, innerEdge, newInnerEdge, innerSourcePos, newInnerSourcePos); innerEdge = newInnerEdge; innerSourcePos = newInnerSourcePos; } } positionOfInnerEdgeSource = Pos(Source(innerEdge)); //look for conflicting edges with targets to the right from the target of innerEdge for (int k = NextRight(Pos(Target(innerEdge))); IsNotRightFrom(k, lastTargetPos); k = NextRight(k)) { foreach (LayerEdge ie in InEdges(lowerLayer[k])) { if (IsLeftFrom(Pos(Source(ie)), Pos(Source(innerEdge)))) MarkEdge(ie); } } } } } LayerEdge InEdgeOfVirtualNode(int v) { return (this.BT ? graph.InEdgeOfVirtualNode(v) : graph.OutEdgeOfVirtualNode(v)); } IEnumerable InEdges(int v) { return this.BT ? graph.InEdges(v) : graph.OutEdges(v); } ///// ///// This function marks conflicting edges with targets positioned between innerEdge and newInnerEdge targets. ///// ///// ///// ///// ///// ///// void MarkEdgesBetweenInnerAndNewInnerEdges(int[] lowerLayer, LayerEdge innerEdge, LayerEdge newInnerEdge, int innerEdgeSourcePos, int newInnerEdgeSourcePos) { int u = NextRight(Pos(Target(innerEdge))); for (; IsLeftFrom(u, Pos(Target(newInnerEdge))); u = NextRight(u)) { foreach (LayerEdge ie in InEdges(lowerLayer[u])) { int ieSourcePos = Pos(Source(ie)); if (IsLeftFrom(ieSourcePos, innerEdgeSourcePos))//the equality is not possible MarkEdge(ie); else if (IsLeftFrom(newInnerEdgeSourcePos, ieSourcePos)) MarkEdge(ie); } } } ///// ///// Returns the inner non-conflicting edge incoming into i-th position ///// of the layer or null if there is no such edge ///// ///// ///// ///// ///// private LayerEdge AlignmentToTheRightOfInner(int[] lowLayer, int i, int posInnerSource ) { int numOfInEdges = NumberOfInEdges(lowLayer[i]); if(numOfInEdges==1){ LayerEdge ie = null; foreach(LayerEdge e in InEdges(lowLayer[i])) ie=e; if (IsInnerEdge(ie) && IsLeftFrom(posInnerSource, Pos(ie.Source))) return ie; return null; } return null; } private int NumberOfInEdges(int v) { return this.BT ? graph.InEdgesCount(v) : graph.OutEdgesCount(v); } int Pos(int v) { return la.X[v]; } LayerEdge InnerEdgeByTarget(int v) { if (IsVirtual(v)) { LayerEdge ie = InEdgeOfVirtualNode(v);//there is exactly one edge entering in to the dummy node if (IsVirtual(Source(ie))) return ie; } return null; } bool IsInnerEdge(LayerEdge e) { return IsVirtual(e.Source) && IsVirtual(e.Target); } private void RemoveMarksFromEdges() { markedEdges.Clear(); } ///// ///// private constructor ///// ///// ///// ///// ///// XCoordsWithAlignment(LayerArrays layerArrays, ProperLayeredGraph layeredGraph, int nOfOriginalVs, Anchor[] anchorsP, double ns) { this.la = layerArrays; this.graph = layeredGraph; this.nOfOriginalVertices = nOfOriginalVs; this.nOfVertices = graph.NodeCount; this.h = la.Layers.Length; this.root = new int[nOfVertices]; this.align = new int[nOfVertices]; // this.sink = new int[nOfVertices]; // this.shift = new double[nOfVertices]; this.anchors = anchorsP; this.nodeSep = ns; } /// ///Calculate the alignment based on the marked edges and greedily resolving the remaining conflicts on the fly, without marking /// void Align() { CreateBlocks(); AssignCoordinatesByLongestPath(); } void AssignCoordinatesByLongestPath() { this.x = this.xCoords[this.EnumRightUp] = new double[this.nOfVertices]; /* * We create a graph of blocks or rather of block roots. There is an edge * from u-block to v-block if some of elements of u-block is to the left of v * on the same layer. Then we topologically sort the graph and assign coordinates * taking into account separation between the blocks. */ //create the graph first List edges = new List(); for (int v = 0; v < nOfVertices; v++) { if (v == root[v])//v is a root { int w = v;//w will be running over the block do { int rightNeighbor; if (TryToGetRightNeighbor(w, out rightNeighbor)) edges.Add(new IntEdge(v, root[rightNeighbor])); w = align[w]; } while (w != v); } } BasicGraph blockGraph = new BasicGraph(edges, nOfVertices); //sort the graph in the topological order int[] topoSort = IntEdge.GetOrder(blockGraph); //start placing the blocks according to the order foreach (int v in topoSort) { if (v == root[v])//not every element of topoSort is a root! { double vx = 0; bool vIsLeftMost = true; int w = v;//w is running over the block do { int wLeftNeighbor; if (TryToGetLeftNeighbor(w, out wLeftNeighbor)) { if (vIsLeftMost) { vx = x[root[wLeftNeighbor]] + DeltaBetweenVertices(wLeftNeighbor, w); vIsLeftMost = false; } else vx = RightMost(vx, x[root[wLeftNeighbor]] + DeltaBetweenVertices(wLeftNeighbor, w)); } w = align[w]; } while (w != v); x[v] = vx; } } //push the roots of the graph maximally to the right foreach (int v in topoSort) { if (v == root[v]) if (blockGraph.InEdges(v).Count == 0) { int w = v;//w runs over the block double xLeftMost = RightMost(-infinity, infinity); double xl = xLeftMost; do { int wRightNeigbor; if (TryToGetRightNeighbor(w, out wRightNeigbor)) xLeftMost = LeftMost(xLeftMost, x[root[wRightNeigbor]] - DeltaBetweenVertices(w, wRightNeigbor)); w = align[w]; } while (w != v); //leave the value zero if there are no right neighbours if (xl != xLeftMost) x[v] = xLeftMost; } } for (int v = 0; v < this.nOfVertices; v++) { if (v != root[v]) x[v] = x[root[v]]; } } /// /// returns true is u has a right neighbor on its layer /// /// /// /// bool TryToGetRightNeighbor(int u, out int neighbor) { int neighborPos = NextRight(Pos(u)); int[] layer = la.Layers[la.Y[u]]; if (neighborPos >= 0 && neighborPos < layer.Length) { neighbor = layer[neighborPos]; return true; } else { neighbor = 0; return false; } } /// /// returns true is u has a right neighbor on its layer /// /// /// /// bool TryToGetLeftNeighbor(int u, out int neighbor) { int neighborPos = NextLeft(Pos(u)); int[] layer = la.Layers[la.Y[u]]; if (neighborPos >= 0 && neighborPos < layer.Length) { neighbor = layer[neighborPos]; return true; } else { neighbor = 0; return false; } } /// /// Organizes the vertices into blocks. A block is a maximal path in the alignment subgraph. /// The alignment is defined by array align. Every vertex is connected to the top vertex of /// the block by using root array. The alignment is cyclic. If we start from a root vertex v and /// apply align then we return to v at some point. /// void CreateBlocks() { for (int v = 0; v < nOfVertices; v++) root[v] = align[v] = v; int lowBound = this.LowerOf(0, h - 1); //i points to the last layer before the highest one for (int i = NextLower(this.UpperOf(0, h - 1)); !IsBelow(i, lowBound); i = NextLower(i)) { int[] layer = la.Layers[i]; int r = LeftMost(-1, la.Layers[NextUpper(i)].Length); //We align vertices of the layer above the i-th one only if their positions are //to the right of r. This moves us forward on the layer above the current and resolves the conflicts. int rightBound = RightMost(0, layer.Length - 1); for (int k = LeftMost(0, layer.Length - 1); IsNotRightFrom(k, rightBound); k = NextRight(k)) { int vk = layer[k]; foreach (int upperNeighborOfVk in UpperEdgeMedians(vk)) { if (!IsMarked(vk, upperNeighborOfVk)) { if (IsLeftFrom(r, Pos(upperNeighborOfVk))) { align[upperNeighborOfVk] = vk; align[vk] = root[vk] = root[upperNeighborOfVk]; r = Pos(upperNeighborOfVk); break;// done with the alignement for vk } } } } } #region debug print #if DEBUGGLEE //PrintLayers(); //for (int i = 0; i < nOfVertices; i++) // if (root[i] == i) // { // Console.Write(" Block "); // int w = i; // do // { // Console.Write(w.ToString() + " "); // w = align[w]; // } // while (w != i); // Console.WriteLine(); // } //Console.WriteLine("edges"); //foreach (IntEdge e in graph.Edges) // Console.WriteLine(e.ToString() + " "); //Console.WriteLine(); #endif #endregion } private bool IsMarked(int source, int target) { if (BT) return markedEdges.Contains(new IntPair(target, source)); else return markedEdges.Contains(new IntPair(source, target)); } private void MarkEdge(LayerEdge ie) { this.markedEdges.Insert(new IntPair(ie.Source, ie.Target)); } #region debug routines #if DEBUGGLEE //List EdgesIntoLayer(int i) { // List es = new List(); // foreach (int v in this.la.Layers[i]) { // es.AddRange(InEdges(v)); // } // return es; //} //List AlignmentIntoLayer(int i) { // List es = new List(); // foreach (int v in this.la.Layers[i]) // foreach (IIntEdge ie in InEdges(v)) // if (ie.Marked == false) // es.Add(ie); // return es; //} //List AlignmentInnerIntoLayer(int i) { // List es = new List(); // foreach (int v in this.la.Layers[i]) // foreach (IIntEdge ie in InEdges(v)) // if (ie.Marked == false && IsInnerEdge(ie)) // es.Add(ie); // return es; //} //private void PrintLayers() { // foreach (int[] lay in la.Layers) { // Console.WriteLine("layer"); // foreach (int v in lay) // Console.Write(v.ToString() + " "); // Console.WriteLine(); // } //} #endif #endregion /// /// Assigning xcoords starting from roots /// const double infinity = Double.MaxValue; /// /// Calculates the minimum separation between two neighboring vertices: if u is to the left of v on the same layer return positive /// number, otherwise negative. /// /// /// /// double DeltaBetweenVertices(int u, int v) { int sign = 1; if (Pos(u) > Pos(v)) { //swap u and v int t = u; u = v; v = t; sign = -1; } double anchorSepar = anchors[u].RightAnchor + anchors[v].LeftAnchor; return (anchorSepar + nodeSep) * sign; } } }