1 package flare.vis.operator.layout
3 import flare.vis.operator.Operator;
4 import flare.vis.data.NodeSprite;
5 import flash.geom.Point;
6 import flare.animate.Transitioner;
7 import flare.util.Arrays;
10 * Layout that places nodes using a tidy layout of a node-link tree
11 * diagram. This algorithm lays out a rooted tree such that each
12 * depth level of the tree is on a shared line. The orientation of the
13 * tree can be set such that the tree goes left-to-right (default),
14 * right-to-left, top-to-bottom, or bottom-to-top.
16 * <p>The algorithm used is that of Christoph Buchheim, Michael Jünger,
17 * and Sebastian Leipert from their research paper
18 * <a href="http://citeseer.ist.psu.edu/buchheim02improving.html">
19 * Improving Walker's Algorithm to Run in Linear Time</a>, Graph Drawing 2002.
20 * This algorithm corrects performance issues in Walker's algorithm, which
21 * generalizes Reingold and Tilford's method for tidy drawings of trees to
22 * support trees with an arbitrary number of children at any given node.</p>
24 public class NodeLinkTreeLayout extends Layout
26 // -- Properties ------------------------------------------------------
28 /** Property name for storing parameters for this layout. */
29 public static const PARAMS:String = "nodeLinkTreeLayoutParams";
31 private var _orient:String = Orientation.LEFT_TO_RIGHT; // orientation
32 private var _bspace:Number = 5; // the spacing between sibling nodes
33 private var _tspace:Number = 25; // the spacing between subtrees
34 private var _dspace:Number = 50; // the spacing between depth levels
35 private var _depths:Array = new Array(20); // stores depth co-ords
36 private var _maxDepth:int = 0;
37 private var _ax:Number, _ay:Number; // for holding anchor co-ordinates
38 private var _t:Transitioner; // temp variable for transitioner access
40 /** The orientation of the layout. */
41 public function get orientation():String { return _orient; }
42 public function set orientation(o:String):void { _orient = o; }
44 /** The space between successive depth levels of the tree. */
45 public function get depthSpacing():Number { return _dspace; }
46 public function set depthSpacing(s:Number):void { _dspace = s; }
48 /** The space between siblings in the tree. */
49 public function get breadthSpacing():Number { return _bspace; }
50 public function set breadthSpacing(s:Number):void { _bspace = s; }
52 /** The space between different sub-trees. */
53 public function get subtreeSpacing():Number { return _tspace; }
54 public function set subtreeSpacing(s:Number):void { _tspace = s; }
57 // -- Methods ---------------------------------------------------------
60 * Creates a new NodeLinkTreeLayout.
61 * @param orientation the orientation of the layout
62 * @param depthSpace the space between depth levels in the tree
63 * @param breadthSpace the space between siblings in the tree
64 * @param subtreeSpace the space between different sub-trees
66 public function NodeLinkTreeLayout(
67 orientation:String=Orientation.LEFT_TO_RIGHT, depthSpace:Number=50,
68 breadthSpace:Number=5, subtreeSpace:Number=25)
70 _orient = orientation;
72 _bspace = breadthSpace;
73 _tspace = subtreeSpace;
77 public override function operate(t:Transitioner=null):void
79 _t = t!=null ? t : Transitioner.DEFAULT; // set transitioner
81 Arrays.fill(_depths, 0);
84 var a:Point = layoutAnchor;
87 var root:NodeSprite = layoutRoot as NodeSprite;
88 if (root == null) { _t = null; return; }
89 var rp:Params = params(root);
91 // do first pass - compute breadth information, collect depth info
92 firstWalk(root, 0, 1);
94 // sum up the depth info
97 // do second pass - assign layout positions
98 secondWalk(root, null, -rp.prelim, 0, true);
100 updateEdgePoints(_t);
101 _t = null; // clear transitioner reference
104 private function firstWalk(n:NodeSprite, num:int, depth:uint):void
106 var np:Params = params(n);
108 updateDepths(depth, n);
110 var expanded:Boolean = n.expanded;
111 if (n.childDegree == 0 || !expanded) // is leaf
113 var l:NodeSprite = n.prevNode;
114 np.prelim = l==null ? 0 : params(l).prelim + spacing(l,n,true);
116 else if (expanded) // has children, is expanded
118 var midpoint:Number, i:uint;
119 var lefty:NodeSprite = n.firstChildNode;
120 var right:NodeSprite = n.lastChildNode;
121 var ancestor:NodeSprite = lefty;
122 var c:NodeSprite = lefty;
124 for (i=0; c != null; ++i, c = c.nextNode) {
125 firstWalk(c, i, depth+1);
126 ancestor = apportion(c, ancestor);
129 midpoint = 0.5 * (params(lefty).prelim + params(right).prelim);
133 np.prelim = params(l).prelim + spacing(l,n,true);
134 np.mod = np.prelim - midpoint;
136 np.prelim = midpoint;
141 private function apportion(v:NodeSprite, a:NodeSprite):NodeSprite
143 var w:NodeSprite = v.prevNode;
145 var vip:NodeSprite, vim:NodeSprite, vop:NodeSprite, vom:NodeSprite;
146 var sip:Number, sim:Number, sop:Number, som:Number;
150 vom = vip.parentNode.firstChildNode;
152 sip = params(vip).mod;
153 sop = params(vop).mod;
154 sim = params(vim).mod;
155 som = params(vom).mod;
158 var nr:NodeSprite = nextRight(vim);
159 var nl:NodeSprite = nextLeft(vip);
160 while (nr != null && nl != null) {
164 vop = nextRight(vop);
165 params(vop).ancestor = v;
166 shift = (params(vim).prelim + sim) -
167 (params(vip).prelim + sip) + spacing(vim,vip,false);
170 moveSubtree(ancestor(vim,v,a), v, shift);
175 sim += params(vim).mod;
176 sip += params(vip).mod;
177 som += params(vom).mod;
178 sop += params(vop).mod;
183 if (nr != null && nextRight(vop) == null) {
184 var vopp:Params = params(vop);
186 vopp.mod += sim - sop;
188 if (nl != null && nextLeft(vom) == null) {
189 var vomp:Params = params(vom);
191 vomp.mod += sip - som;
198 private function nextLeft(n:NodeSprite):NodeSprite
200 var c:NodeSprite = null;
201 if (n.expanded) c = n.firstChildNode;
202 return (c != null ? c : params(n).thread);
205 private function nextRight(n:NodeSprite):NodeSprite
207 var c:NodeSprite = null;
208 if (n.expanded) c = n.lastChildNode;
209 return (c != null ? c : params(n).thread);
212 private function moveSubtree(wm:NodeSprite, wp:NodeSprite, shift:Number):void
214 var wmp:Params = params(wm);
215 var wpp:Params = params(wp);
216 var subtrees:Number = wpp.number - wmp.number;
217 wpp.change -= shift/subtrees;
219 wmp.change += shift/subtrees;
224 private function executeShifts(n:NodeSprite):void
226 var shift:Number = 0, change:Number = 0;
227 for (var c:NodeSprite = n.lastChildNode; c != null; c = c.prevNode)
229 var cp:Params = params(c);
233 shift += cp.shift + change;
237 private function ancestor(vim:NodeSprite, v:NodeSprite, a:NodeSprite):NodeSprite
239 var vimp:Params = params(vim);
240 var p:NodeSprite = v.parentNode;
241 return (vimp.ancestor.parentNode == p ? vimp.ancestor : a);
244 private function secondWalk(n:NodeSprite, p:NodeSprite, m:Number, depth:uint, visible:Boolean):void
247 var np:Params = params(n);
248 var o:Object = _t.$(n);
249 setBreadth(o, p, (visible ? np.prelim : 0) + m);
250 setDepth(o, p, _depths[depth]);
251 setVisibility(n, o, visible);
254 var v:Boolean = n.expanded ? visible : false;
255 var b:Number = m + (n.expanded ? np.mod : np.prelim)
257 for (var c:NodeSprite = n.firstChildNode; c!=null; c=c.nextNode)
259 secondWalk(c, n, b, depth, v);
264 private function setBreadth(n:Object, p:NodeSprite, b:Number):void
267 case Orientation.LEFT_TO_RIGHT:
268 case Orientation.RIGHT_TO_LEFT:
271 case Orientation.TOP_TO_BOTTOM:
272 case Orientation.BOTTOM_TO_TOP:
276 throw new Error("Unrecognized orientation value");
280 private function setDepth(n:Object, p:NodeSprite, d:Number):void
283 case Orientation.LEFT_TO_RIGHT:
286 case Orientation.RIGHT_TO_LEFT:
289 case Orientation.TOP_TO_BOTTOM:
292 case Orientation.BOTTOM_TO_TOP:
296 throw new Error("Unrecognized orientation value");
300 private function setVisibility(n:NodeSprite, o:Object, visible:Boolean):void
302 o.alpha = visible ? 1.0 : 0.0;
303 o.mouseEnabled = visible;
304 if (n.parentEdge != null) {
305 o = _t.$(n.parentEdge);
306 o.alpha = visible ? 1.0 : 0.0;
307 o.mouseEnabled = visible;
312 private function spacing(l:NodeSprite, r:NodeSprite, siblings:Boolean):Number
314 var w:Boolean = Orientation.isVertical(_orient);
315 return (siblings ? _bspace : _tspace) + 0.5 *
316 (w ? l.width + r.width : l.height + r.height)
319 private function updateDepths(depth:uint, item:NodeSprite):void
321 var v:Boolean = Orientation.isVertical(_orient);
322 var d:Number = v ? item.height : item.width;
325 if (depth >= _depths.length) {
326 _depths = Arrays.copy(_depths, new Array(int(1.5*depth)));
327 for (var i:int=depth; i<_depths.length; ++i) _depths[i] = 0;
330 _depths[depth] = Math.max(_depths[depth], d);
331 _maxDepth = Math.max(_maxDepth, depth);
334 private function determineDepths():void
336 for (var i:uint=1; i<_maxDepth; ++i)
337 _depths[i] += _depths[i-1] + _dspace;
340 // -- Parameter Access ------------------------------------------------
342 private function params(n:NodeSprite):Params
344 var p:Params = n.props[PARAMS] as Params;
349 if (p.number == -2) { p.init(n); }
353 } // end of class NodeLinkTreeLayout
358 import flare.vis.data.NodeSprite;
361 public var prelim:Number = 0;
362 public var mod:Number = 0;
363 public var shift:Number = 0;
364 public var change:Number = 0;
365 public var number:int = -2;
366 public var ancestor:NodeSprite = null;
367 public var thread:NodeSprite = null;
369 public function init(item:NodeSprite):void
375 public function clear():void
378 prelim = mod = shift = change = 0;
379 ancestor = thread = null;
381 } // end of class Params