1 package flare.vis.operator.layout
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;
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.
23 public class StackedAreaLayout extends Layout
25 // -- Properties ------------------------------------------------------
27 private var _columns:Array;
28 private var _baseline:Array;
29 private var _peaks:Array;
30 private var _poly:Array;
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;
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;
42 private var _scale:QuantitativeScale = new LinearScale(0,0);
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; }
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; }
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; }
58 /** The orientation of the layout. */
59 public function get orientation():String { return _orient; }
60 public function set orientation(o:String):void {
62 _horiz = Orientation.isHorizontal(_orient);
63 _top = (_orient == Orientation.TOP_TO_BOTTOM ||
64 _orient == Orientation.LEFT_TO_RIGHT);
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;
74 // -- Methods ---------------------------------------------------------
77 * Creates a new StackedAreaLayout.
78 * @param cols an ordered array of properties for the column values
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);
88 public override function setup():void
94 * Initializes the axes prior to layout.
96 protected function initializeAxes():void
98 if (!_initAxes || visualization==null) return;
100 var axes:CartesianAxes = super.xyAxes;
101 var axis1:Axis = _horiz ? axes.xAxis : axes.yAxis;
102 var axis2:Axis = _horiz ? axes.yAxis : axes.xAxis;
104 axis1.axisScale = _scale;
105 axis2.showLines = false;
106 axis2.axisScale = Scales.scale(new Stats(_columns));
107 axis2.axisScale.flush = true;
111 public override function operate(t:Transitioner=null):void
113 _t = (t!=null ? t : Transitioner.DEFAULT);
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;
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;
134 // initialize current polygon
135 var axes:CartesianAxes = super.xyAxes;
136 var scale:Scale = (_horiz ? axes.yAxis : axes.xAxis).axisScale;
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;
146 // perform second walk to compute polygon layout
147 visualization.data.nodes.visit(function(d:NodeSprite):void
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;
154 // set full polygon to current baseline
155 for (i=0; i<len; ++i) {
156 _poly[2*(len-1-i)+ybias] = _baseline[i];
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);
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]));
175 // if size is beneath threshold, then hide
176 if ( height < _threshold ) {
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);
187 obj.points = Arrays.copy(_poly,
188 _t.immediate ? d.points : d.props.poly);
194 private function peaks():Number
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)
204 for (var i:uint=0; i<_columns.length; ++i) {
205 var val:Number = d.data[_columns[i]];
210 var max:Number = Arrays.max(_peaks);
212 // update peaks array as needed
213 // adjust peaks to include padding space
215 Arrays.fill(_peaks, max);
216 for (var i:uint=0; i<_peaks.length; ++i) {
217 _peaks[i] += _padding * _peaks[i];
222 // return max range value
223 if (_normalize) max = 1.0;
224 if (isNaN(max)) max = 0;
228 private function getPolygon(d:Sprite, b:Rectangle, poly:Array=null):Array
231 var len:uint = _columns.length;
232 var inc:Number = _horiz ? (b.left-b.right) : (b.bottom-b.top);
234 var min:Number = _horiz ? (_top ? b.right : b.left)
235 : (_top ? b.top : b.bottom);
236 var bias:int = _horiz ? 1 : 0;
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;
248 } // end of class StackedAreaLayout