Provides access to objects needed for laying out a graph.
Inheritance: LayoutsBase
    SetUp()
    {
        Rectangle oGraphRectangle = new Rectangle(
            Point.Empty, new Size(RectangleWidth, RectangleHeight) );

        m_oLayoutContext = new LayoutContext(oGraphRectangle);
    }
    //*************************************************************************
    //  Constructor: LayOutGraphAsyncArguments()
    //
    /// <summary>
    /// Initializes a new instance of the <see
    /// cref="LayOutGraphAsyncArguments" /> class.
    /// </summary>
    ///
    /// <param name="graph">
    /// Graph to lay out.
    /// </param>
    ///
    /// <param name="layoutContext">
    /// Provides access to objects needed to lay out the graph.
    /// </param>
    //*************************************************************************

    public LayOutGraphAsyncArguments
    (
        IGraph graph,
        LayoutContext layoutContext
    )
    {
        m_oGraph = graph;
        m_oLayoutContext = layoutContext;

        AssertValid();
    }
    SetUp()
    {
        m_oGraph = new Graph();

        Rectangle oRectangle = new Rectangle(21, 34, 56, 78);

        m_oLayoutContext = new LayoutContext(oRectangle);

        m_oLayOutGraphAsyncArguments =
            new LayOutGraphAsyncArguments(m_oGraph, m_oLayoutContext);
    }
    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];
            }
        }
    }
    TestTransformLayoutBad3()
    {
        // null newLayoutContext.

        try
        {
            IGraph oGraph = new Graph();

            LayoutContext oLayoutContext = new LayoutContext(Rectangle.Empty);

            m_oFruchtermanReingoldLayout.TransformLayout(
                oGraph, oLayoutContext, null);
        }
        catch (ArgumentNullException oArgumentNullException)
        {
            Assert.AreEqual(

                "Smrf.NodeXL.Layouts.FruchtermanReingoldLayout."
                + "TransformLayout: newLayoutContext argument can't be"
                + " null.\r\n"
                + "Parameter name: newLayoutContext"
                ,
                oArgumentNullException.Message
                );

            throw oArgumentNullException;
        }
    }
    TestLayOut()
    {
        const Int32 Vertices = 100;

        IGraph oGraph = new Graph();

        IVertex [] aoVertices = TestGraphUtil.AddVertices(oGraph, Vertices);

        TestGraphUtil.MakeGraphComplete(oGraph, aoVertices, false);

        // Initialize the vertex locations to impossible values.

        const Int32 ImpossibleCoordinate = Int32.MinValue;

        foreach (IVertex oVertex in aoVertices)
        {
            oVertex.Location = new Point(
                ImpossibleCoordinate, ImpossibleCoordinate);
        }

        const Int32 Width = 1000;
        const Int32 Height = 600;

        Rectangle oRectangle = new Rectangle(0, 0, Width, Height);

        LayoutContext oLayoutContext = new LayoutContext(oRectangle);

        m_oFruchtermanReingoldLayout.LayOutGraph(oGraph, oLayoutContext);

        foreach (IVertex oVertex in aoVertices)
        {
            PointF oLocation = oVertex.Location;

            Single fX = oLocation.X;

            Assert.AreNotEqual(fX, ImpossibleCoordinate);
            Assert.IsTrue(fX >= 0);
            Assert.IsTrue(fX <= Width);

            Single fY = oLocation.Y;

            Assert.AreNotEqual(fY, ImpossibleCoordinate);
            Assert.IsTrue(fY >= 0);
            Assert.IsTrue(fY <= Height);
        }
    }
    LayOutSmallerComponentsInBins
    (
        IGraph graph,
        ICollection<IVertex> verticesToLayOut,
        LayoutContext layoutContext,
        out ICollection<IVertex> remainingVertices,
        out Rectangle remainingRectangle
    )
    {
        AssertValid();

        remainingVertices = null;
        remainingRectangle = Rectangle.Empty;

        // This method modifies some of the graph's metadata.  Save the
        // original metadata.

        Boolean bOriginalGraphHasBeenLaidOut =
            LayoutMetadataUtil.GraphHasBeenLaidOut(graph);

        ICollection<IVertex> oOriginalLayOutTheseVerticesOnly =
            ( ICollection<IVertex> )graph.GetValue(
                ReservedMetadataKeys.LayOutTheseVerticesOnly,
                typeof( ICollection<IVertex> ) );

        // Split the vertices into strongly connected components, sorted in
        // increasing order of vertex count.

        ConnectedComponentCalculator oConnectedComponentCalculator =
            new ConnectedComponentCalculator();

        IList< LinkedList<IVertex> > oComponents =
            oConnectedComponentCalculator.CalculateStronglyConnectedComponents(
                verticesToLayOut, graph, true);

        Int32 iComponents = oComponents.Count;

        // This object will split the graph rectangle into bin rectangles.

        RectangleBinner oRectangleBinner = new RectangleBinner(
            layoutContext.GraphRectangle, m_iBinLength);

        Int32 iComponent = 0;

        for (iComponent = 0; iComponent < iComponents; iComponent++)
        {
            LinkedList<IVertex> oComponent = oComponents[iComponent];
            Int32 iVerticesInComponent = oComponent.Count;

            if (iVerticesInComponent> m_iMaximumVerticesPerBin)
            {
                // The vertices in the remaining components should not be
                // binned.

                break;
            }

            Rectangle oBinRectangle;

            if ( !oRectangleBinner.TryGetNextBin(out oBinRectangle) )
            {
                // There is no room for an additional bin rectangle.

                break;
            }

            // Lay out the component within the bin rectangle.

            LayOutComponentInBin(graph, oComponent, oBinRectangle);
        }

        // Restore the original metadata on the graph.

        if (bOriginalGraphHasBeenLaidOut)
        {
            LayoutMetadataUtil.MarkGraphAsLaidOut(graph);
        }
        else
        {
            LayoutMetadataUtil.MarkGraphAsNotLaidOut(graph);
        }

        if (oOriginalLayOutTheseVerticesOnly != null)
        {
            graph.SetValue(ReservedMetadataKeys.LayOutTheseVerticesOnly,
                oOriginalLayOutTheseVerticesOnly);
        }
        else
        {
            graph.RemoveKey(ReservedMetadataKeys.LayOutTheseVerticesOnly);
        }

        if ( oRectangleBinner.TryGetRemainingRectangle(
            out remainingRectangle) )
        {
            remainingVertices = GetRemainingVertices(oComponents, iComponent);

            return (remainingVertices.Count > 0);
        }

        return (false);
    }
    TestTransformLayoutBad3()
    {
        // null newLayoutContext.

        try
        {
            IGraph oGraph = new Graph();

            LayoutContext oLayoutContext = new LayoutContext(Rectangle.Empty);

            m_oFruchtermanReingoldLayout.TransformLayout(
                oGraph, oLayoutContext, null);
        }
        catch (ArgumentNullException oArgumentNullException)
        {
            String enMsg="Smrf.NodeXL.Layouts.FruchtermanReingoldLayout."
                + "TransformLayout: newLayoutContext argument can't be"
                + " null.\r\n"
                + "Parameter name: newLayoutContext";
            String chMsg="Smrf.NodeXL.Layouts.FruchtermanReingoldLayout."
                + "TransformLayout: newLayoutContext argument can't be"
                + " null.\r\n"
                + "參數名稱: newLayoutContext";
            Assert.IsTrue((enMsg == oArgumentNullException.Message ||
                chMsg == oArgumentNullException.Message) ? true : false
                );

            throw oArgumentNullException;
        }
    }
    LayOutGraphCoreSorted
    (
        IGraph graph,
        ICollection<IVertex> verticesToLayOut,
        LayoutContext layoutContext,
        BackgroundWorker backgroundWorker
    )
    {
        Debug.Assert(graph != null);
        Debug.Assert(verticesToLayOut != null);
        Debug.Assert(verticesToLayOut.Count > 0);
        Debug.Assert(layoutContext != null);
        AssertValid();

        if (backgroundWorker != null && backgroundWorker.CancellationPending)
        {
            return (false);
        }

        Int32 iVertices = verticesToLayOut.Count;

        // The vertices are placed at equal angles along the spiral.

        Double dAngleBetweenVertices = MaximumSpiralAngle / (Double)iVertices;

        Double dCenterX, dCenterY, dHalfSize;

        GetRectangleCenterAndHalfSize(layoutContext.GraphRectangle,
            out dCenterX, out dCenterY, out dHalfSize);

        // Parametric equations for a spiral:
        //
        //     dX = dA * dAngle * cos(dAngle)
        //     dY = dA * dAngle * sin(dAngle)
        //
        // where A is a constant.

        double dA = dHalfSize / MaximumSpiralAngle;

        Double dAngle = 0;

        foreach (IVertex oVertex in verticesToLayOut)
        {
            if ( !VertexIsLocked(oVertex) )
            {
                Double dX = dCenterX + dA * dAngle * Math.Cos(dAngle);
                Double dY = dCenterY + dA * dAngle * Math.Sin(dAngle);

                oVertex.Location = new PointF( (Single)dX, (Single)dY );
            }

            dAngle += dAngleBetweenVertices;
        }

        return (true);
    }
