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/ForceDirectedLayout.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: 12.9 KB
Line 
1using System;
2using System.Diagnostics;
3using Netron.Diagramming.Core.Analysis;
4using Netron.Diagramming.Core.Layout.Force;
5using System.Drawing;
6using System.Collections.Generic;
7using System.Text;
8using System.Windows.Forms;
9using System.ComponentModel;
10
11namespace Netron.Diagramming.Core
12{
13    /// <summary>
14    /// <para>Layout that positions graph elements based on a physics simulation of
15    ///  interacting forces; by default, nodes repel each other, edges act as
16    ///  springs, and drag forces (similar to air resistance) are applied. This
17    ///  algorithm can be run for multiple iterations for a run-once layout
18    ///  computation or repeatedly run in an animated fashion for a dynamic and
19    ///  interactive layout.</para>
20    /// 
21    ///  <para>The running time of this layout algorithm is the greater of O(N log N)
22    ///  and O(E), where N is the number of nodes and E the number of edges.
23    ///  The addition of custom force calculation modules may, however, increase
24    ///  this value.</para>
25    /// 
26    ///  <para>The <see cref="ForceSimulator"/> used to drive this layout
27    ///  can be set explicitly, allowing any number of custom force directed layouts
28    ///  to be created through the user's selection of included
29    ///  <see cref="Force"/> components. Each node in the layout is
30    ///  mapped to a <see cref="ForceItem"/> instance and each edge
31    ///  to a <see cref="Spring"/> instance for storing the state
32    ///  of the simulation. See the <see cref="Force"/> namespace for more.</para>
33    /// </summary>
34    class ForceDirectedLayout : LayoutBase
35    {
36        #region Fields
37        private ForceSimulator m_fsim;
38        private long m_lasttime = -1L;
39        private long m_maxstep = 50L;
40        private bool m_runonce;
41        private int m_iterations = 100;
42        private bool mEnforceBounds;
43        BackgroundWorker worker;
44        protected INode referrer;
45
46        protected String m_nodeGroup;
47        protected String m_edgeGroup;
48        private Dictionary<string, ForceItem> Pars;
49        #endregion
50
51        #region Properties
52        public long MaxTimeStep
53        {
54            get { return m_maxstep; }
55            set { m_maxstep = value; }
56        }
57
58        public ForceSimulator getForceSimulator
59        {
60            get { return m_fsim; }
61            set { m_fsim = value; }
62        }
63
64        public int Iterations
65        {
66            get { return m_iterations; }
67            set
68            {
69                if (value < 1)
70                    throw new ArgumentException("The amount of iterations has to be bigger or equal to one.");
71                m_iterations = value;
72            }
73        }
74
75
76
77        #endregion
78
79        #region Constructor
80        ///<summary>
81        ///Default constructor
82        ///</summary>
83        public ForceDirectedLayout(IController controller)
84            : base("ForceDirected Layout", controller)
85        {
86
87        }
88        #endregion
89
90        #region Methods
91        /// <summary>
92        /// Handles the DoWork event of the worker control.
93        /// </summary>
94        /// <param name="sender">The source of the event.</param>
95        /// <param name="e">The <see cref="T:System.ComponentModel.DoWorkEventArgs"/> instance containing the event data.</param>
96        private void worker_DoWork(object sender, DoWorkEventArgs e)
97        {
98            this.Controller.View.Suspend();
99            Init();
100            Layout();
101            this.Controller.View.Resume();
102        }
103        /// <summary>
104        /// Runs this instance.
105        /// </summary>
106        public override void Run()
107        {
108            Run(2000);
109        }
110
111        /// <summary>
112        /// Runs the specified time.
113        /// </summary>
114        /// <param name="time">The time.</param>
115        public override void Run(int time)
116        {
117            worker = new BackgroundWorker();
118            worker.DoWork += new DoWorkEventHandler(worker_DoWork);
119            worker.RunWorkerAsync(time);
120        }
121
122        /// <summary>
123        /// Stops this instance.
124        /// </summary>
125        public override void Stop()
126        {
127            if (worker != null && worker.IsBusy)
128                worker.CancelAsync();
129        }
130        ///<summary>
131        /// Get the mass value associated with the given node. Subclasses should
132        /// override this method to perform custom mass assignment.
133        /// @param n the node for which to compute the mass value
134        /// @return the mass value for the node. By default, all items are given
135        /// a mass value of 1.0.
136        ///</summary>
137        protected float getMassValue(INode n)
138        {
139            return 1.0f;
140        }
141
142        ///<summary>
143        /// Get the spring length for the given edge. Subclasses should
144        /// override this method to perform custom spring length assignment.
145        /// @param e the edge for which to compute the spring length
146        /// @return the spring length for the edge. A return value of
147        /// -1 means to ignore this method and use the global default.
148        ///</summary>
149        protected float getSpringLength(IEdge e)
150        {
151            return -1.0F;
152        }
153
154        ///<summary>
155        /// Get the spring coefficient for the given edge, which controls the
156        /// tension or strength of the spring. Subclasses should
157        /// override this method to perform custom spring tension assignment.
158        /// @param e the edge for which to compute the spring coefficient.
159        /// @return the spring coefficient for the edge. A return value of
160        /// -1 means to ignore this method and use the global default.
161        ///</summary>
162        protected float getSpringCoefficient(IEdge e)
163        {
164            return -1.0F;
165        }
166
167        private bool Init()
168        {
169
170            mEnforceBounds = false;
171            m_runonce = true;
172            m_fsim = new ForceSimulator();
173
174            m_fsim.AddForce(new NBodyForce());
175            m_fsim.AddForce(new SpringForce());
176            m_fsim.AddForce(new DragForce());
177
178            this.Graph = this.Model as IGraph;
179
180            if (Graph == null)
181                throw new InconsistencyException("The model has not been set and the Graph property is hence 'null'");
182
183            //Graph.ClearSpanningTree();
184            //Graph.MakeSpanningTree(LayoutRoot as INode);
185
186
187            if (Graph.Nodes.Count == 0)
188                return false;
189            if (Graph.Edges.Count == 0) //this layout is base on embedded springs in the connections
190                return false;
191
192            Pars = new Dictionary<string, ForceItem>();
193
194            foreach (INode node in Nodes)
195            {
196                Pars.Add(node.Uid.ToString(), new ForceItem());
197            }
198            return true;
199        }
200
201        /// <summary>
202        /// Updates the node positions.
203        /// </summary>
204        private void UpdateNodePositions()
205        {
206            double x1 = 0, x2 = 0, y1 = 0, y2 = 0;
207
208            if (Bounds != null)
209            {
210                x1 = Bounds.X; y1 = Bounds.Top;
211                x2 = Bounds.Right; y2 = Bounds.Bottom;
212            }
213
214            // update positions
215            foreach (INode item in Nodes)
216            {
217
218                ForceItem fitem = Pars[item.Uid.ToString()];
219                if (item.IsFixed)
220                {
221                    // clear any force computations
222                    fitem.Force[0] = 0.0f;
223                    fitem.Force[1] = 0.0f;
224                    fitem.Velocity[0] = 0.0f;
225                    fitem.Velocity[1] = 0.0f;
226
227                    if (Double.IsNaN(item.X))
228                    {
229                        setX(item, referrer, 0.0D);
230                        setY(item, referrer, 0.0D);
231                    }
232                    continue;
233                }
234
235                double x = fitem.Location[0];
236                double y = fitem.Location[1];
237                //do we need to check the bounding constraints
238                if (mEnforceBounds && Bounds != null)
239                {
240
241                    double hw = item.Rectangle.Width / 2;
242                    double hh = item.Rectangle.Height / 2;
243                    if (x + hw > x2) x = x2 - hw;
244                    if (x - hw < x1) x = x1 + hw;
245                    if (y + hh > y2) y = y2 - hh;
246                    if (y - hh < y1) y = y1 + hh;
247                }
248
249                // set the actual position
250                setX(item, referrer, x);
251                setY(item, referrer, y);
252            }
253        }
254
255        ///<summary>
256        /// Reset the force simulation state for all nodes processed by this layout.
257        ///</summary>
258        public void Reset()
259        {
260            foreach (INode item in Nodes)
261            {
262                ForceItem fitem = Pars[item.Uid.ToString()];
263                if (fitem != null)
264                {
265                    fitem.Location[0] = (float)item.X;
266                    fitem.Location[1] = (float)item.Y;
267                    fitem.Force[0] = fitem.Force[1] = 0;
268                    fitem.Velocity[0] = fitem.Velocity[1] = 0;
269                }
270            }
271            m_lasttime = -1L;
272        }
273
274        /// <summary>
275        /// Loads the simulator with all relevant force items and springs.
276        /// </summary>
277        /// <param name="fsim"> the force simulator driving this layout.</param>
278        protected void InitializeSimulator(ForceSimulator fsim)
279        {
280            //TODO: some checks here...?
281
282            float startX = (referrer == null ? 0f : (float)referrer.X);
283            float startY = (referrer == null ? 0f : (float)referrer.Y);
284            startX = float.IsNaN(startX) ? 0f : startX;
285            startY = float.IsNaN(startY) ? 0f : startY;
286            if (Nodes != null && Nodes.Count > 0)
287            {
288                foreach (INode item in Nodes)
289                {
290                    ForceItem fitem = Pars[item.Uid.ToString()];
291                    fitem.Mass = getMassValue(item);
292                    double x = item.X;
293                    double y = item.Y;
294                    fitem.Location[0] = (Double.IsNaN(x) ? startX : (float)x);
295                    fitem.Location[1] = (Double.IsNaN(y) ? startY : (float)y);
296                    fsim.addItem(fitem);
297                }
298            }
299            if (Edges != null && Edges.Count > 0)
300            {
301                foreach (IEdge e in Edges)
302                {
303                    INode n1 = e.SourceNode;
304                    if (n1 == null) continue;
305                    ForceItem f1 = Pars[n1.Uid.ToString()];
306                    INode n2 = e.TargetNode;
307                    if (n2 == null) continue;
308                    ForceItem f2 = Pars[n2.Uid.ToString()];
309                    float coeff = getSpringCoefficient(e);
310                    float slen = getSpringLength(e);
311                    fsim.addSpring(f1, f2, (coeff >= 0 ? coeff : -1.0F), (slen >= 0 ? slen : -1.0F));
312                }
313            }
314        }
315        private void Layout()
316        {
317            // perform different actions if this is a run-once or
318            // run-continuously layout
319            if (m_runonce)
320            {
321                PointF anchor = new PointF(Bounds.Width / 2F, Bounds.Height / 2F);
322                foreach (INode node in Nodes)
323                {
324                    setX(node, null, anchor.X);
325                    setY(node, null, anchor.Y);
326                }
327                m_fsim.Clear();
328                long timestep = 1000L;
329
330                InitializeSimulator(m_fsim);
331
332                for (int i = 0; i < m_iterations; i++)
333                {
334                    // use an annealing schedule to set time step
335                    timestep *= Convert.ToInt64(1.0 - i / (double)m_iterations);
336                    long step = timestep + 50;
337                    // run simulator
338                    m_fsim.RunSimulator(step);
339                    // debugging output
340                    //if (i % 10 == 0 ) {Trace.WriteLine("iter: "+i);}
341                }
342                UpdateNodePositions();
343            }
344            else
345            {
346                // get timestep
347                if (m_lasttime == -1)
348                    m_lasttime = DateTime.Now.Ticks * 10 - 20;
349                long time = DateTime.Now.Ticks * 10;//how many milliseconds since the human race started to count things
350                long timestep = Math.Min(m_maxstep, time - m_lasttime);
351                m_lasttime = time;
352
353                // run force simulator
354                m_fsim.Clear();
355                InitializeSimulator(m_fsim);
356                m_fsim.RunSimulator(timestep);
357                UpdateNodePositions();
358            }
359            //if ( frac == 1.0 ) {
360            //    reset();
361            //}
362        }
363        #endregion
364
365    }
366}
Note: See TracBrowser for help on using the repository browser.