Free cookie consent management tool by TermsFeed Policy Generator

source: tags/3.3.8/HeuristicLab.ExtLibs/HeuristicLab.Netron/3.0.2672.12446/Netron.Diagramming.Core-3.0.2672.12446/Layout/ForceDirectedLayout.cs @ 13398

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

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

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