e74bfa68037d24931019cb7f1b524fd1a3bf2e80
[moodle.git] /
1 package flare.vis.operator.layout
2 {
3         import flare.animate.Transitioner;
4         import flare.util.Property;
5         import flare.vis.data.EdgeSprite;
6         import flare.vis.data.NodeSprite;
7         
8         /**
9          * Layout that places items in a dendrogram for displaying the results of a
10          * hierarchical clustering. This class computes a dendrogram layout and sets
11          * edge control points to create "U" shaped dendrogram branches. It is
12          * common, though by no means required, to hide the node instances in a
13          * dendrogram display.
14          * 
15          * <p>To determine the height of dendrogram branches, a distance property
16          * can be provided. The values of this property will directly determine
17          * node heights by laying out the depth axis using a linear scale of
18          * distance values. The distance property should be set for all non-leaf
19          * nodes in the tree. Typically, leaf nodes have a distance of zero,
20          * resulting in an aligned list of leaf nodes.</p>
21          */
22         public class DendrogramLayout extends Layout
23         {
24                 // TODO: support axes, too
25                 
26                 private var _orient:String = Orientation.TOP_TO_BOTTOM; // the orientation of the tree
27                 private var _dp:Property;
28                 private var _t:Transitioner; // temp variable for transitioner access
29                 
30                 private var _leafCount:int;
31                 private var _leafIndex:int = 0;
32                 private var _maxDist:Number;
33                 private var _b1:Number;
34                 private var _db:Number;
35                 private var _d1:Number;
36                 private var _dd:Number;
37                 
38                 /** Data property to use as the distance field for
39                  *  determining the height values of dendrogram branches. */
40                 public function get distanceProperty():String { return _dp.name; }
41                 public function set distanceProperty(dp:String):void {
42                         _dp = Property.$(dp);
43                 }
44                 
45                 /** The orientation of the dendrogram */
46                 public function get orientation():String { return _orient; }
47                 public function set orientation(o:String):void { _orient = o; }
48                 
49                 /**
50                  * Creates a new DendrogramLayout.
51                  * @param distField data property to use as the distance field for
52                  *  determining the height values of dendrogram branches
53                  * @param orientation the orientation of the dendrogram
54                  */
55                 public function DendrogramLayout(distField:String=null,
56                         orientation:String=Orientation.TOP_TO_BOTTOM)
57                 {
58                         _dp = distField==null ? null : Property.$(distField);
59                         _orient = orientation;
60                 }
61                 
62                 /** @inheritDoc */
63                 public override function operate(t:Transitioner=null):void
64                 {
65                         _t = (t == null ? Transitioner.DEFAULT : t);
66                         init();
67                         layout(visualization.tree.root);
68                 }
69                 
70                 /**
71                  * Initialize the layout.
72                  */
73                 private function init():void
74                 {
75                         var root:NodeSprite = visualization.tree.root;
76                         _leafCount = visualization.tree.countLeaves();
77                         _leafIndex = 0;
78                         _maxDist = _dp!=null ? _dp.getValue(root) : computeHeights(root);
79                         
80                         switch (_orient) {
81                                 case Orientation.TOP_TO_BOTTOM:
82                                         _b1 = layoutBounds.left;
83                                         _db = layoutBounds.width;
84                                         _d1 = layoutBounds.bottom;
85                                         _dd = -layoutBounds.height;
86                                         break;
87                                 case Orientation.BOTTOM_TO_TOP:
88                                         _b1 = layoutBounds.left;
89                                         _db = layoutBounds.width;
90                                         _d1 = layoutBounds.top;
91                                         _dd = layoutBounds.height;
92                                         break;
93                                 case Orientation.LEFT_TO_RIGHT:
94                                         _b1 = layoutBounds.top;
95                                         _db = layoutBounds.height;
96                                         _d1 = layoutBounds.right;
97                                         _dd = -layoutBounds.width;
98                                         break;
99                                 case Orientation.RIGHT_TO_LEFT:
100                                         _b1 = layoutBounds.top;
101                                         _db = layoutBounds.height;
102                                         _d1 = layoutBounds.left;
103                                         _dd = layoutBounds.width;
104                                         break;
105                         }
106                 }
107                 
108                 private function computeHeights(n:NodeSprite):int
109                 {
110                         n.u = 0;
111                         for (var i:int=0; i<n.childDegree; ++i) {
112                                 n.u = Math.max(n.u, 1 + computeHeights(n.getChildNode(i)));
113                         }
114                         return n.u;
115                 }
116                 
117                 private function layout(n:NodeSprite):Number
118                 {
119                         var d:Number = _dp!=null ? _dp.getValue(n) : n.u;
120                         d = _d1 + _dd * (d / _maxDist);
121                         
122                         if (n.childDegree > 0) {
123                                 var b:Number = 0, bc:Number;
124                                 for (var i:int=0; i<n.childDegree; ++i) {
125                                         var c:NodeSprite = n.getChildNode(i);
126                                         b += (bc=layout(c));
127                                         layoutEdge(c.parentEdge, bc, d);
128                                 }
129                                 b /= n.childDegree;
130                         } else {
131                                 var step:Number = 1.0 / _leafCount;
132                                 b = _b1 + _db * step * (0.5 + _leafIndex++);
133                         }
134                         layoutNode(n, b, d);
135                         return b;
136                 }
137                 
138                 private function layoutNode(n:NodeSprite, b:Number, d:Number):void
139                 {
140                         var o:Object = _t.$(n);
141                         if (Orientation.isVertical(_orient)) {
142                                 o.x = b; o.y = d;
143                         } else {
144                                 o.x = d; o.y = b;
145                         }
146                 }
147                 
148                 private function layoutEdge(e:EdgeSprite, b:Number, d:Number):void
149                 {
150                         var vert:Boolean = Orientation.isVertical(_orient);
151                         var o:Object = _t.$(e);
152                         if (e.points == null) {
153                                 var s:NodeSprite = e.source;
154                                 var t:NodeSprite = e.target;
155                                 e.points = [(s.x+t.x)/2, (s.y+t.y)/2];
156                         }
157                         _t.$(e).points = vert ? [b, d] : [d, b];
158                 }
159                 
160         } // end of class DendrogramLayout
161 }