Beispiel #10
0
    LayOutGraphCore
    (
        IGraph graph,
        ICollection<IVertex> verticesToLayOut,
        LayoutContext layoutContext,
        BackgroundWorker backgroundWorker
    )
    {
        Debug.Assert(graph != null);
        Debug.Assert(verticesToLayOut != null);
        Debug.Assert(verticesToLayOut.Count > 0);
        Debug.Assert(layoutContext != null);
        AssertValid();

        // This class does not incorporate GLEE source code to implement the
        // layout.  Instead, it transfers the NodeXL graph to a GLEE graph,
        // tells GLEE to lay out the GLEE graph, then reads the resulting
        // layout geometry from the GLEE graph and stores it as metadata in the
        // NodeXL graph.  Although this involves several copy operations, it
        // uses only the GLEE public interfaces and bypasses all the
        // maintenance headaches that would arise if the GLEE source code were
        // used.
        //
        // Note:
        //
        // This class was written when the NodeXL visualization layer used
        // GDI+.  There were complex families of vertex and edge drawers,
        // including SugiyamaVertexDrawer and EdgeVertexDrawer.  This class
        // stores vertex and edge metadata meant for use by those
        // Sugiyama-specific drawers, which were eliminated when the
        // visualization layer was changed to WPF and the vertex- and edge-
        // drawing code was vastly simplified.  Therefore, the vertex radius
        // and edge curve information stored by this class is currently ignored
        // in the WPF layer.  Vertices are of constant size, and edges are
        // drawn as straight lines.
        // 
        // This could be fixed by modifying the WPF layer to be Sugiyama-aware,
        // but as of June 2009 no one has complained about these problems and
        // so the fix is postponed.

        // Create a GLEE graph.

        Microsoft.Glee.GleeGraph oGleeGraph = new Microsoft.Glee.GleeGraph();

        // Get the vertex radius specified by the SugiyamaVertexDrawer.

        Single fNodeXLVertexRadius = GetNodeXLVertexRadius(layoutContext);

        // Loop through the NodeXL vertices.

        foreach (IVertex oVertex in verticesToLayOut)
        {
            // Create a circle that defines the GLEE node's boundary.  GLEE's
            // layout code does not modify the node's boundary, it just shifts
            // the node's center.

            Microsoft.Glee.Splines.ICurve oCurve =
                Microsoft.Glee.Splines.CurveFactory.CreateEllipse(
                    fNodeXLVertexRadius, fNodeXLVertexRadius,
                    new Microsoft.Glee.Splines.Point(0, 0)
                    );

            // Create a GLEE node that corresponds to the NodeXL node.

            Microsoft.Glee.Node oGleeNode =
                new Microsoft.Glee.Node(oVertex.ID.ToString(), oCurve);

            oGleeGraph.AddNode(oGleeNode);

            // Store the GLEE node as temporary metadata in the NodeXL node.

            oVertex.SetValue(ReservedMetadataKeys.SugiyamaGleeNode, oGleeNode);
        }

        // Loop through the NodeXL edges.

        ICollection<IEdge> oEdgesToLayOut =
            GetEdgesToLayOut(graph, verticesToLayOut);

        foreach (IEdge oEdge in oEdgesToLayOut)
        {
            // Retrieve the NodeXL edge's vertices.

            IVertex [] aoVertices = oEdge.Vertices;

            // Retrieve the corresponding GLEE node for the NodeXL edge's
            // vertices.

            Microsoft.Glee.Node oGleeNode0 =
                NodeXLVertexToGleeNode( aoVertices[0] );

            Microsoft.Glee.Node oGleeNode1 =
                NodeXLVertexToGleeNode( aoVertices[1] );

            // Create a GLEE edge using the two GLEE nodes.

            Microsoft.Glee.Edge oGleeEdge =
                new Microsoft.Glee.Edge(oGleeNode0, oGleeNode1);

            oGleeGraph.AddEdge(oGleeEdge);

            // Store the GLEE edge as temporary metadata in the NodeXL edge.

            oEdge.SetValue(
                ReservedMetadataKeys.SugiyamaGleeEdge, oGleeEdge);
        }

        // Tell GLEE to lay out the GLEE graph.  This shifts the node centers,
        // connects the nodes with lines, and computes the smallest rectangle
        // that contains all the nodes and edges.

        oGleeGraph.CalculateLayout();

        // The rectangle computed by GLEE does not have the dimensions
        // specified by layoutContext.GraphRectangle.  Get a transformation
        // that will map coordinates in the GLEE rectangle to coordinates in
        // the specified NodeXL rectangle.

        Matrix oTransformationMatrix =
            GetTransformationMatrix(oGleeGraph, layoutContext.GraphRectangle);

        // Because of the transformation, the radius of the vertices is no
        // longer the original fNodeXLVertexRadius.  Compute the transformed
        // radius.

        PointF [] aoRadiusPoints = new PointF [] {
            PointF.Empty,
            new PointF(0, fNodeXLVertexRadius)
            };

        oTransformationMatrix.TransformPoints(aoRadiusPoints);

        Double dX = aoRadiusPoints[1].X - aoRadiusPoints[0].X;
        Double dY = aoRadiusPoints[1].Y - aoRadiusPoints[0].Y;

        Single fTransformedNodeXLVertexRadius =
            (Single)Math.Sqrt(dX * dX + dY * dY);

        // Store the computed radius as metadata on the graph, to be retrieved
        // by SugiyamaVertexDrawer.DrawVertex().

        graph.SetValue(ReservedMetadataKeys.SugiyamaComputedRadius,
            fTransformedNodeXLVertexRadius);

        // Loop through the NodeXL vertices again.

        foreach (IVertex oVertex in verticesToLayOut)
        {
            // Retrieve the corresponding GLEE node.

            Microsoft.Glee.Node oGleeNode =
                NodeXLVertexToGleeNode(oVertex);

            oVertex.RemoveKey(ReservedMetadataKeys.SugiyamaGleeNode);

            // Get the shifted node center and transform it to NodeXL
            // coordinates.

            if ( !VertexIsLocked(oVertex) )
            {
                oVertex.Location = GleePointToTransformedPointF(
                    oGleeNode.Center, oTransformationMatrix);
            }
        }

        // Loop through the NodeXL edges again.

        foreach (IEdge oEdge in oEdgesToLayOut)
        {
            // Retrieve the corresponding GLEE edge.

            Microsoft.Glee.Edge oGleeEdge = NodeXLEdgeToGleeEdge(oEdge);

            oEdge.RemoveKey(ReservedMetadataKeys.SugiyamaGleeEdge);

            // Get the GLEE curve that describes most (but not all) of the
            // edge.

            Microsoft.Glee.Splines.Curve oCurve =
                (Microsoft.Glee.Splines.Curve)oGleeEdge.Curve;

            // TODO: In Microsoft.Glee.GraphViewerGdi.GViewer.
            // TransferGeometryFromGleeGraphToGraph() in the GLEE source code,
            // oCurve can apparently be null.  Can it be null here?  For now,
            // assume that the answer is no.

            Debug.Assert(oCurve != null);

            // Convert the curve to an array of PointF objects in NodeXL
            // coordinates.

            PointF [] aoCurvePoints = GleeCurveToTransformedPointFArray(
                oCurve, oTransformationMatrix);

            // Store the array as metadata in the edge, to be retrieved by
            // SugiyamaEdgeDrawer.DrawEdge().

            oEdge.SetValue(
                ReservedMetadataKeys.SugiyamaCurvePoints, aoCurvePoints);

            // Get the endpoint of the curve and transform it to NodeXL
            // coordinates.

            PointF oEndpoint = GleePointToTransformedPointF(
                oGleeEdge.ArrowHeadAtTargetPosition, oTransformationMatrix);

            // Store the endpoint as metadata in the edge, to be retrieved by
            // SugiyamaEdgeDrawer.DrawEdge().

            oEdge.SetValue(ReservedMetadataKeys.SugiyamaEndpoint, oEndpoint);
        }

        return (true);
    }
    LayOutGraphCoreSorted
    (
        IGraph graph,
        ICollection<IVertex> verticesToLayOut,
        LayoutContext layoutContext,
        BackgroundWorker backgroundWorker
    )
    {
        Debug.Assert(graph != null);
        Debug.Assert(verticesToLayOut != null);
        Debug.Assert(verticesToLayOut.Count > 0);
        Debug.Assert(layoutContext != null);
        AssertValid();

        if (backgroundWorker != null && backgroundWorker.CancellationPending)
        {
            return (false);
        }

        RectangleF oRectangleF = layoutContext.GraphRectangle;

        Debug.Assert(oRectangleF.Width > 0 && oRectangleF.Height > 0);

        // Get the number of rows and columns to use in the grid.

        Int32 iRows, iColumns;

        GetRowsAndColumns(verticesToLayOut, layoutContext,
            out iRows, out iColumns);

        Debug.Assert(iRows > 0);
        Debug.Assert(iColumns > 0);
        Debug.Assert(iColumns * iRows >= verticesToLayOut.Count);

        // Get the distances between vertices;

        Double dRowSpacing    = (Double)oRectangleF.Height / (Double)iRows;
        Double dColumnSpacing = (Double)oRectangleF.Width  / (Double)iColumns;

        // Set the location on each vertex.  The vertices are placed at the
        // centers of the grid boxes.

        Double dXStart = oRectangleF.Left + (dColumnSpacing / 2.0);

        Double dX = dXStart;
        Double dY = oRectangleF.Top + (dRowSpacing / 2.0);

        Int32 iColumn = 0;

        foreach (IVertex oVertex in verticesToLayOut)
        {
            if ( !VertexIsLocked(oVertex) )
            {
                oVertex.Location = new PointF( (Single)dX,  (Single)dY );
            }

            iColumn++;

            if (iColumn >= iColumns)
            {
                dX = dXStart;
                dY += dRowSpacing;

                iColumn = 0;
            }
            else
            {
                dX += dColumnSpacing;
            }
        }

        return (true);
    }
    GetAdjustedLayoutContext
    (
        IGraph oGraph,
        LayoutContext oOriginalLayoutContext,
        out LayoutContext oAdjustedLayoutContext
    )
    {
        Debug.Assert(oGraph != null);
        Debug.Assert(oOriginalLayoutContext != null);
        AssertValid();

        oAdjustedLayoutContext = null;
        Rectangle oAdjustedRectangle = oOriginalLayoutContext.GraphRectangle;

        if (
            oGraph.ContainsKey(
                ReservedMetadataKeys.LayOutTheseVerticesWithinBounds)
            &&
            oGraph.ContainsKey(
                ReservedMetadataKeys.LayOutTheseVerticesOnly)
            )
        {
            // Get the bounding rectangle of the specified vertices.

            Single fMinimumX = Single.MaxValue;
            Single fMaximumX = Single.MinValue;
            Single fMinimumY = Single.MaxValue;
            Single fMaximumY = Single.MinValue;
            
            foreach ( IVertex oVertex in GetVerticesToLayOut(oGraph) )
            {
                PointF oLocation = oVertex.Location;
                Single fX = oLocation.X;
                Single fY = oLocation.Y;

                fMinimumX = Math.Min(fX, fMinimumX);
                fMaximumX = Math.Max(fX, fMaximumX);
                fMinimumY = Math.Min(fY, fMinimumY);
                fMaximumY = Math.Max(fY, fMaximumY);
            }

            if (fMinimumX != Single.MaxValue)
            {
                oAdjustedRectangle = Rectangle.FromLTRB(
                    (Int32)Math.Ceiling(fMinimumX),
                    (Int32)Math.Ceiling(fMinimumY),
                    (Int32)Math.Floor(fMaximumX),
                    (Int32)Math.Floor(fMaximumY) );
            }
        }
        else
        {
            oAdjustedRectangle.Inflate(-m_iMargin, -m_iMargin);
        }

        if (oAdjustedRectangle.Width > 0 && oAdjustedRectangle.Height > 0)
        {
            oAdjustedLayoutContext = new LayoutContext(oAdjustedRectangle);
            return (true);
        }

        return (false);
    }
    TransformLayout
    (
        IGraph graph,
        LayoutContext originalLayoutContext,
        LayoutContext newLayoutContext
    )
    {
        AssertValid();

        const String MethodName = "TransformLayout";

        this.ArgumentChecker.CheckArgumentNotNull(MethodName, "graph", graph);

        this.ArgumentChecker.CheckArgumentNotNull(
            MethodName, "originalLayoutContext", originalLayoutContext);

        this.ArgumentChecker.CheckArgumentNotNull(
            MethodName, "newLayoutContext", newLayoutContext);

        if (graph.Vertices.Count == 0)
        {
            return;
        }

        TransformLayoutCore(graph, originalLayoutContext, newLayoutContext);
    }
    LayOutGraphAsync
    (
        IGraph graph,
        LayoutContext layoutContext
    )
    {
        AssertValid();

        const String MethodName = "LayOutGraphAsync";

        this.ArgumentChecker.CheckArgumentNotNull(MethodName, "graph", graph);

        this.ArgumentChecker.CheckArgumentNotNull(
            MethodName, "layoutContext", layoutContext);

        if (m_oBackgroundWorker.IsBusy)
        {
            throw new InvalidOperationException(String.Format(

                "{0}.{1}: A layout is already in progress."
                ,
                this.ClassName,
                MethodName
                ) );
        }

        // Start a worker thread, then return immediately.

        m_oBackgroundWorker.RunWorkerAsync(
            new LayOutGraphAsyncArguments(graph, layoutContext) );
    }
    LayOutGraph
    (
        IGraph graph,
        LayoutContext layoutContext
    )
    {
        AssertValid();

        const String MethodName = "LayOutGraph";

        this.ArgumentChecker.CheckArgumentNotNull(MethodName, "graph", graph);

        this.ArgumentChecker.CheckArgumentNotNull(MethodName, "layoutContext",
            layoutContext);

        LayOutGraphInternal(graph, layoutContext, null, null);
    }
