]> git.mjollnir.org Git - moodle.git/blob
2bc92bbda06aa89dc9e41b58978673b5e5e5145c
[moodle.git] /
1 package flare.vis.operator.layout
2 {
3         import flare.animate.Transitioner;
4         import flash.geom.Rectangle;
5         import flare.vis.data.NodeSprite;
6         import flare.util.Maths;
7         import flare.util.Arrays;
8         import flash.display.Sprite;
9         import flare.vis.scale.LinearScale;
10         import flare.vis.Visualization;
11         import flare.vis.axis.CartesianAxes;
12         import flare.vis.axis.Axis;
13         import flare.vis.scale.QuantitativeScale;
14         import flare.vis.scale.Scale;
15         import flare.vis.scale.Scales;
16         import flare.util.Stats;
17         
18         /**
19          * Layout that consecutively places items on top of each other. The layout
20          * currently assumes that each column value is available as separate
21          * properties of individual DataSprites.
22          */
23         public class StackedAreaLayout extends Layout
24         {
25                 // -- Properties ------------------------------------------------------
26                 
27                 private var _columns:Array;
28         private var _baseline:Array;
29         private var _peaks:Array;
30         private var _poly:Array;
31                 
32                 private var _orient:String = Orientation.BOTTOM_TO_TOP;
33                 private var _horiz:Boolean = false;
34                 private var _top:Boolean = false;
35                 private var _initAxes:Boolean = true;
36                 
37                 private var _normalize:Boolean = false;
38                 private var _padding:Number = 0.05;
39                 private var _threshold:Number = 1.0;
40                 private var _t:Transitioner;
41                 
42                 private var _scale:QuantitativeScale = new LinearScale(0,0);
43                 
44                 /** Flag indicating if the visualization should be normalized. */               
45                 public function get normalize():Boolean { return _normalize; }
46                 public function set normalize(b:Boolean):void { _normalize = b; }
47                 
48                 /** Flag indicating the padding (as a percentage of the view)
49                  *  that should be reserved within the visualization. */
50                 public function get padding():Number { return _padding; }
51                 public function set padding(p:Number):void { _padding = p; }
52                 
53                 /** Threshold value that at least one column value must surpass for
54                  *  a stack to remain visible. */
55                 public function get threshold():Number { return _threshold; }
56                 public function set threshold(t:Number):void { _threshold = t; }
57                 
58                 /** The orientation of the layout. */
59                 public function get orientation():String { return _orient; }
60                 public function set orientation(o:String):void {
61                         _orient = o;
62                         _horiz = Orientation.isHorizontal(_orient);
63                 _top   = (_orient == Orientation.TOP_TO_BOTTOM ||
64                                   _orient == Orientation.LEFT_TO_RIGHT);
65                 initializeAxes();
66                 }
67                 
68                 /** The scale used to layout the stacked values. */
69                 public function get scale():QuantitativeScale { return _scale; }
70                 public function set scale(s:QuantitativeScale):void {
71                         _scale = s; _scale.dataMin = 0;
72                 }
73                 
74                 // -- Methods ---------------------------------------------------------
75                 
76                 /**
77                  * Creates a new StackedAreaLayout.
78                  * @param cols an ordered array of properties for the column values
79                  */             
80                 public function StackedAreaLayout(cols:Array) {
81                         _columns = Arrays.copy(cols);
82                         _baseline = new Array(cols.length);
83                         _peaks = new Array(cols.length);
84                         _poly = new Array(cols.length);
85                 }
86                 
87                 /** @inheritDoc */
88                 public override function setup():void
89                 {
90                         initializeAxes();
91                 }
92                 
93                 /**
94                  * Initializes the axes prior to layout.
95                  */
96                 protected function initializeAxes():void
97                 {
98                         if (!_initAxes || visualization==null) return;
99                         
100                         var axes:CartesianAxes = super.xyAxes;
101                         var axis1:Axis = _horiz ? axes.xAxis : axes.yAxis;
102                         var axis2:Axis = _horiz ? axes.yAxis : axes.xAxis;
103                         
104                         axis1.axisScale = _scale;
105                         axis2.showLines = false;
106                         axis2.axisScale = Scales.scale(new Stats(_columns));
107                         axis2.axisScale.flush = true;
108                 }
109                 
110                 /** @inheritDoc */
111                 public override function operate(t:Transitioner=null):void
112                 {
113                         _t = (t!=null ? t : Transitioner.DEFAULT);
114
115                 // get the orientation specifics sorted out
116                 var bounds:Rectangle = layoutBounds;
117                 var hgt:Number, wth:Number;
118                 var xbias:int, ybias:int, mult:int, len:int;
119                 hgt = (_horiz ? bounds.width : bounds.height);
120                 wth = (_horiz ? -bounds.height : bounds.width);
121                 xbias = (_horiz ? 1 : 0);
122                 ybias = (_horiz ? 0 : 1);
123                 mult = _top ? 1 : -1;
124                 len = _columns.length;
125
126                 // perform first walk to compute max values
127                 var maxValue:Number = peaks();
128                 var minX:Number = _horiz ? bounds.bottom : bounds.left;
129                 var minY:Number = _horiz ? (_top ? bounds.left : bounds.right)
130                                          : (_top ? bounds.top : bounds.bottom);
131                 Arrays.fill(_baseline, minY);
132                 _scale.dataMax = maxValue;
133                 
134                 // initialize current polygon
135                 var axes:CartesianAxes = super.xyAxes;
136                 var scale:Scale = (_horiz ? axes.yAxis : axes.xAxis).axisScale;
137                 var xx:Number;
138                 for (var j:uint=0; j<len; ++j) {
139                                 xx = minX + wth * scale.interpolate(_columns[j]);
140                     _poly[2*(len+j)+xbias] = xx;
141                     _poly[2*(len+j)+ybias] = minY;
142                     _poly[2*(len-1-j)+xbias] = xx;
143                     _poly[2*(len-1-j)+ybias] = minY;
144                 }
145                 
146                 // perform second walk to compute polygon layout
147                 visualization.data.nodes.visit(function(d:NodeSprite):void
148                 {
149                         var obj:Object = t.$(d);
150                         var height:Number = 0, i:uint;
151                         var visible:Boolean = d.visible && d.alpha>0;
152                         var filtered:Boolean = !obj.visible;
153                         
154                         // set full polygon to current baseline
155                         for (i=0; i<len; ++i) {
156                         _poly[2*(len-1-i)+ybias] = _baseline[i];
157                     }
158                     // if not visible, flatten on current baseline
159                         if (!visible || filtered) {
160                                 if (!visible || _t.immediate) d.points = Arrays.copy(_poly, d.points);
161                                 else obj.points = Arrays.copy(_poly, d.props.poly);
162                                 return;
163                         }
164                         
165                         // if visible, compute the new heights
166                     for (i=0; i<len; ++i ) {
167                         var base:int = 2*(len+i);
168                         var value:Number = d.data[_columns[i]];
169                         _baseline[i] += mult * hgt * Maths.invLinearInterp(value,0,_peaks[i]);
170                         _poly[base+ybias] = _baseline[i];
171                         height = Math.max(height, Math.abs(
172                                 _poly[2*(len-1-i)+ybias] - _poly[base+ybias]));
173                     }
174                     
175                     // if size is beneath threshold, then hide
176                     if ( height < _threshold ) {
177                         obj.visible = false;
178                     }
179                     
180                     // update data sprite layout
181                     if (d.points == null)
182                         d.points = getPolygon(d, bounds);
183                     if (d.props.poly == null)
184                         d.props.poly = Arrays.copy(_poly);
185                     obj.x = 0;
186                     obj.y = 0;
187                     obj.points = Arrays.copy(_poly, 
188                         _t.immediate ? d.points : d.props.poly);
189                 });
190                         
191                         _t = null;
192                 }
193                 
194                 private function peaks():Number
195                 {
196                         var sum:Number = 0;
197                 
198                 // first, compute max value of the current data
199                 Arrays.fill(_peaks, 0);
200                 visualization.data.nodes.visit(function(d:NodeSprite):void {
201                         if (!d.visible || d.alpha <= 0 || !_t.$(d).visible)
202                                 return;
203                         
204                         for (var i:uint=0; i<_columns.length; ++i) {
205                                 var val:Number = d.data[_columns[i]];
206                                 _peaks[i] += val;
207                                 sum += val;
208                         }
209                 });
210                 var max:Number = Arrays.max(_peaks);
211                 
212                 // update peaks array as needed
213                 // adjust peaks to include padding space
214                 if (!_normalize) {
215                         Arrays.fill(_peaks, max);
216                     for (var i:uint=0; i<_peaks.length; ++i) {
217                         _peaks[i] += _padding * _peaks[i];
218                     }
219                     max += _padding*max;
220                 }
221                 
222                 // return max range value
223                 if (_normalize) max = 1.0;
224                 if (isNaN(max)) max = 0;
225                 return max;
226                 }
227                 
228                 private function getPolygon(d:Sprite, b:Rectangle, poly:Array=null):Array
229                 {
230                         // get oriented
231                         var len:uint = _columns.length;
232                         var inc:Number = _horiz ? (b.left-b.right) : (b.bottom-b.top);
233                         inc /= len-1;
234                         var min:Number = _horiz ? (_top ? b.right : b.left)
235                                                                         : (_top ? b.top : b.bottom);
236                         var bias:int = _horiz ? 1 : 0;
237                         
238                         // create polygon, populate default values
239                         if (poly==null) poly = new Array(4*len);
240                         Arrays.fill(poly, min);
241                         for (var i:uint=0, x:Number=min; i<len; ++i, x = i*inc+min) {
242                                 poly[2*(len+1)  +bias] = x;
243                                 poly[2*(len-1-i)+bias] = x;
244                         }
245                         return poly;
246                 }
247                 
248         } // end of class StackedAreaLayout
249 }