InitializeMetadata ( ICollection <IVertex> verticesToLayOut ) { Debug.Assert(verticesToLayOut != null); AssertValid(); foreach (IVertex oVertex in verticesToLayOut) { // 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. Object oTag = oVertex.Tag; if (oTag != null) { oVertex.SetValue( ReservedMetadataKeys.FruchtermanReingoldLayoutTagStorage, oTag ); } oVertex.Tag = oFruchtermanReingoldVertexInfo; } }
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) ); 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; } }
//************************************************************************* // Method: InitializeMetadata() // /// <summary> /// Stores required metadata on the graph's vertices before the layout /// begins. /// </summary> /// /// <param name="verticesToLayOut"> /// Vertices to lay out. The collection is guaranteed to have at least one /// vertex. /// </param> //************************************************************************* protected void InitializeMetadata( ICollection<IVertex> verticesToLayOut ) { Debug.Assert(verticesToLayOut != null); AssertValid(); foreach (IVertex oVertex in verticesToLayOut) { // 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. Object oTag = oVertex.Tag; if (oTag != null) { oVertex.SetValue( ReservedMetadataKeys.FruchtermanReingoldLayoutTagStorage, oTag ); } oVertex.Tag = oFruchtermanReingoldVertexInfo; } }