Free cookie consent management tool by TermsFeed Policy Generator

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

Last change on this file since 2768 was 2768, checked in by mkommend, 14 years ago

added solution folders and sources for the netron library (ticket #867)

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