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); }
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); }
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) ); } }
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); }
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); }