/// <summary> /// Runs the force-directed layout algorithm on this Diagram, using the specified parameters. /// </summary> /// <param name="damping">Value between 0 and 1 that slows the motion of the nodes during layout.</param> /// <param name="springLength">Value in pixels representing the length of the imaginary springs that run along the connectors.</param> /// <param name="maxIterations">Maximum number of iterations before the algorithm terminates.</param> /// <param name="deterministic">Whether to use a random or deterministic layout.</param> public void Arrange(double damping, int springLength, int maxIterations, bool deterministic) { // random starting positions can be made deterministic by seeding System.Random with a constant Random rnd = deterministic ? new Random(0) : new Random(); // copy nodes into an array of metadata and randomise initial coordinates for each node NodeLayoutInfo[] layout = new NodeLayoutInfo[mNodes.Count]; for (int i = 0; i < mNodes.Count; i++) { layout[i] = new NodeLayoutInfo(mNodes[i], new Layv(), Point.Empty); layout[i].Layn.Location = new Point(rnd.Next(-50, 50), rnd.Next(-50, 50)); } int stopCount = 0; int iterations = 0; while (true) { double totalDisplacement = 0; for (int i = 0; i < layout.Length; i++) { NodeLayoutInfo current = layout[i]; // express the node's current position as a vector, relative to the origin Layv currentPosition = new Layv(CalcDistance(Point.Empty, current.Layn.Location), GetBearingAngle(Point.Empty, current.Layn.Location)); Layv netForce = new Layv(0, 0); // determine repulsion between nodes foreach (Layn other in mNodes) { if (other != current.Layn) { netForce += CalcRepulsionForce(current.Layn, other); } } // determine attraction caused by connections foreach (Layn child in current.Layn.Connections) { netForce += CalcAttractionForce(current.Layn, child, springLength); } foreach (Layn parent in mNodes) { if (parent.Connections.Contains(current.Layn)) { netForce += CalcAttractionForce(current.Layn, parent, springLength); } } // apply net force to node velocity current.Velocity = (current.Velocity + netForce) * damping; // apply velocity to node position current.NextPosition = (currentPosition + current.Velocity).ToPoint(); } // move nodes to resultant positions (and calculate total displacement) for (int i = 0; i < layout.Length; i++) { NodeLayoutInfo current = layout[i]; totalDisplacement += CalcDistance(current.Layn.Location, current.NextPosition); current.Layn.Location = current.NextPosition; } iterations++; if (totalDisplacement < 10) { stopCount++; } if (stopCount > 15) { break; } if (iterations > maxIterations) { break; } } // center the diagram around the origin Rectangle logicalBounds = GetDiagramBounds(); Point midPoint = new Point(logicalBounds.X + (logicalBounds.Width / 2), logicalBounds.Y + (logicalBounds.Height / 2)); foreach (Layn node in mNodes) { node.Location -= (Size)midPoint; } }
public Point NextPosition; // the node's position after the next iteration /// <summary> /// Initialises a new instance of the Diagram.NodeLayoutInfo class, using the specified parameters. /// </summary> /// <param name="layn"></param> /// <param name="velocity"></param> /// <param name="nextPosition"></param> public NodeLayoutInfo(Layn layn, Layv velocity, Point nextPosition) { Layn = layn; Velocity = velocity; NextPosition = nextPosition; }