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/StandardTreeLayout.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: 17.7 KB
Line 
1using System;
2using System.Diagnostics;
3using Netron.Diagramming.Core.Analysis;
4using System.Drawing;
5using System.Collections.Generic;
6using System.Text;
7using System.Windows.Forms;
8using System.ComponentModel;
9namespace Netron.Diagramming.Core
10{
11    /// <summary>
12    /// <para>TreeLayout that computes a tidy layout of a node-link tree
13    ///  diagram. This algorithm lays out a rooted tree such that each
14    ///  depth level of the tree is on a shared line. The orientation of the
15    ///  tree can be set such that the tree goes left-to-right (default),
16    ///  right-to-left, top-to-bottom, or bottom-to-top.</para>
17    /// 
18    ///  <para>The algorithm used is that of Christoph Buchheim, Michael Jünger,
19    ///  and Sebastian Leipert from their research paper
20    ///  <a href="http://citeseer.ist.psu.edu/buchheim02improving.html">
21    ///  Improving Walker's Algorithm to Run in Linear Time</a>, Graph Drawing 2002.
22    ///  This algorithm corrects performance issues in Walker's algorithm, which
23    ///  generalizes Reingold and Tilford's method for tidy drawings of trees to
24    ///  support trees with an arbitrary number of children at any given node.</para>
25    /// </summary>
26    class StandardTreeLayout : TreeLayoutBase
27    {
28
29       
30
31        #region Fields
32        BackgroundWorker worker;
33        private static TreeOrientation mOrientation = TreeOrientation.TopBottom;  // the orientation of the tree
34        private static float mBreadth = 50F;   // the spacing between sibling nodes
35        private static float mSubTreeSpacing = 50F;  // the spacing between subtrees
36        private static float mDepth = 50F;  // the spacing between depth levels
37        private float m_offset = 50;  // pixel offset for root node position
38        private PointF m_anchor;
39        private PointF m_tmpa;
40        private float[] m_depths = new float[10];
41        private int mMaxDepth = 0;
42        private Dictionary<string, Params> Pars;
43        private float m_ax, m_ay; // for holding anchor co-ordinates
44        #endregion
45
46        #region Properties
47        /// <summary>
48        /// Gets or sets the orientation of the tree.
49        /// </summary>
50        /// <value>The orientation.</value>
51        public static TreeOrientation Orientation
52        {
53            get
54            {
55                return mOrientation;
56            }
57            set
58            {
59                mOrientation = value;
60            }
61        }
62        /// <summary>
63        /// Gets or sets the depth spacing.
64        /// </summary>
65        /// <value>The depth spacing.</value>
66        public static float DepthSpacing
67        {
68            get { return mDepth; }
69            set { mDepth = value; }
70        }
71
72        /// <summary>
73        /// Gets or sets the breadth spacing.
74        /// </summary>
75        /// <value>The breadth spacing.</value>
76        public static float BreadthSpacing
77        {
78            get { return mBreadth; }
79            set { mBreadth = value; }
80        }
81        /// <summary>
82        /// Gets or sets the spacing between subtrees.
83        /// </summary>
84        /// <value>The sub tree spacing.</value>
85        public static float SubTreeSpacing
86        {
87            get { return mSubTreeSpacing; }
88            set { mSubTreeSpacing = value; }
89
90        }
91
92        public float RootOffset
93        {
94            get { return m_offset; }
95            set { m_offset = value; }
96        }
97
98        #endregion
99
100        #region Constructor
101        public StandardTreeLayout(IController controller)
102            : base("Standard TreeLayout", controller)
103        {
104
105
106
107        }
108        #endregion
109
110        #region Methods
111
112        public override void Run()
113        {
114            Run(2000);
115        }
116        public override void Run(int time)
117        {
118            if (worker != null && worker.IsBusy)
119                worker.CancelAsync();               
120            worker = new BackgroundWorker();
121            worker.WorkerSupportsCancellation = true;
122            worker.DoWork += new DoWorkEventHandler(worker_DoWork);
123            worker.RunWorkerAsync(time);
124        }
125
126        public override void Stop()
127        {
128            if (worker != null && worker.IsBusy)
129                worker.CancelAsync();
130        }
131
132        private void worker_DoWork(object sender, DoWorkEventArgs e)
133        {
134            this.Controller.View.Suspend();
135            Init();
136            Layout();
137            this.Controller.View.Resume();
138        }
139        /// <summary>
140        /// Runs a single step.
141        /// </summary>
142        private void RunStep()
143        {
144
145        }
146        private bool Init()
147        {
148
149            this.Graph = this.Model as IGraph;
150
151            if (Graph == null)
152                throw new InconsistencyException("The model has not been set and the Graph property is hence 'null'");
153
154            this.LayoutRoot = this.Controller.Model.LayoutRoot;//could be null if not set in the GUI
155            Graph.ClearSpanningTree();
156            Graph.MakeSpanningTree(LayoutRoot as INode);
157
158            Trace.WriteLine((LayoutRoot as INode).ChildCount);
159
160            if (Graph.SpanningTree == null)
161                throw new InconsistencyException("The spanning tree is not set (se the root of the tree layout first)");
162
163           
164           
165
166
167            Pars = new Dictionary<string, Params>();
168            if (Graph.Nodes.Count == 0)
169                return false;
170            if (Graph.Edges.Count == 0) //this layout is base on embedded springs in the connections
171                return false;
172
173
174            Params par;
175
176            foreach (INode node in Graph.Nodes)
177            {
178                par = new Params();
179                par.init(node);
180                Pars.Add(node.Uid.ToString(), par);
181            }
182            return true;
183        }
184        public PointF LayoutAnchor
185        {
186            get
187            {
188                if (m_anchor != null)
189                {
190                    return m_anchor;
191                }
192
193                m_tmpa = PointF.Empty;
194                if (Graph != null)
195                {
196                    Rectangle b = Bounds;
197
198                    switch (mOrientation)
199                    {
200                        case TreeOrientation.LeftRight:
201                            m_tmpa = new PointF(m_offset, b.Height / 2F);
202                            break;
203                        case TreeOrientation.RightLeft:
204                            m_tmpa = new PointF(b.Width - m_offset, b.Height / 2F);
205                            break;
206                        case TreeOrientation.TopBottom:
207                            m_tmpa = new PointF(b.Width / 2F, m_offset);
208                            break;
209                        case TreeOrientation.BottomTop:
210                            m_tmpa = new PointF(b.Width / 2F, b.Height - m_offset);
211                            break;
212                        case TreeOrientation.Center:
213                            m_tmpa = new PointF(b.Width / 2F, b.Height / 2F);
214                            break;
215                        default:
216                            break;
217                    }
218                    //TODO: set the center of the layout here on the view
219                    //d.getInverseTransform().transform(m_tmpa, m_tmpa);
220                }
221                return m_tmpa;
222            }
223            set
224            {
225                m_anchor = value;
226            }
227        }
228
229        /// <summary>
230        /// Returns the location of halfway the two given nodes
231        /// </summary>
232        /// <param name="l"></param>
233        /// <param name="r"></param>
234        /// <param name="siblings"></param>
235        /// <returns></returns>
236        private double spacing(INode l, INode r, bool siblings)
237        {
238            bool w = (mOrientation == TreeOrientation.TopBottom || mOrientation == TreeOrientation.BottomTop);
239            return (siblings ? mBreadth : mSubTreeSpacing) + 0.5 * (w ? l.Rectangle.Width + r.Rectangle.Width : l.Rectangle.Height + r.Rectangle.Height);
240        }
241        private void updateDepths(int depth, INode item)
242        {
243            bool v = (mOrientation == TreeOrientation.TopBottom || mOrientation == TreeOrientation.BottomTop);
244            double d = (v ? item.Rectangle.Height : item.Rectangle.Width);
245            if (m_depths.Length <= depth)
246                Array.Resize<float>(ref m_depths, (int)(3 * depth / 2));
247            m_depths[depth] = (float)Math.Max(m_depths[depth], d);
248            mMaxDepth = (int)Math.Max(mMaxDepth, depth);
249        }
250
251        private void determineDepths()
252        {
253            for (int i = 1; i < mMaxDepth; ++i)
254                m_depths[i] += m_depths[i - 1] + mDepth;
255        }
256
257        public void Layout()
258        {
259            m_depths.Initialize();
260
261            mMaxDepth = 0;
262
263            PointF a = LayoutAnchor;
264            m_ax = a.X;
265            m_ay = a.Y;
266
267            INode root = LayoutRoot as INode;
268            Params rp = Pars[root.Uid.ToString()];
269
270            // do first pass - compute breadth information, collect depth info
271            firstWalk(root, 0, 1);
272
273            // sum up the depth info
274            determineDepths();
275
276            // do second pass - assign layout positions
277            secondWalk(root, null, -rp.prelim, 0);
278        }
279
280        private void firstWalk(INode n, int num, int depth)
281        {
282            Trace.WriteLine("depthj: " + depth);
283            Params np = Pars[n.Uid.ToString()];
284
285            np.number = num;
286            updateDepths(depth, n);
287
288            bool expanded = n.IsExpanded;
289            if (n.ChildCount == 0 || !expanded) // is leaf
290            {
291                INode l = (INode)n.PreviousSibling;
292                if (l == null)
293                {
294                    np.prelim = 0;
295                }
296                else
297                {
298                    np.prelim = Pars[l.Uid.ToString()].prelim + spacing(l, n, true);
299                }
300            }
301            else if (expanded)
302            {
303                INode leftMost = n.FirstChild;
304                INode rightMost = n.LastChild;
305                INode defaultAncestor = leftMost;
306                INode c = leftMost;
307                for (int i = 0; c != null; ++i, c = c.NextSibling)
308                {
309                    firstWalk(c, i, depth + 1);
310                    defaultAncestor = apportion(c, defaultAncestor);
311                }
312
313                executeShifts(n);
314
315                double midpoint = 0.5 *
316                    (Pars[leftMost.Uid.ToString()].prelim + Pars[rightMost.Uid.ToString()].prelim);
317
318                INode left = (INode)n.PreviousSibling;
319                if (left != null)
320                {
321                    np.prelim = Pars[left.Uid.ToString()].prelim + spacing(left, n, true);
322                    np.mod = np.prelim - midpoint;
323                }
324                else
325                {
326                    np.prelim = midpoint;
327                }
328            }
329        }
330        private INode apportion(INode v, INode a)
331        {
332            INode w = (INode)v.PreviousSibling;
333            if (w != null)
334            {
335                INode vip, vim, vop, vom;
336                double sip, sim, sop, som;
337
338                vip = vop = v;
339                vim = w;
340                vom = (INode)vip.ParentNode.FirstChild;
341
342                sip = Pars[vip.Uid.ToString()].mod;
343                sop = Pars[vop.Uid.ToString()].mod;
344                sim = Pars[vim.Uid.ToString()].mod;
345                som = Pars[vom.Uid.ToString()].mod;
346                Params parms;
347                INode nr = nextRight(vim);
348                INode nl = nextLeft(vip);
349                while (nr != null && nl != null)
350                {
351                    vim = nr;
352                    vip = nl;
353                    vom = nextLeft(vom);
354                    vop = nextRight(vop);
355                    parms = Pars[vop.Uid.ToString()];
356                    parms.ancestor = v;
357                    double shift = (Pars[vim.Uid.ToString()].prelim + sim) -
358                        (Pars[vip.Uid.ToString()].prelim + sip) + spacing(vim, vip, false);
359                    if (shift > 0)
360                    {
361                        moveSubtree(ancestor(vim, v, a), v, shift);
362                        sip += shift;
363                        sop += shift;
364                    }
365                    sim += Pars[vim.Uid.ToString()].mod;
366                    sip += Pars[vip.Uid.ToString()].mod;
367                    som += Pars[vom.Uid.ToString()].mod;
368                    sop += Pars[vop.Uid.ToString()].mod;
369
370                    nr = nextRight(vim);
371                    nl = nextLeft(vip);
372                }
373                if (nr != null && nextRight(vop) == null)
374                {
375                    Params vopp = Pars[vop.Uid.ToString()];
376                    vopp.thread = nr;
377                    vopp.mod += sim - sop;
378                }
379                if (nl != null && nextLeft(vom) == null)
380                {
381                    Params vomp = Pars[vom.Uid.ToString()];
382                    vomp.thread = nl;
383                    vomp.mod += sip - som;
384                    a = v;
385                }
386            }
387            return a;
388        }
389        private INode nextLeft(INode n)
390        {
391            INode c = null;
392            if (n.IsExpanded) c = n.FirstChild;
393            return (c != null ? c : Pars[n.Uid.ToString()].thread);
394        }
395
396        private INode nextRight(INode n)
397        {
398            INode c = null;
399            if (n.IsExpanded) c = n.LastChild;
400            return (c != null ? c : Pars[n.Uid.ToString()].thread);
401        }
402
403        private void moveSubtree(INode wm, INode wp, double shift)
404        {
405            Params wmp = Pars[wm.Uid.ToString()];
406            Params wpp = Pars[wp.Uid.ToString()];
407            double subtrees = wpp.number - wmp.number;
408            wpp.change -= shift / subtrees;
409            wpp.shift += shift;
410            wmp.change += shift / subtrees;
411            wpp.prelim += shift;
412            wpp.mod += shift;
413        }
414
415        private void executeShifts(INode n)
416        {
417            double shift = 0, change = 0;
418            for (INode c = n.LastChild; c != null; c = c.PreviousSibling)
419            {
420                Params cp = Pars[c.Uid.ToString()];
421                cp.prelim += shift;
422                cp.mod += shift;
423                change += cp.change;
424                shift += cp.shift + change;
425            }
426        }
427
428        private INode ancestor(INode vim, INode v, INode a)
429        {
430            INode p = (INode)v.ParentNode;
431            Params vimp = Pars[vim.Uid.ToString()];
432            if (vimp.ancestor != null && vimp.ancestor.ParentNode == p)
433            {
434                return vimp.ancestor;
435            }
436            else
437            {
438                return a;
439            }
440        }
441
442        private void secondWalk(INode n, INode p, double m, int depth)
443        {
444            Params np = Pars[n.Uid.ToString()];
445            setBreadth(n, p, np.prelim + m);
446            setDepth(n, p, m_depths[depth]);
447
448            if (n.IsExpanded)
449            {
450                depth += 1;
451                for (INode c = n.FirstChild; c != null; c = c.NextSibling)
452                {
453                    if (worker.CancellationPending)
454                        break;
455                    secondWalk(c, n, m + np.mod, depth);
456                }
457            }
458
459            np.clear();
460        }
461
462        private void setBreadth(INode n, INode p, double b)
463        {
464            switch (mOrientation)
465            {
466                case TreeOrientation.LeftRight:
467                case TreeOrientation.RightLeft:
468                    setY(n, p, m_ay + b);
469                    break;
470                case TreeOrientation.TopBottom:
471                case TreeOrientation.BottomTop:
472                    setX(n, p, m_ax + b);
473                    break;
474                default:
475                    throw new InconsistencyException();
476            }
477        }
478
479        private void setDepth(INode n, INode p, double d)
480        {
481            switch (mOrientation)
482            {
483                case TreeOrientation.LeftRight:
484                    setX(n, p, m_ax + d);
485                    break;
486                case TreeOrientation.RightLeft:
487                    setX(n, p, m_ax - d);
488                    break;
489                case TreeOrientation.TopBottom:
490                    setY(n, p, m_ay + d);
491                    break;
492                case TreeOrientation.BottomTop:
493                    setY(n, p, m_ay - d);
494                    break;
495                default:
496                    throw new InconsistencyException();
497            }
498        }
499
500
501        #endregion
502
503        /// <summary>
504        /// Paramter blob to temporarily keep working data of one node.
505        /// </summary>
506        class Params
507        {
508            public double prelim;
509            public double mod;
510            public double shift;
511            public double change;
512            public int number;
513            public INode ancestor;
514            public INode thread;
515
516            public void init(INode item)
517            {
518                ancestor = item;
519                number = -1;
520                ancestor = thread = null;
521            }
522
523            public void clear()
524            {
525                number = -2;
526                prelim = mod = shift = change = 0;
527                ancestor = thread = null;
528            }
529        }
530    }
531
532    public enum TreeOrientation
533    {
534        LeftRight,
535        RightLeft,
536        TopBottom,
537        BottomTop,
538        Center
539    }
540}
Note: See TracBrowser for help on using the repository browser.