Free cookie consent management tool by TermsFeed Policy Generator

source: trunk/HeuristicLab.ExtLibs/HeuristicLab.Netron/3.0.2672.12446/Netron.Diagramming.Core-3.0.2672.12446/Layout/FruchtermanReingoldLayout.cs @ 18091

Last change on this file since 18091 was 4068, checked in by swagner, 14 years ago

Sorted usings and removed unused usings in entire solution (#1094)

File size: 9.0 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.ComponentModel;
4using System.Drawing;
5using Netron.Diagramming.Core.Analysis;
6namespace Netron.Diagramming.Core {
7  /// <summary>
8  /// <para>Layout instance implementing the Fruchterman-Reingold algorithm for
9  ///  force-directed placement of graph nodes. The computational complexity of
10  ///  this algorithm is quadratic [O(n^2)] in the number of nodes, so should
11  ///  only be applied for relatively small graphs, particularly in interactive
12  ///  situations.</para>
13  /// 
14  ///  <para>This implementation was ported from the implementation in the
15  ///  <a href="http://jung.sourceforge.net/">JUNG</a> framework.</para>
16  /// </summary>
17  class FruchtermanReingoldLayout : LayoutBase {
18    #region Fields
19    private double forceConstant;
20    private double temp;
21    private int maxIter = 300;
22
23    protected int m_fidx;
24    private Random rnd;
25    private static double EPSILON = 0.000001D;
26    private static double ALPHA = 0.1D;
27
28    Dictionary<string, Params> Pars;
29    double width;
30    double height;
31    BackgroundWorker worker;
32    #endregion
33
34    #region Constructor
35    ///<summary>
36    ///Default constructor
37    ///</summary>
38    public FruchtermanReingoldLayout(IController controller)
39      : base("FruchtermanReingold Layout", controller) {
40    }
41    #endregion
42
43    #region Methods
44
45    /// <summary>
46    /// Inits the calculational state
47    /// </summary>
48    private bool Init() {
49      this.Graph = this.Model as IGraph;
50
51      if (Graph == null)
52        throw new InconsistencyException("The model has not been set and the Graph property is hence 'null'");
53
54      //this.LayoutRoot = this.Controller.Model.LayoutRoot;//could be null if not set in the GUI
55      //Graph.ClearSpanningTree();
56      //Graph.MakeSpanningTree(LayoutRoot as INode);
57
58      Pars = new Dictionary<string, Params>();
59      if (Nodes.Count == 0)
60        return false;
61      if (Edges.Count == 0) //this layout is base on embedded springs in the connections
62        return false;
63
64      width = (double)Bounds.Width;
65      height = (double)Bounds.Height;
66      rnd = new Random();
67
68
69      temp = width / 10;
70      forceConstant = 0.75 * Math.Sqrt(height * width / Nodes.Count);
71
72      // initialize node positions
73
74
75      double scaleW = ALPHA * width / 2;
76      double scaleH = ALPHA * height / 2;
77      Params par;
78      double[] loc;
79      foreach (INode node in Nodes) {
80        loc = new double[2];
81        loc[0] = Center.X + rnd.NextDouble() * scaleW;
82        loc[1] = Center.Y + rnd.NextDouble() * scaleH;
83        par = new Params(loc, new double[2]);
84        Pars.Add(node.Uid.ToString(), par);
85      }
86      return true;
87    }
88
89    /// <summary>
90    /// Runs this instance.
91    /// </summary>
92    public override void Run() {
93      width = this.Bounds.Width;
94      height = this.Bounds.Height;
95      Run(1000);
96
97
98    }
99    /// <summary>
100    /// Stops this instance.
101    /// </summary>
102    public override void Stop() {
103      if (worker != null && worker.IsBusy)
104        worker.CancelAsync();
105    }
106    /// <summary>
107    /// Runs the layout for a specified time.
108    /// </summary>
109    /// <param name="time">The time.</param>
110    public override void Run(int time) {
111      if (Init()) {
112
113        worker = new BackgroundWorker();
114        worker.DoWork += new DoWorkEventHandler(worker_DoWork);
115        worker.RunWorkerAsync(time);
116
117      }
118
119    }
120
121    /// <summary>
122    /// Handles the DoWork event of the worker control.
123    /// </summary>
124    /// <param name="sender">The source of the event.</param>
125    /// <param name="e">The <see cref="T:System.ComponentModel.DoWorkEventArgs"/> instance containing the event data.</param>
126    void worker_DoWork(object sender, DoWorkEventArgs e) {
127      //this.Controller.View.Suspend();
128      DateTime start = DateTime.Now;
129      while (DateTime.Now < start.AddMilliseconds((int)e.Argument)) {
130        RunStep();
131
132      }
133      //this.Controller.View.Resume();
134      UpdateShapes();
135
136      RunAnimation();
137    }
138
139    private void RunAnimation() {
140
141    }
142
143    /// <summary>
144    /// Runs a single step.
145    /// </summary>
146    private void RunStep() {
147
148      for (int curIter = 0; curIter < maxIter; curIter++) {
149
150        // Calculate repulsion
151        foreach (INode node in Nodes) {
152          if (node.IsFixed) continue;
153          CalculateRepulsion(node);
154        }
155
156        // Calculate attraction
157        foreach (IEdge edge in Edges) {
158          CalculateAttraction(edge);
159        }
160
161        foreach (INode node in Nodes) {
162          if (node.IsFixed) continue;
163          CalculatePositions(node);
164        }
165
166        CoolDown(curIter);
167
168
169      }
170
171
172    }
173
174    private void UpdateShapes() {
175      int x, y;
176      lock (Nodes)
177        lock (Pars) {
178          foreach (INode node in Nodes) {
179            x = Convert.ToInt32(Pars[node.Uid.ToString()].loc[0]) - node.Rectangle.X;
180            y = Convert.ToInt32(Pars[node.Uid.ToString()].loc[1]) - node.Rectangle.Y;
181            //if (node.Rectangle.X + x < width - node.Rectangle.Width - 10 && node.Rectangle.X + x > 10 && node.Rectangle.Y + y < height - node.Rectangle.Height - 10 && node.Rectangle.Y + y > 10)
182            node.MoveBy(new Point(x, y));
183          }
184        }
185    }
186
187    #region Calculations
188    public void CalculatePositions(INode n) {
189      Params np = Pars[n.Uid.ToString()];
190
191
192      double deltaLength = Math.Max(EPSILON, Math.Sqrt(np.disp[0] * np.disp[0] + np.disp[1] * np.disp[1]));
193
194      double xDisp = np.disp[0] / deltaLength * Math.Min(deltaLength, temp);
195
196      if (Double.IsNaN(xDisp)) {
197        System.Diagnostics.Trace.WriteLine("Oops, the layout resulted in a NaN problem.");
198        return;
199      }
200
201      double yDisp = np.disp[1] / deltaLength * Math.Min(deltaLength, temp);
202
203      np.loc[0] += xDisp;
204      np.loc[1] += yDisp;
205
206
207      // don't let nodes leave the display
208      double borderWidth = width / 50.0;
209      double x = np.loc[0];
210      if (x < borderWidth) {
211        x = borderWidth + rnd.NextDouble() * borderWidth * 2.0;
212      } else if (x > (width - borderWidth)) {
213        x = width - borderWidth - rnd.NextDouble() * borderWidth * 2.0;
214      }
215
216      double y = np.loc[1];
217      if (y < borderWidth) {
218        y = borderWidth + rnd.NextDouble() * borderWidth * 2.0;
219      } else if (y > (height - borderWidth)) {
220        y = height - borderWidth - rnd.NextDouble() * borderWidth * 2.0;
221      }
222
223      np.loc[0] = x;
224      np.loc[1] = y;
225    }
226
227    /// <summary>
228    /// Calculates the attraction or tension on the given edge.
229    /// </summary>
230    /// <param name="edge">The edge.</param>
231    public void CalculateAttraction(IEdge edge) {
232      INode n1, n2;
233      Params n1p = new Params();
234      Params n2p = new Params();
235      if (edge.SourceNode != null) {
236        n2 = edge.SourceNode;
237        n2p = Pars[n2.Uid.ToString()];
238      };
239      if (edge.TargetNode != null) {
240        n1 = edge.TargetNode;
241        n1p = Pars[n1.Uid.ToString()];
242      };
243
244
245
246      double xDelta = n1p.loc[0] - n2p.loc[0];
247      double yDelta = n1p.loc[1] - n2p.loc[1];
248
249      double deltaLength = Math.Max(EPSILON, Math.Sqrt(xDelta * xDelta + yDelta * yDelta));
250      double force = (deltaLength * deltaLength) / forceConstant;
251
252      if (Double.IsNaN(force)) {
253        System.Diagnostics.Trace.WriteLine("Oops, the layout resulted in a NaN problem.");
254        return;
255      }
256
257      double xDisp = (xDelta / deltaLength) * force;
258      double yDisp = (yDelta / deltaLength) * force;
259
260      n1p.disp[0] -= xDisp; n1p.disp[1] -= yDisp;
261      n2p.disp[0] += xDisp; n2p.disp[1] += yDisp;
262    }
263
264    public void CalculateRepulsion(INode node) {
265      Params np = Pars[node.Uid.ToString()];
266      np.disp[0] = 0.0; np.disp[1] = 0.0;
267
268      foreach (INode n2 in Nodes) {
269
270        Params n2p = Pars[n2.Uid.ToString()];
271        if (n2.IsFixed) continue;
272        if (node != n2) {
273          double xDelta = np.loc[0] - n2p.loc[0];
274          double yDelta = np.loc[1] - n2p.loc[1];
275
276          double deltaLength = Math.Max(EPSILON, Math.Sqrt(xDelta * xDelta + yDelta * yDelta));
277
278          double force = (forceConstant * forceConstant) / deltaLength;
279
280          if (Double.IsNaN(force)) {
281            System.Diagnostics.Trace.WriteLine("Oops, the layout resulted in a NaN problem.");
282            return;
283          }
284
285          np.disp[0] += (xDelta / deltaLength) * force;
286          np.disp[1] += (yDelta / deltaLength) * force;
287        }
288      }
289    }
290
291    private void CoolDown(int curIter) {
292      temp *= (1.0 - curIter / (double)maxIter);
293    }
294    #endregion
295
296
297    /// <summary>
298    /// Paramter blob to temporarily keep working data of one node.
299    /// </summary>
300    struct Params {
301      public double[] loc;
302      public double[] disp;
303
304      public Params(double[] loc, double[] disp) {
305        this.loc = loc;
306        this.disp = disp;
307      }
308
309
310    }
311
312
313    #endregion
314
315  }
316}
Note: See TracBrowser for help on using the repository browser.