1 | using System;
|
---|
2 | using System.Collections.Generic;
|
---|
3 | using System.Text;
|
---|
4 | using System.Drawing;
|
---|
5 | using System.Threading;
|
---|
6 | using System.ComponentModel;
|
---|
7 | using Netron.Diagramming.Core.Analysis;
|
---|
8 | namespace 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 | }
|
---|