public bool Arrange(IGraph graph, GridLayoutInfo info, LayoutProgress progress) { _graph = new Graph(graph); _info = info; if (_info.RndSeed != 0) { // Use the randomization seed supplied by the user _random = new Random(_info.RndSeed); } Path path = null; // Find the longest path between the start and the end node if (_info.StartNode != null && _info.EndNode != null) { path = PathFinder.FindLongestPath(graph, _info.StartNode, _info.EndNode); } else { path = PathFinder.FindLongestPath(graph); } if (path == null) return false; // Build path consisting of the corresponding GraphNode-s _backbone = new ArrayList(); foreach (INode inode in path.Nodes) { foreach (GraphNode node in _graph.Nodes) { if (node.Node == inode) { _backbone.Add(node); break; } } } GraphNode[,] bestGrid = InitGrid(); float bestEstimation = float.MaxValue; int maxShift = Math.Max(4, _gridWidth / 10); // Update progress int current = 0; int total = CountIterations(maxShift); float ffactor = total / 100; int factor = 1; if (ffactor > 1) { factor = (int)Math.Floor((double)ffactor); total = total / factor + 1; } if (progress != null) progress(current, total); while (maxShift > 0) { for (int iter = 0; iter < (maxShift > 2 ? _info.Iterations : (_info.Iterations / 5 * maxShift * _graph.Nodes.Count)); ++iter) { // Update progress if (progress != null) { if (current % factor == 0) progress(current / factor, total); current++; } GraphNode[,] grid = ScrambleGrid(bestGrid, maxShift, iter); float estimation = EvaluateGrid(grid, maxShift); if (estimation < bestEstimation) { bestEstimation = estimation; bestGrid = grid; } else { ApplyGrid(bestGrid); } } maxShift--; } PlaceObjects(bestGrid); // Update progress if (progress != null) progress(total, total); return true; }
/// <summary> /// Performs the arrangement of the diagram. /// </summary> public bool Arrange(IGraph graph, LayeredLayoutInfo info, LayoutProgress progress) { _graph = new Graph(graph); _info = info; _progress = progress; _current = 0; _total = 1 /*path finding*/ + 1 /*layers splitting*/ + 1 /*dummify*/ + 6 /*minimize crossings*/ + 4 /*swap pairs*/ + 1 /*layout*/ + 1 /*apply*/ + 1 /*compact*/ + 1 /*dedumify*/; // Update progress if (_progress != null) _progress(_current, _total); // Initialize nodes foreach (GraphNode node in _graph.Nodes) { node.SetData(_Layer, -1); node.SetData(_UBaryCenter, 0.0); node.SetData(_DBaryCenter, 0.0); node.SetData(_ULinkCount, 0); node.SetData(_DLinkCount, 0); node.SetData(_UPriority, 0); node.SetData(_DPriority, 0); node.SetData(_GridPosition, 0); node.SetData(_Dummy, false); } // Initialize links foreach (GraphLink link in _graph.Links) { link.SetData(_DummificationLevel, 0); } // Determine the layer depth Path longestPath = PathFinder.FindLongestPath(graph, info.TimeLimit); if (longestPath == null) return true; // No objects in the graph // Update progress if (_progress != null) _progress(_current++, _total); _layers = new ArrayList(); for (int i = 0; i < longestPath.Nodes.Count; i++) _layers.Add(new ArrayList()); // Distribute nodes to their appropriate layers, // starting with the nodes from the longest path for (int i = 0; i < longestPath.Nodes.Count; i++) { // Find the corresponding node in the internal graph GraphNode node = null; foreach (GraphNode n in _graph.Nodes) { if (n.Node == longestPath.Nodes[i]) { node = n; break; } } node.SetData(_Layer, i); (_layers[i] as ArrayList).Add(node); } foreach (INode node in longestPath.Nodes) { // Find the corresponding node in the internal graph GraphNode gnode = null; foreach (GraphNode n in _graph.Nodes) { if (n.Node == node) { gnode = n; break; } } AddChildren(gnode); } // For all layers whose nodes are > 1 / 20 of the total -> // drop nodes. This will ensure that there are no // too wide layers, thus improving visibility of the graph. int total = 0; bool found; foreach (ArrayList layer in _layers) total += layer.Count; int threshold = total / 10; if (total > 50 && _info.SplitLayers) { found = true; while (found) { found = false; foreach (ArrayList layer in _layers) { if (layer.Count > threshold) { // Find candidates for dropping. // Good candidate is a node which // when moved downwards will be closer // to its children SortedList candidates = new SortedList(); foreach (GraphNode node in layer) { // Calculate total child depth int factor = 0; int nodeLayer = (int)node.GetData(_Layer); foreach (GraphLink link in node.OutLinks) { int childLayer = (int)link.Destination.GetData(_Layer); if (childLayer <= nodeLayer) factor += 1; else factor -= 1; } foreach (GraphLink link in node.InLinks) { int childLayer = (int)link.Origin.GetData(_Layer); if (childLayer <= nodeLayer) factor += 1; else factor -= 1; } if (factor > 0) candidates[factor] = node; } if (candidates.Keys.Count == 0) continue; found = true; ArrayList nextLayer = null; int ilayer = _layers.IndexOf(layer); if (ilayer == _layers.Count - 1) { nextLayer = new ArrayList(); _layers.Add(nextLayer); } else { nextLayer = _layers[ilayer + 1] as ArrayList; } while (layer.Count > threshold) { if (candidates.Keys.Count == 0) break; GraphNode node = candidates.GetByIndex(candidates.Count - 1) as GraphNode; candidates.RemoveAt(candidates.Count - 1); nextLayer.Add(node); layer.Remove(node); node.SetData(_Layer, (int)node.GetData(_Layer) + 1); } } if (found) break; } } } // Check if there are directly related nodes on the same layer found = true; while (found) { found = false; int ilayer = 0; ArrayList nodesToDrop = new ArrayList(); foreach (ArrayList layer in _layers) { nodesToDrop.Clear(); foreach (GraphNode node in layer) { foreach (GraphLink link in node.OutLinks) { if ((int)link.Destination.GetData(_Layer) == ilayer) { // The node's child is on the same layer. // Mark it for dropping if (!nodesToDrop.Contains(link.Destination)) nodesToDrop.Add(link.Destination); found = true; } } } // Drop nodes downwards if (found) { ArrayList curLayer = _layers[ilayer] as ArrayList; ArrayList nextLayer = null; if (ilayer == _layers.Count - 1) { nextLayer = new ArrayList(); _layers.Add(nextLayer); } else { nextLayer = _layers[ilayer + 1] as ArrayList; } foreach (GraphNode node in nodesToDrop) { if (curLayer.Count > 1) { nextLayer.Add(node); curLayer.Remove(node); node.SetData(_Layer, (int)node.GetData(_Layer) + 1); } } break; } ilayer++; } } // Calculate initial grid positions foreach (ArrayList layer in _layers) for (int i = 0; i < layer.Count; i++) (layer[i] as GraphNode).SetData(_GridPosition, (double)i); // Update progress if (_progress != null) _progress(_current++, _total); // Add dummy nodes for all links which cross one or more // layers. Add one dummy node for each crossed layer Dummify(); // Reduce crossings MinimizeCrossings(); // Further reduce crossings through pair swap SwapPairs(); // Arrange nodes Layout(); // Update progress if (_progress != null) _progress(_current++, _total); // Compact levels if (_info.ArrowsCompactFactor != 1) Compact(); // Update progress if (_progress != null) _progress(_current++, _total); Apply(); // Update progress if (_progress != null) _progress(_current++, _total); // Reverse dummification Dedummify(); // Update progress if (_progress != null) _progress(_total, _total); // Update nodes positions foreach (GraphNode node in _graph.Nodes) if (node.Node != null) node.Node.Bounds = node.Bounds; // Update arrow points foreach (GraphLink link in _graph.Links) { if (link.Link != null) { object points = link.GetData(_LinkPoints); if (points != null) link.Link.SetPoints(points as ArrayList); } } return true; }
public bool Arrange(IGraph graph, SpringLayoutInfo layoutInfo, LayoutProgress progress) { _info = layoutInfo; if (_info.RndSeed != 0) { // Use the randomization seed supplied by the user _r = new Random(_info.RndSeed); } // Build the internal graph _graph = new Graph(graph); RectangleF rcDoc = CalcContentRect(_graph); float minNodeX = rcDoc.Left - (50f * _info.NodeDistance); float minNodeY = rcDoc.Top - (50f * _info.NodeDistance); float maxNodeX = rcDoc.Right + (50f * _info.NodeDistance); float maxNodeY = rcDoc.Bottom + (50f * _info.NodeDistance); // Initialize foreach (GraphNode node in _graph.Nodes) node.SetData(_SpringVertex, new SpringVertexData()); foreach (GraphLink link in _graph.Links) link.SetData(_SpringEdge, new SpringEdgeData()); // Calculate vertex radiuses foreach (GraphNode node in _graph.Nodes) { RectangleF rc = node.Bounds; node.SetData(_VertexRadius, (double)(rc.Width + rc.Height) / 4); } // Update progress int total = layoutInfo.IterationCount; float ffactor = total / 100; int factor = 1; if (ffactor > 1) { factor = (int)Math.Floor((double)ffactor); total = total / factor + 1; } if (progress != null) progress(0, total); // An iterative layout algorithm for(int step = 1; step <= layoutInfo.IterationCount; step++) { // Update progress if (progress != null) { if (step % factor == 0) progress(step / factor, total); } foreach(GraphNode node in _graph.Nodes) { SpringVertexData data = (SpringVertexData) node.GetData(_SpringVertex); data.DX /= 4; data.DY /= 4; data.EdgeDX = data.EdgeDY = 0; data.RepulsionDX = data.RepulsionDY = 0; } // here the "springs" confine nodes to the desired node distance foreach (GraphLink link in _graph.Links) { // get incident nodes GraphNode v1 = link.Origin; GraphNode v2 = link.Destination; // compute current distance between nodes PointF v1Pos = v1.Center; PointF v2Pos = v2.Center; double vx = v1Pos.X - v2Pos.X; double vy = v1Pos.Y - v2Pos.Y; double dist = Math.Sqrt(vx * vx + vy * vy); dist = (dist == 0) ? 0.0001 : dist; // get desired distance accomodated for node extents double desiredDist = _info.NodeDistance; desiredDist += ((double)v1.GetData(_VertexRadius) + (double)v2.GetData(_VertexRadius)) / 2; // now handling cluster center nodes ? bool masterNodes = _info.EnableClusters && (v1.LinkCount > 4 && v2.LinkCount > 4); desiredDist *= masterNodes ? 4 : link.Link.Weight; // force factor: optimal length minus actual length, // is made smaller as the current actual length gets larger. double f = _ForceConstant * (desiredDist - dist) / dist; // nodes with few links are moved easier than ones with more double f1 = masterNodes ? f : (f * Math.Pow(_stretch / 100.0, v1.LinkCount - 1)); double f2 = masterNodes ? f : (f * Math.Pow(_stretch / 100.0, v2.LinkCount - 1)); // move first node towards the second one SpringVertexData v1D = (SpringVertexData)v1.GetData(_SpringVertex); v1D.EdgeDX += f1 * vx; v1D.EdgeDY += f1 * vy; // move second node towards the first one SpringVertexData v2D = (SpringVertexData)v2.GetData(_SpringVertex); v2D.EdgeDX += -f2 * vx; v2D.EdgeDY += -f2 * vy; } // here nodes tend to run away one from another foreach (GraphNode node in _graph.Nodes) { SpringVertexData svd = (SpringVertexData)node.GetData(_SpringVertex); double dx = 0; double dy = 0; foreach (GraphNode node2 in _graph.Nodes) { if (node2 == node) continue; // now handling cluster center nodes ? bool masterNodes = _info.EnableClusters && (node.LinkCount > 4 && node2.LinkCount > 4); // if distance is longer than this, ignore repulsion double intrZone = _info.NodeDistance * (masterNodes ? 4.5 : _info.RepulsionFactor); // if distance is shorter than this, randomize node positions double tooClose = _info.NodeDistance / (masterNodes ? 1 : 5); // distance to move when using random position double moveRange = _info.NodeDistance * (masterNodes ? 5 : 1); // compute current distance between nodes double vx = node.Center.X - node2.Center.X; double vy = node.Center.Y - node2.Center.Y; double distance = vx * vx + vy * vy; // kind of simmulated annealing, use random jumps for nodes // that are close one to another; longer jumps for nodes that // overlap completely and shorter for nodes that don't overlap if (distance == 0) { dx += moveRange/2 + _r.Next() % ((int)(moveRange * 3)); dy += moveRange/2 + _r.Next() % ((int)(moveRange * 3)); if (_r.Next() % 2 == 1) dx = -dx; if (_r.Next() % 2 == 1) dy = -dy; } else if (distance < tooClose * tooClose) { dx += moveRange/2 + _r.Next((int)moveRange); dy += moveRange/2 + _r.Next((int)moveRange); if (_r.Next() % 2 == 1) dx = -dx; if (_r.Next() % 2 == 1) dy = -dy; } // if nodes are not that close, calculate normal repulsion else if (distance < intrZone * intrZone) { float mf = masterNodes ? (node.LinkCount + node2.LinkCount) : 1; dx += mf * vx / (distance * distance); dy += mf * vy / (distance * distance); } } // apply the summary repulsion of this node with all other nodes double dlen = dx * dx + dy * dy; if (dlen > 0) { dlen = Math.Sqrt(dlen) / 2; svd.RepulsionDX += dx / dlen; svd.RepulsionDY += dy / dlen; } } // sum repulsion and "spring" forces and move nodes foreach(GraphNode node in _graph.Nodes) { // update node movement speed SpringVertexData vd = (SpringVertexData)node.GetData(_SpringVertex); vd.DX += vd.RepulsionDX + vd.EdgeDX; vd.DY += vd.RepulsionDY + vd.EdgeDY; // move node PointF xyd = node.Center; xyd.X += (float)vd.DX; xyd.Y += (float)vd.DY; if (layoutInfo.EnableClusters && (((xyd.X < minNodeX) || (xyd.Y < minNodeY)) || ((xyd.X > maxNodeX) || (xyd.Y > maxNodeY)))) { xyd = node.Center; vd.DX /= 4; vd.DY /= 4; } node.Center = xyd; } // try to decrease crossings if that option is enabled if (_info.MinimizeCrossings && (step % 10 == 0) && (step < _info.IterationCount * 2 / 3)) { int currentCrossings = CountCrossings(); if (currentCrossings > 0) TryDecreaseCrossings(graph.DocRect, _info.NodeDistance, currentCrossings); } } // offset the graph to its original location RectangleF rcDoc2 = CalcContentRect(_graph); float pdx = rcDoc2.Left - rcDoc.Left; float pdy = rcDoc2.Top - rcDoc.Top; foreach(GraphNode node in _graph.Nodes) { PointF xyd = node.Center; xyd.X -= pdx; xyd.Y -= pdy; node.Center = xyd; } // update flowchart nodes positions foreach(GraphNode node in _graph.Nodes) node.Node.Bounds = node.Bounds; // call progress delegate if (progress != null) progress(total, total); return true; }
public bool Arrange(IGraph graph, AnnealLayoutInfo info, LayoutProgress progress) { _tempGraph = new Graph(graph); _info = info; float nsize = 0; foreach (INode node in graph.Nodes) nsize += node.Bounds.Width; averageNodeSize = nsize / graph.Nodes.Count; // determine what the graph boundaries should be if (info.LayoutArea != RectangleF.Empty) { _drawingArea = info.LayoutArea; } else { double squareSize = averageNodeSize * Math.Ceiling(Math.Sqrt(graph.Nodes.Count)); double width = squareSize * info.WidthHeightRatio; double height = squareSize / info.WidthHeightRatio; _drawingArea = new RectangleF(0, 0, (float)width * 4.5f, (float)height * 4.5f); } // start with random positions if (info.Randomize) { Random rnd = new Random(); foreach (GraphNode node in _tempGraph.Nodes) { node.Center = new PointF( _drawingArea.Left + (float)rnd.NextDouble() * _drawingArea.Width, _drawingArea.Top + (float)rnd.NextDouble() * _drawingArea.Height); } } if (progress != null) progress(0, _info.Stages + 2); SetGraphElements(); SetInitStep(); CalculateInitialState(); _temperature = _info.Temperature; double totalCost = CostFunction(); double previousCost; double stopIterCond; Random rand = new Random(); // cool down for the specified number of annealing stages for (int stage = 0; stage < _info.Stages; stage++) { for (int iter = 0; iter < _info.IterationsPerStage; iter++) { previousCost = totalCost; for (int n = 0; n < _tempGraph.Nodes.Count; n++) { double oldNodeCost = evalCost(_nodes[n]); foreach (int i in Enum.GetValues(typeof(ShiftTo))) { ShiftNode(_nodes[n], (ShiftTo)i, false); if (!nodeInBounds(_nodes[n])) { ShiftNode(_nodes[n], (ShiftTo)i, true); continue; } double newNodeCost = evalCost(_nodes[n]); double newTotalCost = totalCost - oldNodeCost + newNodeCost; if (newNodeCost < oldNodeCost || Math.Pow(Math.E, (totalCost - newTotalCost) / _temperature) > rand.NextDouble()) { totalCost = newTotalCost; CommitMove(_nodes[n]); break; } else { ShiftNode(_nodes[n], (ShiftTo)i, true); RollbackMove(_nodes[n]); } } } stopIterCond = previousCost / totalCost; if (stopIterCond > .98 && stopIterCond <= 1) break; } SetTemperature(); DecreaseMoveValue(); if (progress != null) progress(stage + 1, _info.Stages + 2); } FineTuning(); if (progress != null) progress(_info.Stages + 2, _info.Stages + 2); foreach (GraphNode node in _tempGraph.Nodes) node.Node.Bounds = node.Bounds; return true; }
private RectangleF CalcContentRect(Graph graph) { RectangleF res = RectangleF.Empty; foreach(GraphNode node in graph.Nodes) { if(res == RectangleF.Empty) res = node.Bounds; else res = Utilities.UnionRects(res, node.Bounds); } return res; }