Stores information calculated by FruchtermanReingoldLayout for one vertex.
FruchtermanReingoldLayout calculates several pieces of information about each vertex in the graph being laid out. Instead of storing each piece of information in a separate vertex metadata key, which would require multiple key lookups and inefficient boxing and unboxing of value types, it stores all the information for the vertex in one instance of type FruchtermanReingoldVertexInfo and stores the instance in the vertex's Tag.

All data is exposed as public fields instead of properties. That's because the method in FruchtermanReingoldLayout that calculates repulsive forces accesses the data repeatedly in an O(V-squared) loop, and property getters are much slower than direct field accesses.

Inheritance: LayoutsBase
        InitializeMetadata
        (
            IGraph oGraph,
            ICollection <IVertex> oVerticesToLayOut
        )
        {
            Debug.Assert(oGraph != null);
            Debug.Assert(oVerticesToLayOut != null);
            AssertValid();

            foreach (IVertex oVertex in oVerticesToLayOut)
            {
                // Create an object that will store all calculated values for the
                // vertex.

                FruchtermanReingoldVertexInfo oFruchtermanReingoldVertexInfo =
                    new FruchtermanReingoldVertexInfo(oVertex.Location);

                // The object could be stored in a metadata key, but because the
                // number of retrievals can be very large, it's more efficient to
                // store it in the Tag.  If a Tag already exists, save it in a
                // metadata key.

                SaveTag(oVertex);
                oVertex.Tag = oFruchtermanReingoldVertexInfo;
            }

            foreach (IEdge oEdge in oGraph.Edges)
            {
                // Do the same for edges.  In this case, each edge has just one
                // piece of information, the edge weight.

                SaveTag(oEdge);

                oEdge.Tag = (Single)EdgeUtil.GetPositiveEdgeWeight(oEdge);
            }
        }
    InitializeMetadata
    (
        IGraph oGraph,
        ICollection<IVertex> oVerticesToLayOut
    )
    {
        Debug.Assert(oGraph != null);
        Debug.Assert(oVerticesToLayOut != null);
        AssertValid();

        foreach (IVertex oVertex in oVerticesToLayOut)
        {
            // Create an object that will store all calculated values for the
            // vertex.

            FruchtermanReingoldVertexInfo oFruchtermanReingoldVertexInfo =
                new FruchtermanReingoldVertexInfo(oVertex.Location);

            // The object could be stored in a metadata key, but because the
            // number of retrievals can be very large, it's more efficient to
            // store it in the Tag.  If a Tag already exists, save it in a
            // metadata key.

            SaveTag(oVertex);
            oVertex.Tag = oFruchtermanReingoldVertexInfo;
        }

        foreach (IEdge oEdge in oGraph.Edges)
        {
            // Do the same for edges.  In this case, each edge has just one
            // piece of information, the edge weight.

            SaveTag(oEdge);

            oEdge.Tag = (Single)EdgeUtil.GetPositiveEdgeWeight(oEdge);
        }
    }
        SetUnboundedLocations
        (
            ICollection <IVertex> verticesToLayOut,
            LayoutContext layoutContext,
            Single fTemperature,
            Boolean bAlsoSetVertexLocations
        )
        {
            Debug.Assert(verticesToLayOut != null);
            Debug.Assert(layoutContext != null);
            Debug.Assert(fTemperature > 0);
            AssertValid();

            // The following variables define the unbounded rectangle.

            TMathType tMinLocationX = Single.MaxValue;
            TMathType tMaxLocationX = Single.MinValue;

            TMathType tMinLocationY = Single.MaxValue;
            TMathType tMaxLocationY = Single.MinValue;

            foreach (IVertex oVertex in verticesToLayOut)
            {
                // Retrieve the object that stores calculated values for the
                // vertex.  We need the vertex's current unbounded location and
                // the displacement created by the repulsive and attractive forces
                // on the vertex.

                FruchtermanReingoldVertexInfo oVertexInfo =
                    (FruchtermanReingoldVertexInfo)oVertex.Tag;

                TMathType tUnboundedLocationX =
                    (TMathType)oVertexInfo.UnboundedLocationX;

                TMathType tUnboundedLocationY =
                    (TMathType)oVertexInfo.UnboundedLocationY;

                TMathType tDisplacementX = (TMathType)oVertexInfo.DisplacementX;
                TMathType tDisplacementY = (TMathType)oVertexInfo.DisplacementY;

                TMathType tDisplacement = (TMathType)Math.Sqrt(
                    (tDisplacementX * tDisplacementX) +
                    (tDisplacementY * tDisplacementY)
                    );

                if (tDisplacement != 0)
                {
                    // Calculate a new unbounded location, limited by the current
                    // temperature.

                    tUnboundedLocationX += (tDisplacementX / tDisplacement) *
                                           Math.Min(tDisplacement, (TMathType)fTemperature);

                    tUnboundedLocationY += (tDisplacementY / tDisplacement) *
                                           Math.Min(tDisplacement, (TMathType)fTemperature);
                }

                // Update the vertex's unbounded location.

                oVertexInfo.UnboundedLocationX = (Single)tUnboundedLocationX;
                oVertexInfo.UnboundedLocationY = (Single)tUnboundedLocationY;

                // Expand the unbounded rectangle if necessary.

                tMinLocationX = Math.Min(tUnboundedLocationX, tMinLocationX);
                tMaxLocationX = Math.Max(tUnboundedLocationX, tMaxLocationX);

                tMinLocationY = Math.Min(tUnboundedLocationY, tMinLocationY);
                tMaxLocationY = Math.Max(tUnboundedLocationY, tMaxLocationY);
            }

            if (!bAlsoSetVertexLocations)
            {
                return;
            }

            Debug.Assert(verticesToLayOut.Count != 0);

            Debug.Assert(tMinLocationX != Single.MaxValue);
            Debug.Assert(tMaxLocationX != Single.MinValue);
            Debug.Assert(tMinLocationY != Single.MaxValue);
            Debug.Assert(tMaxLocationY != Single.MinValue);

            // Get a Matrix that will transform vertex locations from coordinates
            // in the unbounded rectangle to cooordinates in the bounded graph
            // rectangle.

            Matrix oTransformationMatrix = LayoutUtil.GetRectangleTransformation(

                RectangleF.FromLTRB(
                    (Single)tMinLocationX,
                    (Single)tMinLocationY,
                    (Single)tMaxLocationX,
                    (Single)tMaxLocationY
                    ),

                layoutContext.GraphRectangle
                );

            // Transform the vertex locations.

            foreach (IVertex oVertex in verticesToLayOut)
            {
                FruchtermanReingoldVertexInfo oVertexInfo =
                    (FruchtermanReingoldVertexInfo)oVertex.Tag;

                PointF [] aoLocation = new PointF [] {
                    new PointF(
                        oVertexInfo.UnboundedLocationX,
                        oVertexInfo.UnboundedLocationY
                        )
                };

                oTransformationMatrix.TransformPoints(aoLocation);

                if (!VertexIsLocked(oVertex))
                {
                    oVertex.Location = aoLocation[0];
                }
            }
        }
        CalculateAttractiveForces
        (
            ICollection <IEdge> edgesToLayOut,
            Single k
        )
        {
            Debug.Assert(edgesToLayOut != null);
            Debug.Assert(k != 0);
            AssertValid();

            const String MethodName = "CalculateAttractiveForces";

            foreach (IEdge oEdge in edgesToLayOut)
            {
                if (oEdge.IsSelfLoop)
                {
                    // A vertex isn't attracted to itself.

                    continue;
                }

                // Get the edge's vertices.

                IVertex oVertexV, oVertexU;

                EdgeUtil.EdgeToVertices(oEdge, this.ClassName, MethodName,
                                        out oVertexV, out oVertexU);

                // Retrieve the objects that store calculated values for the
                // vertices.

                FruchtermanReingoldVertexInfo oVertexInfoV =
                    (FruchtermanReingoldVertexInfo)oVertexV.Tag;

                FruchtermanReingoldVertexInfo oVertexInfoU =
                    (FruchtermanReingoldVertexInfo)oVertexU.Tag;

                TMathType tDeltaX =
                    (TMathType)oVertexInfoV.UnboundedLocationX -
                    (TMathType)oVertexInfoU.UnboundedLocationX;

                TMathType tDeltaY =
                    (TMathType)oVertexInfoV.UnboundedLocationY -
                    (TMathType)oVertexInfoU.UnboundedLocationY;

                TMathType tDelta = (TMathType)Math.Sqrt(
                    (tDeltaX * tDeltaX) + (tDeltaY * tDeltaY)
                    );

                // Use the edge weight, which InitializeMetadata() stored in the
                // Tag.

                tDelta *= (Single)oEdge.Tag;

                TMathType tDisplacementV_X = (TMathType)oVertexInfoV.DisplacementX;
                TMathType tDisplacementV_Y = (TMathType)oVertexInfoV.DisplacementY;

                TMathType tDisplacementU_X = (TMathType)oVertexInfoU.DisplacementX;
                TMathType tDisplacementU_Y = (TMathType)oVertexInfoU.DisplacementY;

                // (Note that there is an obvious typo in the Fruchterman-Reingold
                // paper for computing the attractive force.  The function fa(z) at
                // the top of Figure 1 is defined as x squared over k.  It should
                // read z squared over k.)

                TMathType fa = (tDelta * tDelta) / (TMathType)k;

                if (tDelta == 0)
                {
                    // TODO: Is this the correct way to handle vertices in the same
                    // location?  See the notes in CalculateRepulsiveForces().

                    continue;
                }

                Debug.Assert(tDelta != 0);

                TMathType faOverDelta = fa / tDelta;

                TMathType tFactorX = tDeltaX * faOverDelta;
                TMathType tFactorY = tDeltaY * faOverDelta;

                tDisplacementV_X -= tFactorX;
                tDisplacementV_Y -= tFactorY;

                tDisplacementU_X += tFactorX;
                tDisplacementU_Y += tFactorY;

                oVertexInfoV.DisplacementX = (Single)tDisplacementV_X;
                oVertexInfoV.DisplacementY = (Single)tDisplacementV_Y;

                oVertexInfoU.DisplacementX = (Single)tDisplacementU_X;
                oVertexInfoU.DisplacementY = (Single)tDisplacementU_Y;
            }
        }
        CalculateRepulsiveForces
        (
            ICollection <IVertex> verticesToLayOut,
            Single k
        )
        {
            Debug.Assert(verticesToLayOut != null);
            AssertValid();

            TMathType tkSquared = (TMathType)(k * k);

            foreach (IVertex oVertexV in verticesToLayOut)
            {
                // Retrieve the object that stores calculated values for the
                // vertex.

                FruchtermanReingoldVertexInfo oVertexInfoV =
                    (FruchtermanReingoldVertexInfo)oVertexV.Tag;

                TMathType tDisplacementX = 0;
                TMathType tDisplacementY = 0;

                foreach (IVertex oVertexU in verticesToLayOut)
                {
                    if (oVertexU == oVertexV)
                    {
                        continue;
                    }

                    FruchtermanReingoldVertexInfo oVertexInfoU =
                        (FruchtermanReingoldVertexInfo)oVertexU.Tag;

                    TMathType tDeltaX =
                        (TMathType)oVertexInfoV.UnboundedLocationX -
                        (TMathType)oVertexInfoU.UnboundedLocationX;

                    TMathType tDeltaY =
                        (TMathType)oVertexInfoV.UnboundedLocationY -
                        (TMathType)oVertexInfoU.UnboundedLocationY;

                    TMathType tDelta = (TMathType)Math.Sqrt(
                        (tDeltaX * tDeltaX) + (tDeltaY * tDeltaY)
                        );

                    // The Fruchterman-Reingold paper says this about vertices in
                    // the same location:
                    //
                    // "A special case occurs when vertices are in the same
                    // position: our implementation acts as though the two vertices
                    // are a small distance apart in a randomly chosen orientation:
                    // this leads to a violent repulsive effect separating them."
                    //
                    // Handle this case by arbitrarily setting a small
                    // displacement.

                    if (tDelta == 0)
                    {
                        tDisplacementX += 1;
                        tDisplacementY += 1;
                    }
                    else
                    {
                        Debug.Assert(tDelta != 0);

                        TMathType fr = tkSquared / tDelta;

                        TMathType frOverDelta = fr / tDelta;

                        tDisplacementX += tDeltaX * frOverDelta;
                        tDisplacementY += tDeltaY * frOverDelta;
                    }
                }

                // Save the results for VertexV.

                oVertexInfoV.DisplacementX = tDisplacementX;
                oVertexInfoV.DisplacementY = tDisplacementY;
            }
        }
 SetUp()
 {
     m_oFruchtermanReingoldVertexInfo = new FruchtermanReingoldVertexInfo(
         new PointF(InitialLocationX, InitialLocationY) );
 }