Beispiel #16
0
    TransformLayoutCore
    (
        IGraph graph,
        LayoutContext originalLayoutContext,
        LayoutContext newLayoutContext
    )
    {
        Debug.Assert(graph != null);
        Debug.Assert(originalLayoutContext != null);
        Debug.Assert(newLayoutContext != null);
        AssertValid();

        // Transform the graph's vertex locations.

        Matrix oTransformationMatrix = LayoutUtil.GetRectangleTransformation(
            originalLayoutContext.GraphRectangle,
            newLayoutContext.GraphRectangle
            );

        base.TransformLayoutCore(graph, originalLayoutContext,
            newLayoutContext);

        // Tranform the geometry metadata added by LayOutGraphCore().

        Object oValue;

        if ( graph.TryGetValue(
            ReservedMetadataKeys.SugiyamaComputedRadius, typeof(Single),
            out oValue) )
        {
            // Transforming the radius in the x-direction only isn't ideal, but
            // doing the transform properly would involve drawing the vertex as
            // an ellipse.

            PointF oTransformedRadius = LayoutUtil.TransformPointF(
                new PointF( (Single)oValue, 0 ), oTransformationMatrix
                );

            graph.SetValue(
                ReservedMetadataKeys.SugiyamaComputedRadius,
                oTransformedRadius.X
                );
        }

        foreach (IEdge oEdge in graph.Edges)
        {
            if ( !oEdge.TryGetValue(
                ReservedMetadataKeys.SugiyamaCurvePoints, typeof( PointF [] ),
                    out oValue
                ) )
            {
                continue;
            }

            PointF [] aoCurvePoints = ( PointF [] )oValue;

            oTransformationMatrix.TransformPoints(aoCurvePoints);

            oEdge.SetValue(ReservedMetadataKeys.SugiyamaCurvePoints,
                aoCurvePoints);

            PointF oEndpoint = (PointF)oEdge.GetRequiredValue(
                ReservedMetadataKeys.SugiyamaEndpoint, typeof(PointF)
                );

            oEdge.SetValue(
                ReservedMetadataKeys.SugiyamaEndpoint,
                LayoutUtil.TransformPointF(oEndpoint, oTransformationMatrix)
                );
        }
    }
