Free cookie consent management tool by TermsFeed Policy Generator

source: branches/PersistenceOverhaul/HeuristicLab.ExtLibs/HeuristicLab.Netron/3.0.2672.12446/Netron.Diagramming.Core-3.0.2672.12446/Layout/RadialTreeLayout.cs

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

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

File size: 9.1 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.ComponentModel;
4using System.Drawing;
5using Netron.Diagramming.Core.Analysis;
6
7namespace Netron.Diagramming.Core {
8  /// <summary>
9  /// <para>TreeLayout instance that computes a radial layout, laying out subsequent
10  ///  depth levels of a tree on circles of progressively increasing radius.
11  ///  </para>
12  /// 
13  ///  <para>The algorithm used is that of Ka-Ping Yee, Danyel Fisher, Rachna Dhamija,
14  ///  and Marti Hearst in their research paper
15  ///  <a href="http://citeseer.ist.psu.edu/448292.html">Animated Exploration of
16  ///  Dynamic Graphs with Radial Layout</a>, InfoVis 2001. This algorithm computes
17  ///  a radial layout which factors in possible variation in sizes, and maintains
18  ///  both orientation and ordering constraints to facilitate smooth and
19  ///  understandable transitions between layout configurations.
20  ///  </para>
21  /// </summary>
22  class RadialTreeLayout : TreeLayoutBase {
23    #region Fields
24    public static int DEFAULT_RADIUS = 550;
25    private static int MARGIN = 30;
26
27    protected int m_maxDepth = 0;
28    protected double m_radiusInc;
29    protected double m_theta1, m_theta2;
30    protected bool m_setTheta = false;
31    protected bool m_autoScale = true;
32
33    protected PointF m_origin;
34    protected INode m_prevRoot;
35    private Dictionary<string, Params> Pars;
36    BackgroundWorker worker;
37    #endregion
38
39    #region Properties
40
41
42    /// <summary>
43    /// Gets or sets the RadiusIncrement
44    /// </summary>
45    public double RadiusIncrement {
46      get { return m_radiusInc; }
47      set { m_radiusInc = value; }
48    }
49
50    public bool AutoScale {
51      get { return m_autoScale; }
52      set { m_autoScale = value; }
53    }
54
55    #endregion
56
57    #region Constructor
58    ///<summary>
59    ///Default constructor
60    ///</summary>
61    public RadialTreeLayout(IController controller)
62      : base("Radial TreeLayout", controller) {
63    }
64    private bool Init() {
65      m_radiusInc = DEFAULT_RADIUS;
66      m_prevRoot = null;
67      m_theta1 = 0;
68      m_theta2 = m_theta1 + Math.PI * 2;
69
70      this.Graph = this.Model as IGraph;
71
72      if (Graph == null)
73        throw new InconsistencyException("The model has not been set and the Graph property is hence 'null'");
74
75      this.LayoutRoot = this.Controller.Model.LayoutRoot;//could be null if not set in the GUI
76      Graph.ClearSpanningTree();
77      Graph.MakeSpanningTree(LayoutRoot as INode);
78
79      Pars = new Dictionary<string, Params>();
80      if (Graph.Nodes.Count == 0)
81        return false;
82      if (Graph.Edges.Count == 0) //this layout is base on embedded springs in the connections
83        return false;
84
85
86      Params par;
87
88      foreach (INode node in Graph.Nodes) {
89        par = new Params();
90        Pars.Add(node.Uid.ToString(), par);
91      }
92      return true;
93    }
94    #endregion
95
96    #region Methods
97    public void setAngularBounds(double theta, double width) {
98      m_theta1 = theta;
99      m_theta2 = theta + width;
100      m_setTheta = true;
101    }
102
103    private void Layout() {
104
105
106      INode n = LayoutRoot as INode;
107      Params np = Pars[n.Uid.ToString()];
108      // calc relative widths and maximum tree depth
109      // performs one pass over the tree
110      m_maxDepth = 0;
111      calcAngularWidth(n, 0);
112
113      if (m_autoScale) setScale(Bounds);
114      if (!m_setTheta) calcAngularBounds(n);
115
116      // perform the layout
117      if (m_maxDepth > 0)
118        layout(n, m_radiusInc, m_theta1, m_theta2);
119
120      // update properties of the root node
121      setX(n, null, m_origin.X);
122      setY(n, null, m_origin.Y);
123      np.angle = m_theta2 - m_theta1;
124    }
125
126    protected void setScale(RectangleF bounds) {
127      double r = Math.Min(Bounds.Width, Bounds.Height) / 2.0D;
128      if (m_maxDepth > 0)
129        m_radiusInc = 3 * (r - MARGIN) / m_maxDepth;
130    }
131    private void calcAngularBounds(INode r) {
132      if (m_prevRoot == null || r == m_prevRoot)             //|| !m_prevRoot.isValid()
133            {
134        m_prevRoot = r;
135        return;
136      }
137
138      // try to find previous parent of root
139      INode p = m_prevRoot;
140      while (true) {
141        INode pp = (INode)p.ParentNode;
142        if (pp == r) {
143          break;
144        } else if (pp == null) {
145          m_prevRoot = r;
146          return;
147        }
148        p = pp;
149      }
150
151      // compute offset due to children's angular width
152      double dt = 0;
153      CollectionBase<INode> iter = sortedChildren(r);
154      foreach (INode n in iter) {
155        if (n == p) break;
156        dt += Pars[n.Uid.ToString()].width;
157      }
158      double rw = Pars[r.Uid.ToString()].width;
159      double pw = Pars[p.Uid.ToString()].width;
160      dt = -Math.PI * 2 * (dt + pw / 2) / rw;
161
162      // set angular bounds
163      m_theta1 = dt + Math.Atan2(p.Y - r.Y, p.X - r.X);
164      m_theta2 = m_theta1 + Math.PI * 2;
165      m_prevRoot = r;
166    }
167    private double calcAngularWidth(INode n, int d) {
168      if (d > m_maxDepth) m_maxDepth = d;
169      double aw = 0;
170
171      RectangleF bounds = n.Rectangle;
172      double w = Bounds.Width, h = Bounds.Height;
173      double diameter = d == 0 ? 0 : Math.Sqrt(w * w + h * h) / d;
174
175      if (n.IsExpanded && n.ChildCount > 0) {
176
177        foreach (INode c in n.Children) {
178          aw += calcAngularWidth(c, d + 1);
179        }
180        aw = Math.Max(diameter, aw);
181      } else {
182        aw = diameter;
183      }
184      Pars[n.Uid.ToString()].width = aw;
185      return aw;
186    }
187
188    private static double normalize(double angle) {
189      while (angle > Math.PI * 2) {
190        angle -= Math.PI * 2;
191      }
192      while (angle < 0) {
193        angle += Math.PI * 2;
194      }
195      return angle;
196    }
197
198    private CollectionBase<INode> sortedChildren(INode n) {
199      double basevalue = 0;
200      // update basevalue angle for node ordering
201      INode p = n.ParentNode;
202      if (p != null) {
203        basevalue = normalize(Math.Atan2(p.Y - n.Y, p.X - n.X));
204      }
205      int cc = n.ChildCount;
206      if (cc == 0) return null;
207
208      INode c = (INode)n.FirstChild;
209
210      // TODO: this is hacky and will break when filtering
211      // how to know that a branch is newly expanded?
212      // is there an alternative property we should check?
213      //if ( !c.isStartVisible() )
214      //{
215      //    // use natural ordering for previously invisible nodes
216      //    return n.Children;
217      //}
218
219
220
221      double[] angle = new double[cc];
222      int[] idx = new int[cc];
223      for (int i = 0; i < cc; ++i, c = c.NextSibling) {
224        idx[i] = i;
225        angle[i] = normalize(-basevalue + Math.Atan2(c.Y - n.Y, c.X - n.X));
226      }
227
228      Array.Sort(angle, idx);//or is it the other way around
229      CollectionBase<INode> col = new CollectionBase<INode>();
230      CollectionBase<INode> children = n.Children;
231      for (int i = 0; i < cc; ++i) {
232        col.Add(children[idx[i]]);
233      }
234      return col;
235
236      // return iterator over sorted children
237      //return new Iterator() {
238      //    int cur = 0;
239      //    public Object next() {
240      //        return n.getChild(idx[cur++]);
241      //    }
242      //    public bool hasNext() {
243      //        return cur < idx.Length;
244      //    }
245      //    public void remove() {
246      //        throw new UnsupportedOperationException();
247      //    }
248      //};
249    }
250    protected void layout(INode n, double r, double theta1, double theta2) {
251      double dtheta = (theta2 - theta1);
252      double dtheta2 = dtheta / 2.0;
253      double width = Pars[n.Uid.ToString()].width;
254      double cfrac, nfrac = 0.0;
255      foreach (INode c in sortedChildren(n)) {
256        Params cp = Pars[c.Uid.ToString()];
257        cfrac = cp.width / width;
258        if (c.IsExpanded && c.ChildCount > 0) {
259          layout(c, r + m_radiusInc, theta1 + nfrac * dtheta, theta1 + (nfrac + cfrac) * dtheta);
260        }
261        setPolarLocation(c, n, r, theta1 + nfrac * dtheta + cfrac * dtheta2);
262        cp.angle = cfrac * dtheta;
263        nfrac += cfrac;
264      }
265
266    }
267    protected void setPolarLocation(INode n, INode p, double r, double t) {
268      setX(n, p, m_origin.X + r * Math.Cos(t));
269      setY(n, p, m_origin.Y + r * Math.Sin(t));
270    }
271    public override void Run() {
272      Run(2000);
273    }
274    public override void Run(int time) {
275      worker = new BackgroundWorker();
276      worker.DoWork += new DoWorkEventHandler(worker_DoWork);
277      worker.RunWorkerAsync(time);
278    }
279
280    public override void Stop() {
281      if (worker != null && worker.IsBusy)
282        worker.CancelAsync();
283    }
284
285    private void worker_DoWork(object sender, DoWorkEventArgs e) {
286      this.Controller.View.Suspend();
287      Init();
288      Layout();
289      this.Controller.View.Resume();
290    }
291
292
293    #endregion
294
295    /// <summary>
296    /// Paramter blob to temporarily keep working data of one node.
297    /// </summary>
298    class Params {
299      public double width = 0;
300      public double angle = 0;
301      public Object clone() {
302        Params p = new Params();
303        p.width = this.width;
304        p.angle = this.angle;
305        return p;
306      }
307    }
308
309  }
310
311}
Note: See TracBrowser for help on using the repository browser.