private void PositionRank(Graph graph, Rank rank, float xOffset) { // spreads nodes in rank across y-axis at even intervals var nodes = rank.Column; float spacePerRow = graph.ScaledHeight / (float)nodes.Count; float yOffset = spacePerRow / 2.0f; foreach (var node in nodes) { node.ScaledLocation.X = xOffset; node.ScaledLocation.Y = graph.ScaledOffset + yOffset; yOffset += spacePerRow; } }
private void SetLabelArea(Graph graph, RectangleF area, Rank rank, float right) { var rankNodes = rank.Column.Where(n => n.ID != 0).ToArray(); for (int x = 0; x < rankNodes.Length; x++) { var node = rankNodes[x]; // check if enough room in box for label node.RoomForLabel = false; node.LabelClipped = false; SizeF textSize = Renderer.MeasureString(node.Name, TextFont); // first see if label fits above node, we prefer this because it looks better when zoomed in float left = node.AreaF.Left; float top = area.Y + area.Height * graph.ScaledOffset; bool topFit = true; if (x > 0) top = rankNodes[x - 1].AreaF.Bottom; float bottom = node.AreaF.Top; node.LabelRect = new RectangleF(left, top, right - left, bottom - top); // if label doesnt fit above, put it to the right of the node if (textSize.Height > node.LabelRect.Height) { //area from middle of node to edges of midpoint between adjacent nodes, and length to next rank - max node's width /2 topFit = false; left = node.AreaF.Right; top = graph.ScaledOffset; var thisY = rankNodes[x].ScaledLocation.Y; if (x > 0) { float aboveY = rankNodes[x - 1].ScaledLocation.Y; float distance = thisY - aboveY; top = aboveY + (distance / 2f); } bottom = graph.ScaledOffset + graph.ScaledHeight; if (x < rankNodes.Length - 1) { float belowY = rankNodes[x + 1].ScaledLocation.Y; float distance = belowY - thisY; bottom = thisY + (distance / 2f); } float distanceFromCenter = Math.Min(node.ScaledLocation.Y - top, bottom - node.ScaledLocation.Y); top = area.Y + (node.ScaledLocation.Y - distanceFromCenter) * area.Height; bottom = area.Y + (node.ScaledLocation.Y + distanceFromCenter) * area.Height; node.LabelRect = new RectangleF(left, top, right - left, bottom - top); } // if can fit height and 30% of the text label if (textSize.Height < node.LabelRect.Height && textSize.Width * 0.3f < node.LabelRect.Width) { node.RoomForLabel = true; if (node.LabelRect.Width < textSize.Width) node.LabelClipped = true; else node.LabelRect.Width = textSize.Width; // trim label size if (topFit) node.LabelRect.Y = node.LabelRect.Bottom - textSize.Height; else node.LabelRect.Y = (node.LabelRect.Y + node.LabelRect.Height / 2f) - (textSize.Height / 2f); node.LabelRect.Height = textSize.Height; } } }
private void BuildGraphs() { foreach (var node in PositionMap.Values) node.Rank = null; do { // group nodes into connected graphs var graph = new Dictionary<int, NodeModel>(); // add first unranked node to a graph var unrankedNode = PositionMap.Values.First(n => n.Rank == null); LayoutGraph(graph, unrankedNode, 0, new List<int>()); // while group contains unranked nodes while (graph.Values.Any(n => n.Rank == null && n.EdgesOut != null)) { // head node to start traversal unrankedNode = graph.Values.First(n => n.Rank == null && n.EdgesOut != null); // only way node could be in group is if child added it, so there is a minrank // min rank is 1 back from the lowest ranked child of the node int? minRank = unrankedNode.EdgesOut.Min(dest => { if (PositionMap.ContainsKey(dest)) { var destNode = PositionMap[dest]; if (destNode.Rank.HasValue) return destNode.Rank.Value; } return int.MaxValue; }); LayoutGraph(graph, unrankedNode, minRank.Value - 1, new List<int>()); } // remove graphs with 1 element if (graph.Count == 1) { bool remove = false; var onlyNode = graph.First().Value; if (GraphMode == CallGraphMode.Dependencies || GraphMode == CallGraphMode.Method) remove = true; if(GraphMode == CallGraphMode.Class || GraphMode == CallGraphMode.Layers) { // dont remove method/field if alone as a graph because it may be still connect to other classes if (onlyNode.ObjType == XObjType.Method || onlyNode.ObjType == XObjType.Field) { // in class mode edges between nodes set dynamically, in layers mode edges are based on calls if ((GraphMode == CallGraphMode.Layers && onlyNode.XNode.CalledIn == null && onlyNode.XNode.CallsOut == null) || (GraphMode == CallGraphMode.Class && onlyNode.EdgesIn == null && onlyNode.EdgesOut == null)) remove = true; } // remove empty lonesome classes else if (onlyNode.ObjType == XObjType.Class && !Model.ShowMethods && !Model.ShowFields) remove = true; // if node is by lonesome and has no sub-graphs inside of it else if (Subsets.ContainsKey(onlyNode.ID) && Subsets[onlyNode.ID].Graphs.Count == 0) remove = true; } if(remove) { PositionMap.Remove(onlyNode.ID); CenterMap.Remove(onlyNode.ID); continue; } } // normalize ranks so sequential without any missing between int nextSequentialRank = -1; int currentRank = int.MinValue; foreach (var n in graph.Values.OrderBy(v => v.Rank)) { if (n.Rank != currentRank) { currentRank = n.Rank.Value; nextSequentialRank++; } n.Rank = nextSequentialRank; } // put all nodes into a rank based multi-map Rank[] ranks = new Rank[nextSequentialRank + 1]; for (int i = 0; i < ranks.Length; i++) ranks[i] = new Rank(); long graphWeight = 0; foreach (var source in graph.Values) { graphWeight += source.Value; ranks[source.Rank.Value].Column.Add(source); if (source.EdgesOut == null) continue; foreach (var destId in source.EdgesOut) { if (!graph.ContainsKey(destId)) continue; var destination = graph[destId]; // ranks are equal if nodes are outside zoom if (source.ID == destination.ID || destination.Rank == source.Rank) continue; if (source.Intermediates != null) source.Intermediates.Remove(destId); // if destination is not 1 forward/1 back then create intermediate nodes if (source.Rank != destination.Rank + 1 && source.Rank != destination.Rank - 1) { if (source.Intermediates == null) source.Intermediates = new Dictionary<int, List<NodeModel>>(); source.Intermediates[destId] = new List<NodeModel>(); bool increase = destination.Rank > source.Rank; int nextRank = increase ? source.Rank.Value + 1 : source.Rank.Value - 1; var lastNode = source; while (nextRank != destination.Rank) { // create new node var intermediate = new NodeModel(Model); intermediate.Rank = nextRank; intermediate.Value = 10; // todo make smarter - intermediate.Adjacents = new List<NodeModel>(); // add forward node to prev if (lastNode != source) lastNode.Adjacents.Add(intermediate); // add back node to curr intermediate.Adjacents.Add(lastNode); // add to temp path, rank map source.Intermediates[destId].Add(intermediate); ranks[nextRank].Column.Add(intermediate); //PositionMap not needed because we dont need any mouse over events? just follow along and draw from list, not id lastNode = intermediate; nextRank = increase ? nextRank + 1 : nextRank - 1; } try { lastNode.Adjacents.Add(destination); source.Intermediates[destId].Add(destination); } catch { System.IO.File.WriteAllText("debugX.txt", string.Format("{0}\r\n{1}\r\n", source.Rank, destination.Rank)); throw new Exception("wtf"); } } } } Graphs.Add(new Graph() { Ranks = ranks, Weight = graphWeight }); } while (PositionMap.Values.Any(n => n.Rank == null)); }