Beispiel #17
0
        LayOutGraphCoreSorted
        (
            IGraph graph,
            ICollection <IVertex> verticesToLayOut,
            LayoutContext layoutContext,
            BackgroundWorker backgroundWorker
        )
        {
            Debug.Assert(graph != null);
            Debug.Assert(verticesToLayOut != null);
            Debug.Assert(verticesToLayOut.Count > 0);
            Debug.Assert(layoutContext != null);
            AssertValid();

            if (backgroundWorker != null && backgroundWorker.CancellationPending)
            {
                return(false);
            }

            RectangleF oRectangleF = layoutContext.GraphRectangle;

            Debug.Assert(oRectangleF.Width > 0 && oRectangleF.Height > 0);

            // Get the number of rows and columns to use in the grid.

            Int32 iRows, iColumns;

            GetRowsAndColumns(verticesToLayOut, layoutContext,
                              out iRows, out iColumns);

            Debug.Assert(iRows > 0);
            Debug.Assert(iColumns > 0);
            Debug.Assert(iColumns * iRows >= verticesToLayOut.Count);

            // Get the distances between vertices;

            Double dRowSpacing    = (Double)oRectangleF.Height / (Double)iRows;
            Double dColumnSpacing = (Double)oRectangleF.Width / (Double)iColumns;

            // Set the location on each vertex.  The vertices are placed at the
            // centers of the grid boxes.

            Double dXStart = oRectangleF.Left + (dColumnSpacing / 2.0);

            Double dX = dXStart;
            Double dY = oRectangleF.Top + (dRowSpacing / 2.0);

            Int32 iColumn = 0;

            foreach (IVertex oVertex in verticesToLayOut)
            {
                if (!VertexIsLocked(oVertex))
                {
                    oVertex.Location = new PointF((Single)dX, (Single)dY);
                }

                iColumn++;

                if (iColumn >= iColumns)
                {
                    dX  = dXStart;
                    dY += dRowSpacing;

                    iColumn = 0;
                }
                else
                {
                    dX += dColumnSpacing;
                }
            }

            return(true);
        }
    LayOutGraphCore
    (
        IGraph graph,
        ICollection<IVertex> verticesToLayOut,
        LayoutContext layoutContext,
        BackgroundWorker backgroundWorker
    )
    {
        Debug.Assert(graph != null);
        Debug.Assert(verticesToLayOut != null);
        Debug.Assert(verticesToLayOut.Count > 0);
        Debug.Assert(layoutContext != null);
        AssertValid();

        if (backgroundWorker != null && backgroundWorker.CancellationPending)
        {
            return (false);
        }

        // (Do nothing.)

        return (true);
    }
Beispiel #19
0
        GetRowsAndColumns
        (
            ICollection <IVertex> oVerticesToLayOut,
            LayoutContext oLayoutContext,
            out Int32 iRows,
            out Int32 iColumns
        )
        {
            Debug.Assert(oVerticesToLayOut != null);
            Debug.Assert(oLayoutContext != null);
            AssertValid();

        #if false
            Some definitions:

            W = rectangle width

                H = rectangle height

                    A = rectangle aspect ratio = W / H

                                                 V = number of vertices in graph

                                                     R = number of grid rows

                                                         C = number of grid columns


                                                             First simulataneous equation, allowing R and C to be fractional for
now:

            R * C = V


                    Second simulataneous equation:

                    C / R = A


                            Combining these equations yields:

                            1 / 2
                            C = (V * A)
        #endif

            Int32 V = oVerticesToLayOut.Count;

            // Compute the aspect ratio.

            RectangleF oRectangleF = oLayoutContext.GraphRectangle;
            Debug.Assert(oRectangleF.Height != 0);
            Double A = oRectangleF.Width / oRectangleF.Height;

            Double C = Math.Sqrt(V * A);
            Debug.Assert(A != 0);
            Double R = C / A;

            // Try the floor/ceiling combinations.

            // C floor, R floor

            iColumns = (Int32)Math.Floor(C);
            iRows    = (Int32)Math.Floor(R);

            if (RowsAndColumnsAreSufficient(iRows, iColumns, V))
            {
                return;
            }

            // C floor, R ceiling

            iRows++;

            if (RowsAndColumnsAreSufficient(iRows, iColumns, V))
            {
                return;
            }

            // C ceiling, R floor

            iColumns = (Int32)Math.Ceiling(C);
            iRows    = (Int32)Math.Floor(R);

            if (RowsAndColumnsAreSufficient(iRows, iColumns, V))
            {
                return;
            }

            // C ceiling, R ceiling

            iRows++;

            Debug.Assert(RowsAndColumnsAreSufficient(iRows, iColumns, V));
        }
    GetRowsAndColumns
    (
        ICollection<IVertex> oVerticesToLayOut,
        LayoutContext oLayoutContext,
        out Int32 iRows,
        out Int32 iColumns
    )
    {
        Debug.Assert(oVerticesToLayOut != null);
        Debug.Assert(oLayoutContext != null);
        AssertValid();

        #if false

        Some definitions:

            W = rectangle width

            H = rectangle height

            A = rectangle aspect ratio = W / H

            V = number of vertices in graph

            R = number of grid rows

            C = number of grid columns


        First simulataneous equation, allowing R and C to be fractional for
        now:

            R * C = V


        Second simulataneous equation:

            C / R = A


        Combining these equations yields:

                       1/2
            C = (V * A)


        #endif

        Int32 V = oVerticesToLayOut.Count;

        // Compute the aspect ratio.

        RectangleF oRectangleF = oLayoutContext.GraphRectangle;
        Debug.Assert(oRectangleF.Height != 0);
        Double A = oRectangleF.Width / oRectangleF.Height;

        Double C = Math.Sqrt(V * A);
        Debug.Assert(A != 0);
        Double R = C / A;

        // Try the floor/ceiling combinations.

        // C floor, R floor

        iColumns = (Int32)Math.Floor(C);
        iRows = (Int32)Math.Floor(R);

        if ( RowsAndColumnsAreSufficient(iRows, iColumns, V) )
        {
            return;
        }

        // C floor, R ceiling

        iRows++;

        if ( RowsAndColumnsAreSufficient(iRows, iColumns, V) )
        {
            return;
        }

        // C ceiling, R floor

        iColumns = (Int32)Math.Ceiling(C);
        iRows = (Int32)Math.Floor(R);

        if ( RowsAndColumnsAreSufficient(iRows, iColumns, V) )
        {
            return;
        }

        // C ceiling, R ceiling

        iRows++;

        Debug.Assert( RowsAndColumnsAreSufficient(iRows, iColumns, V) );
    }
    //*************************************************************************
    //  Constructor: NodeXLControl()
    //
    /// <summary>
    /// Initializes a new instance of the <see cref="NodeXLControl" /> class.
    /// </summary>
    //*************************************************************************

    public NodeXLControl()
    {
        m_oGraph = new Graph();
        CreateGraphDrawer();
        m_fEdgeBundlerStraightening = 0.15F;

        m_oLayout = new FruchtermanReingoldLayout();
        OnNewLayout(m_oLayout);

        m_oLastLayoutContext =
            new LayoutContext(System.Drawing.Rectangle.Empty);

        m_oLastGraphDrawingContext = null;

        m_eLayoutState = LayoutState.Stable;

        m_eMouseMode = MouseMode.Select;
        m_bMouseAlsoSelectsIncidentEdges = true;
        m_bAllowVertexDrag = true;

        m_oVerticesBeingDragged = null;
        m_oMarqueeBeingDragged = null;
        m_oTranslationBeingDragged = null;

        m_oSelectedVertices = new HashSet<IVertex>();
        m_oSelectedEdges = new HashSet<IEdge>();
        m_oCollapsedGroups = new Dictionary<String, IVertex>();
        m_oDoubleClickedVertexInfo = null;

        m_bShowVertexToolTips = false;
        m_oLastMouseMoveLocation = new Point(-1, -1);

        // Create a helper object for displaying vertex tooltips.

        CreateVertexToolTipTracker();
        m_oVertexToolTip = null;

        m_bGraphZoomCentered = false;

        this.AddLogicalChild(m_oGraphDrawer.VisualCollection);

        CreateTransforms();

        // Prevent a focus rectangle from being drawn around the control when
        // it captures keyboard focus.  The focus rectangle does not behave
        // properly when the layout and render transforms are applied --
        // sometimes the rectangle disappears, and sometimes it gets magnified
        // by the render layout.

        this.FocusVisualStyle = null;

        // AssertValid();
    }
    TransformGroupRectangles
    (
        IGraph graph,
        LayoutContext originalLayoutContext,
        LayoutContext newLayoutContext
    )
    {
        Debug.Assert(graph != null);
        Debug.Assert(originalLayoutContext != null);
        Debug.Assert(newLayoutContext != null);

        GroupLayoutDrawingInfo oGroupLayoutDrawingInfo;

        if ( TryGetGroupLayoutDrawingInfo(graph, out oGroupLayoutDrawingInfo) )
        {
            // Replace the metadata value's group rectangles with a transformed
            // set of group rectangles.

            Matrix oTransformationMatrix =
                LayoutUtil.GetRectangleTransformation(
                    originalLayoutContext.GraphRectangle,
                    newLayoutContext.GraphRectangle
                    );

            foreach (GroupInfo oGroupInfo in
                oGroupLayoutDrawingInfo.GroupsToDraw)
            {
                oGroupInfo.Rectangle = LayoutUtil.TransformRectangle(
                    oGroupInfo.Rectangle, oTransformationMatrix);
            }
        }
    }
    LayOutOrDrawGraph()
    {
        AssertValid();

        #if TRACE_LAYOUT_AND_DRAW
        Debug.WriteLine("NodeXLControl: LayOutOrDrawGraph(), m_eLayoutState = "
            + m_eLayoutState);
        #endif

        Rect oGraphRectangle = this.GraphRectangle;

        #if TRACE_LAYOUT_AND_DRAW
        Debug.WriteLine("NodeXLControl: LayOutOrDrawGraph(), size = " +
            oGraphRectangle.Width + " , " + oGraphRectangle.Height);
        #endif

        System.Drawing.Rectangle oGraphRectangle2 =
            WpfGraphicsUtil.RectToRectangle(oGraphRectangle);

        switch (m_eLayoutState)
        {
            case LayoutState.Stable:

                break;

            case LayoutState.LayoutRequired:

                Debug.Assert(!m_oLayout.IsBusy);

                FireLayingOutGraph();

                m_oLastLayoutContext = new LayoutContext(oGraphRectangle2);

                m_eLayoutState = LayoutState.LayingOut;

                if (m_oLayout is SortableLayoutBase)
                {
                    // If the vertex layout order has been set, tell the layout
                    // object to sort the vertices before laying them out.

                    ( (SortableLayoutBase)m_oLayout ).
                        UseMetadataVertexSorter(m_oGraph);
                }

                // Start an asynchronous layout.  The m_oLayout object will
                // fire a LayOutGraphCompleted event when it is done.

                m_oLayout.LayOutGraphAsync(
                    m_oGraph, m_oLastLayoutContext);

                break;

            case LayoutState.LayingOut:

                break;

            case LayoutState.LayoutCompleted:

                // The asynchronous layout has completed and now the graph
                // needs to be drawn.

                m_eLayoutState = LayoutState.Stable;

                // Has the size of the control changed since the layout was
                // started?

                System.Drawing.Rectangle oLastGraphRectangle =
                    m_oLastLayoutContext.GraphRectangle;

                if (
                    oLastGraphRectangle.Width != oGraphRectangle2.Width
                    ||
                    oLastGraphRectangle.Height != oGraphRectangle2.Height
                    )
                {
                    // Yes.  Transform the layout to the new size.

                    #if TRACE_LAYOUT_AND_DRAW
                    Debug.WriteLine("NodeXLControl: Transforming layout.");
                    #endif

                    m_oLastLayoutContext = TransformLayout(oGraphRectangle);
                }

                DrawGraph(oGraphRectangle);

                break;

            case LayoutState.TransformRequired:

                // The control has been resized and now the graph's layout
                // needs to be transformed to the new size.

                m_oLastLayoutContext = TransformLayout(oGraphRectangle);

                m_eLayoutState = LayoutState.Stable;

                DrawGraph(oGraphRectangle);

                break;

            default:

                Debug.Assert(false);
                break;
        }
    }
    TestTransformLayoutBad()
    {
        // null graph.

        try
        {
            LayoutContext oLayoutContext = new LayoutContext(Rectangle.Empty);

            m_oFruchtermanReingoldLayout.TransformLayout(
                null, oLayoutContext, oLayoutContext);
        }
        catch (ArgumentNullException oArgumentNullException)
        {
            String enMsg="Smrf.NodeXL.Layouts.FruchtermanReingoldLayout."
                + "TransformLayout: graph argument can't be null.\r\n"
                + "Parameter name: graph";
            String chMsg="Smrf.NodeXL.Layouts.FruchtermanReingoldLayout."
                + "TransformLayout: graph argument can't be null.\r\n"
                + "參數名稱: graph";
            Assert.IsTrue((enMsg == oArgumentNullException.Message ||
                chMsg == oArgumentNullException.Message) ? true : false
                );

            throw oArgumentNullException;
        }
    }
    TransformLayout
    (
        Rect oNewGraphRectangle
    )
    {
        AssertValid();

        LayoutContext oNewLayoutContext = new LayoutContext(
            WpfGraphicsUtil.RectToRectangle(oNewGraphRectangle) );

        m_oLayout.TransformLayout(m_oGraph,
            m_oLastLayoutContext, oNewLayoutContext);

        return (oNewLayoutContext);
    }
    LayOutGraphCore
    (
        IGraph graph,
        ICollection<IVertex> verticesToLayOut,
        LayoutContext layoutContext,
        BackgroundWorker backgroundWorker
    )
    {
        Debug.Assert(graph != null);
        Debug.Assert(verticesToLayOut != null);
        Debug.Assert(verticesToLayOut.Count > 0);
        Debug.Assert(layoutContext != null);
        AssertValid();

        if (backgroundWorker != null && backgroundWorker.CancellationPending)
        {
            return (false);
        }

        base.RandomizeVertexLocations(verticesToLayOut, layoutContext,
            new Random() );

        return (true);
    }
    LayOutGraphCoreSorted
    (
        IGraph graph,
        ICollection<IVertex> verticesToLayOut,
        LayoutContext layoutContext,
        BackgroundWorker backgroundWorker
    )
    {
        Debug.Assert(graph != null);
        Debug.Assert(verticesToLayOut != null);
        Debug.Assert(verticesToLayOut.Count > 0);
        Debug.Assert(layoutContext != null);
        AssertValid();

        if (backgroundWorker != null && backgroundWorker.CancellationPending)
        {
            return (false);
        }

        Int32 iVertices = verticesToLayOut.Count;

        // The vertices are placed at equal angles along one cycle of a sine
        // wave.

        Rectangle oRectangle = layoutContext.GraphRectangle;

        Double dWidth = (Double)oRectangle.Width;
        Double dHeight = (Double)oRectangle.Height;
        Double dWidthOrHeight = (m_bIsHorizontal ? dWidth : dHeight);
        Double dHeightOrWidth = (m_bIsHorizontal ? dHeight : dWidth);

        Double dXorYIncrement = dWidthOrHeight / (Double)iVertices;
        Double dAmplitude = dHeightOrWidth / 2.0;
        Double dSinFactor = m_dCycleLength / dWidthOrHeight;

        Single fYorXOffset =
            (m_bIsHorizontal ? oRectangle.Top : oRectangle.Left) +
            (Single)dHeightOrWidth / 2.0F;

        Single fXorYOffset =
            m_bIsHorizontal ? oRectangle.Left : oRectangle.Top;

        Double dXorY = 0;

        foreach (IVertex oVertex in verticesToLayOut)
        {
            if ( !VertexIsLocked(oVertex) )
            {
                Single fYorX = fYorXOffset -
                    (Single)( dAmplitude * Math.Sin(dXorY * dSinFactor) );

                oVertex.Location = m_bIsHorizontal ?

                    new PointF( (Single)dXorY + fXorYOffset, fYorX)
                    :
                    new PointF(fYorX, (Single)dXorY + fXorYOffset);
            }

            dXorY += dXorYIncrement;
        }

        return (true);
    }
    LayOutComponentInBin
    (
        IGraph oGraph,
        ICollection<IVertex> oVerticesInComponent,
        Rectangle oBinRectangle
    )
    {
        Debug.Assert(oGraph != null);
        Debug.Assert(oVerticesInComponent != null);
        AssertValid();

        oGraph.SetValue(ReservedMetadataKeys.LayOutTheseVerticesOnly,
            oVerticesInComponent);

        // Force the FruchtermanReingoldLayout class to randomize the vertices.

        LayoutMetadataUtil.MarkGraphAsNotLaidOut(oGraph);

        ILayout oLayout = new FruchtermanReingoldLayout();
        oLayout.Margin = BinMargin;
        LayoutContext oLayoutContext = new LayoutContext(oBinRectangle);
        oLayout.LayOutGraph(oGraph, oLayoutContext);
    }
    LayOutGraphCore
    (
        IGraph graph,
        ICollection<IVertex> verticesToLayOut,
        LayoutContext layoutContext,
        BackgroundWorker backgroundWorker
    )
    {
        Debug.Assert(graph != null);
        Debug.Assert(verticesToLayOut != null);
        Debug.Assert(verticesToLayOut.Count > 0);
        Debug.Assert(layoutContext != null);
        AssertValid();

        // Sort the vertices if necessary.

        if (m_oVertexSorter != null)
        {
            verticesToLayOut = m_oVertexSorter.Sort(verticesToLayOut);
        }

        return ( LayOutGraphCoreSorted(graph, verticesToLayOut, layoutContext,
            backgroundWorker) );
    }
    TestTransformLayoutBad()
    {
        // null graph.

        try
        {
            LayoutContext oLayoutContext = new LayoutContext(Rectangle.Empty);

            m_oFruchtermanReingoldLayout.TransformLayout(
                null, oLayoutContext, oLayoutContext);
        }
        catch (ArgumentNullException oArgumentNullException)
        {
            Assert.AreEqual(

                "Smrf.NodeXL.Layouts.FruchtermanReingoldLayout."
                + "TransformLayout: graph argument can't be null.\r\n"
                + "Parameter name: graph"
                ,
                oArgumentNullException.Message
                );

            throw oArgumentNullException;
        }
    }
 LayOutGraphCoreSorted
 (
     IGraph graph,
     ICollection<IVertex> verticesToLayOut,
     LayoutContext layoutContext,
     BackgroundWorker backgroundWorker
 );
    LayOutGraphCore
    (
        IGraph graph,
        ICollection<IVertex> verticesToLayOut,
        LayoutContext layoutContext,
        BackgroundWorker backgroundWorker
    )
    {
        Debug.Assert(graph != null);
        Debug.Assert(verticesToLayOut != null);
        Debug.Assert(verticesToLayOut.Count > 0);
        Debug.Assert(layoutContext != null);
        AssertValid();

        Int32 iVertices = verticesToLayOut.Count;

        ICollection<IEdge> oEdgesToLayOut =
            GetEdgesToLayOut(graph, verticesToLayOut);

        // If the graph has already been laid out, use the current vertex
        // locations as initial values.

        if ( !LayoutMetadataUtil.GraphHasBeenLaidOut(graph) )
        {
            // The graph has not been laid out.  Randomize the locations of
            // those vertices that are not locked.

            RandomizeVertexLocations( verticesToLayOut, layoutContext,
                new Random(1) );
        }

        // Store required metadata on the graph's vertices and edges.

        InitializeMetadata(graph, verticesToLayOut);

        Rectangle oRectangle = layoutContext.GraphRectangle;

        Single fArea = oRectangle.Width * oRectangle.Height;

        Debug.Assert(iVertices > 0);

        // The algorithm in Figure 1 of the Fruchterman-Reingold paper doesn't
        // include the constant C, but it is included in the calculation for k
        // under the "Modelling the forces" section.

        Single k = m_fC * (Single)Math.Sqrt(fArea / (Single)iVertices);

        // The rectangle is guaranteed to have non-zero width and height, so
        // k should never be zero.

        Debug.Assert(k != 0);

        // Use the simple cooling algorithm suggested in the Fruchterman-
        // Reingold paper.

        Single fTemperature = oRectangle.Width / 10F;

        Debug.Assert(m_iIterations != 0);

        Single fTemperatureDecrement = fTemperature / (Single)m_iIterations;

        while (fTemperature > 0)
        {
            if (backgroundWorker != null &&
                backgroundWorker.CancellationPending)
            {
                return (false);
            }

            // Calculate the attractive and repulsive forces between the
            // vertices.  The results get written to metadata on the vertices.

            CalculateRepulsiveForces(verticesToLayOut, k);
            CalculateAttractiveForces(oEdgesToLayOut, k);

            Single fNextTemperature = fTemperature - fTemperatureDecrement;

            // Set the unbounded location of each vertex based on the vertex's
            // current location and the calculated forces.

            SetUnboundedLocations(verticesToLayOut, layoutContext,
                fTemperature, fNextTemperature > 0);

            // Decrease the temperature.

            fTemperature = fNextTemperature;
        }

        RemoveMetadata(graph, verticesToLayOut);

        return (true);
    }
    LayOutGraphCore
    (
        IGraph graph,
        ICollection<IVertex> verticesToLayOut,
        LayoutContext layoutContext,
        BackgroundWorker backgroundWorker
    )
    {
        Debug.Assert(graph != null);
        Debug.Assert(verticesToLayOut != null);
        Debug.Assert(verticesToLayOut.Count > 0);
        Debug.Assert(layoutContext != null);
        AssertValid();

        if (backgroundWorker != null && backgroundWorker.CancellationPending)
        {
            return (false);
        }

        ICollection<IEdge> oEdgesToLayOut =
            GetEdgesToLayOut(graph, verticesToLayOut);

        Int32 iVertices = verticesToLayOut.Count;

        // The MultiScaleLayout class uses a simple scheme where the graph's
        // vertices consist of the integers 0 through N-1, where N is the
        // number of vertices in the graph.  NodeXL uses IVertex objects and
        // the vertex collection isn't indexed.  Work around this
        // incompatibility by creating a dictionary that maps vertices to
        // zero-based vertex indexes.

        Dictionary<IVertex, Int32> oVertexDictionary =
            new Dictionary<IVertex, Int32>(iVertices);

        Int32 iVertexIndex = 0;

        foreach (IVertex oVertex in verticesToLayOut)
        {
            oVertexDictionary.Add(oVertex, iVertexIndex);
            iVertexIndex++;
        }

        // Create and populate a MultiScaleLayout graph.

        MultiScaleLayout.Graph oMultiScaleLayoutGraph =
            new MultiScaleLayout.Graph(iVertices, String.Empty);

        foreach (IEdge oEdge in oEdgesToLayOut)
        {
            IVertex [] aoEdgeVertices = oEdge.Vertices;

            oMultiScaleLayoutGraph.AddEdge(
                oVertexDictionary[ aoEdgeVertices[0] ],
                oVertexDictionary[ aoEdgeVertices[1] ]
                );
        }

        // Lay it out.

        oMultiScaleLayoutGraph.PrepareForUse();

        MultiScaleLayout.GraphLayoutSettings oGraphLayoutSettings =
            new MultiScaleLayout.GraphLayoutSettings(m_iRad,
                m_iLocalIterations, m_iRatio, m_iMinSize);

        oMultiScaleLayoutGraph.MultiScaleLayout(
            ( new Random() ).Next(), oGraphLayoutSettings);

        // Retrieve the laid out vertex coordinates, which are normalized to
        // fall within the range [0,1].

        MultiScaleLayout.PointD [] oMultiScaleLayoutLocations =
            oMultiScaleLayoutGraph.vertexCoords;

        Rectangle oRectangle = layoutContext.GraphRectangle;

        Debug.Assert(oRectangle.Width > 0);
        Debug.Assert(oRectangle.Height > 0);

        Int32 iLeft = oRectangle.Left;
        Int32 iTop = oRectangle.Top;
        Int32 iWidth = oRectangle.Width;
        Int32 iHeight = oRectangle.Height;

        foreach (IVertex oVertex in verticesToLayOut)
        {
            if ( !VertexIsLocked(oVertex) )
            {
                // Convert the normalized coordinates to coordinates within the
                // layout rectangle.

                MultiScaleLayout.PointD oMultiScaleLayoutLocation =
                    oMultiScaleLayoutLocations[ oVertexDictionary[oVertex] ];

                oVertex.Location = new PointF(
                    iLeft + (Single)(oMultiScaleLayoutLocation.x * iWidth),
                    iTop + (Single)(oMultiScaleLayoutLocation.y * iHeight)
                    );
            }
        }

        return (true);
    }
    LayOutGraphCore
    (
        IGraph graph,
        ICollection<IVertex> verticesToLayOut,
        LayoutContext layoutContext,
        BackgroundWorker backgroundWorker
    )
    {
        Debug.Assert(graph != null);
        Debug.Assert(verticesToLayOut != null);
        Debug.Assert(verticesToLayOut.Count > 0);
        Debug.Assert(layoutContext != null);
        AssertValid();

        if (backgroundWorker != null && backgroundWorker.CancellationPending)
        {
            return (false);
        }

        Double dCenterX, dCenterY, dHalfSize;

        GetRectangleCenterAndHalfSize(layoutContext.GraphRectangle,
            out dCenterX, out dCenterY, out dHalfSize);

        foreach (IVertex oVertex in verticesToLayOut)
        {
            if ( VertexIsLocked(oVertex) )
            {
                continue;
            }

            Double dX = dCenterX;
            Double dY = dCenterY;
            Object oSinglePolarCoordinatesAsObject;

            if ( oVertex.TryGetValue(
                ReservedMetadataKeys.PolarLayoutCoordinates,
                typeof(SinglePolarCoordinates),
                out oSinglePolarCoordinatesAsObject) )
            {
                SinglePolarCoordinates oSinglePolarCoordinates =
                    (SinglePolarCoordinates)oSinglePolarCoordinatesAsObject;

                Double dR = oSinglePolarCoordinates.R;

                if (!m_bPolarRIsAbsolute)
                {
                    dR = Math.Max(dR, 0.0);
                    dR = Math.Min(dR, 1.0) * dHalfSize;
                }

                Double dAngleRadians = -MathUtil.DegreesToRadians(
                    oSinglePolarCoordinates.Angle);

                dX = dCenterX + dR * Math.Cos(dAngleRadians);
                dY = dCenterY + dR * Math.Sin(dAngleRadians);
            }

            oVertex.Location = new PointF( (Single)dX,  (Single)dY );
        }

        return (true);
    }
        LayOutGraphCore
        (
            IGraph graph,
            ICollection <IVertex> verticesToLayOut,
            LayoutContext layoutContext,
            BackgroundWorker backgroundWorker
        )
        {
            Debug.Assert(graph != null);
            Debug.Assert(verticesToLayOut != null);
            Debug.Assert(verticesToLayOut.Count > 0);
            Debug.Assert(layoutContext != null);
            AssertValid();

            if (backgroundWorker != null && backgroundWorker.CancellationPending)
            {
                return(false);
            }

            ICollection <IEdge> oEdgesToLayOut =
                GetEdgesToLayOut(graph, verticesToLayOut);

            Int32 iVertices = verticesToLayOut.Count;

            // The MultiScaleLayout class uses a simple scheme where the graph's
            // vertices consist of the integers 0 through N-1, where N is the
            // number of vertices in the graph.  NodeXL uses IVertex objects and
            // the vertex collection isn't indexed.  Work around this
            // incompatibility by creating a dictionary that maps vertices to
            // zero-based vertex indexes.

            Dictionary <IVertex, Int32> oVertexDictionary =
                new Dictionary <IVertex, Int32>(iVertices);

            Int32 iVertexIndex = 0;

            foreach (IVertex oVertex in verticesToLayOut)
            {
                oVertexDictionary.Add(oVertex, iVertexIndex);
                iVertexIndex++;
            }

            // Create and populate a MultiScaleLayout graph.

            MultiScaleLayout.Graph oMultiScaleLayoutGraph =
                new MultiScaleLayout.Graph(iVertices, String.Empty);

            foreach (IEdge oEdge in oEdgesToLayOut)
            {
                IVertex [] aoEdgeVertices = oEdge.Vertices;

                oMultiScaleLayoutGraph.AddEdge(
                    oVertexDictionary[aoEdgeVertices[0]],
                    oVertexDictionary[aoEdgeVertices[1]]
                    );
            }

            // Lay it out.

            oMultiScaleLayoutGraph.PrepareForUse();

            MultiScaleLayout.GraphLayoutSettings oGraphLayoutSettings =
                new MultiScaleLayout.GraphLayoutSettings(m_iRad,
                                                         m_iLocalIterations, m_iRatio, m_iMinSize);

            oMultiScaleLayoutGraph.MultiScaleLayout(
                (new Random()).Next(), oGraphLayoutSettings);

            // Retrieve the laid out vertex coordinates, which are normalized to
            // fall within the range [0,1].

            MultiScaleLayout.PointD [] oMultiScaleLayoutLocations =
                oMultiScaleLayoutGraph.vertexCoords;

            Rectangle oRectangle = layoutContext.GraphRectangle;

            Debug.Assert(oRectangle.Width > 0);
            Debug.Assert(oRectangle.Height > 0);

            Int32 iLeft   = oRectangle.Left;
            Int32 iTop    = oRectangle.Top;
            Int32 iWidth  = oRectangle.Width;
            Int32 iHeight = oRectangle.Height;

            foreach (IVertex oVertex in verticesToLayOut)
            {
                if (!VertexIsLocked(oVertex))
                {
                    // Convert the normalized coordinates to coordinates within the
                    // layout rectangle.

                    MultiScaleLayout.PointD oMultiScaleLayoutLocation =
                        oMultiScaleLayoutLocations[oVertexDictionary[oVertex]];

                    oVertex.Location = new PointF(
                        iLeft + (Single)(oMultiScaleLayoutLocation.x * iWidth),
                        iTop + (Single)(oMultiScaleLayoutLocation.y * iHeight)
                        );
                }
            }

            return(true);
        }