1package com.yahoo.astra.fl.charts.series 2{ 3 import com.yahoo.astra.animation.Animation; 4 import com.yahoo.astra.animation.AnimationEvent; 5 import com.yahoo.astra.fl.charts.*; 6 import com.yahoo.astra.fl.charts.axes.IAxis; 7 import com.yahoo.astra.fl.charts.axes.IClusteringAxis; 8 import com.yahoo.astra.fl.charts.axes.IOriginAxis; 9 import com.yahoo.astra.fl.charts.skins.RectangleSkin; 10 import com.yahoo.astra.fl.utils.UIComponentUtil; 11 12 import fl.core.UIComponent; 13 14 import flash.display.DisplayObject; 15 import flash.geom.Point; 16 17 /** 18 * Renders data points as a series of horizontal bars. 19 * 20 * @author Josh Tynjala 21 */ 22 public class BarSeries extends CartesianSeries 23 { 24 25 //-------------------------------------- 26 // Class Variables 27 //-------------------------------------- 28 29 /** 30 * @private 31 */ 32 private static var defaultStyles:Object = 33 { 34 markerSkin: RectangleSkin, 35 markerSize: 18 36 }; 37 38 //-------------------------------------- 39 // Class Methods 40 //-------------------------------------- 41 42 /** 43 * @private 44 * @copy fl.core.UIComponent#getStyleDefinition() 45 */ 46 public static function getStyleDefinition():Object 47 { 48 return mergeStyles(defaultStyles, CartesianSeries.getStyleDefinition()); 49 } 50 51 52 //-------------------------------------- 53 // Constructor 54 //-------------------------------------- 55 56 /** 57 * Constructor. 58 */ 59 public function BarSeries(data:Object = null) 60 { 61 super(data); 62 } 63 64 //-------------------------------------- 65 // Properties 66 //-------------------------------------- 67 68 /** 69 * @private 70 * The Animation instance that controls animation in this series. 71 */ 72 private var _animation:Animation; 73 74 //-------------------------------------- 75 // Public Methods 76 //-------------------------------------- 77 78 /** 79 * @inheritDoc 80 */ 81 override public function clone():ISeries 82 { 83 var series:BarSeries = new BarSeries(); 84 if(this.dataProvider is Array) 85 { 86 //copy the array rather than pass it by reference 87 series.dataProvider = (this.dataProvider as Array).concat(); 88 } 89 else if(this.dataProvider is XMLList) 90 { 91 series.dataProvider = (this.dataProvider as XMLList).copy(); 92 } 93 series.displayName = this.displayName; 94 series.horizontalField = this.horizontalField; 95 series.verticalField = this.verticalField; 96 97 return series; 98 } 99 100 //-------------------------------------- 101 // Protected Methods 102 //-------------------------------------- 103 104 /** 105 * @private 106 */ 107 override protected function draw():void 108 { 109 super.draw(); 110 111 this.graphics.clear(); 112 113 //if we don't have data, let's get out of here 114 if(!this.dataProvider) 115 { 116 return; 117 } 118 119 this.graphics.lineStyle(1, 0x0000ff); 120 121 //grab the axes 122 var cartesianChart:CartesianChart = this.chart as CartesianChart; 123 var xAxis:String = this.axis == "primary" ? "horizontalAxis" : "secondaryHorizontalAxis"; 124 var valueAxis:IOriginAxis = cartesianChart[xAxis] as IOriginAxis; 125 var otherAxis:IAxis = cartesianChart.verticalAxis; 126 if(!valueAxis) 127 { 128 throw new Error("To use a BarSeries object, the horizontal axis of the chart it appears within must be an IOriginAxis."); 129 return; 130 } 131 132 var markerSizes:Array = []; 133 var allSeriesOfType:Array = ChartUtil.findSeriesOfType(this, this.chart as IChart); 134 var totalMarkerSize:Number = this.calculateTotalMarkerSize(otherAxis, markerSizes); 135 var seriesIndex:int = allSeriesOfType.indexOf(this); 136 var markerSize:Number = markerSizes[seriesIndex] as Number; 137 var yOffset:Number = this.calculateYOffset(valueAxis, otherAxis, markerSizes, totalMarkerSize, allSeriesOfType); 138 var seriesItemSpacing:Number = UIComponentUtil.getStyleValue(UIComponent(this.chart), "seriesItemSpacing") as Number; 139 140 var startValues:Array = []; 141 var endValues:Array = []; 142 var itemCount:int = this.length; 143 for(var i:int = 0; i < itemCount; i++) 144 { 145 var originValue:Object = this.calculateOriginValue(i, valueAxis, allSeriesOfType); 146 var originPosition:Number = valueAxis.valueToLocal(originValue); 147 148 var position:Point = IChart(this.chart).itemToPosition(this, i); 149 var marker:DisplayObject = this.markers[i] as DisplayObject; 150 position.y += (allSeriesOfType.length - 1) * seriesItemSpacing; 151 152 marker.y = position.y + yOffset; 153 marker.height = markerSize; 154 155 //if we have a bad position, don't display the marker 156 if(isNaN(position.x) || isNaN(position.y)) 157 { 158 this.invalidateMarker(ISeriesItemRenderer(marker)); 159 } 160 else if(this.isMarkerInvalid(ISeriesItemRenderer(marker))) 161 { 162 //initialize the marker to the origin 163 marker.x = originPosition; 164 marker.width = 0; 165 166 if(marker is UIComponent) 167 { 168 (marker as UIComponent).drawNow(); 169 } 170 this.validateMarker(ISeriesItemRenderer(marker)); 171 } 172 173 //stupid Flash UIComponent rounding! 174 position.x = Math.round(position.x); 175 originPosition = Math.round(originPosition); 176 177 var calculatedWidth:Number = originPosition - position.x; 178 if(calculatedWidth < 0) 179 { 180 calculatedWidth = Math.abs(calculatedWidth); 181 position.x = Math.round(originPosition); 182 //always put the marker on the origin 183 marker.x = position.x; 184 } 185 186 startValues.push(marker.x, marker.width); 187 endValues.push(position.x, calculatedWidth); 188 } 189 190 //handle animating all the markers in one fell swoop. 191 if(this._animation) 192 { 193 this._animation.removeEventListener(AnimationEvent.UPDATE, tweenUpdateHandler); 194 this._animation.removeEventListener(AnimationEvent.COMPLETE, tweenUpdateHandler); 195 this._animation = null; 196 } 197 198 //don't animate on livepreview! 199 if(this.isLivePreview || !this.getStyleValue("animationEnabled")) 200 { 201 this.drawMarkers(endValues); 202 } 203 else 204 { 205 var animationDuration:int = this.getStyleValue("animationDuration") as int; 206 var animationEasingFunction:Function = this.getStyleValue("animationEasingFunction") as Function; 207 208 this._animation = new Animation(animationDuration, startValues, endValues); 209 this._animation.addEventListener(AnimationEvent.UPDATE, tweenUpdateHandler); 210 this._animation.addEventListener(AnimationEvent.COMPLETE, tweenUpdateHandler); 211 this._animation.easingFunction = animationEasingFunction; 212 } 213 } 214 215 /** 216 * @private 217 * Determines the maximum possible marker size for the containing chart. 218 */ 219 protected function calculateMaximumAllowedMarkerSize(axis:IAxis):Number 220 { 221 var seriesItemSpacing:Number = UIComponentUtil.getStyleValue(UIComponent(this.chart), "seriesItemSpacing") as Number; 222 if(axis is IClusteringAxis) 223 { 224 var allSeriesOfType:Array = ChartUtil.findSeriesOfType(this, this.chart as IChart); 225 var availableHeight:Number = this.height - (IClusteringAxis(axis).clusterCount * seriesItemSpacing *(allSeriesOfType.length - 1)); 226 return (availableHeight / IClusteringAxis(axis).clusterCount) / allSeriesOfType.length; 227 } 228 return Number.POSITIVE_INFINITY; 229 } 230 231 /** 232 * @private 233 * Determines the marker size for a series. 234 */ 235 protected function calculateMarkerSize(series:ISeries, axis:IAxis):Number 236 { 237 var markerSize:Number = UIComponentUtil.getStyleValue(UIComponent(series), "markerSize") as Number; 238 var maximumAllowedMarkerSize:Number = this.calculateMaximumAllowedMarkerSize(axis); 239 markerSize = Math.min(maximumAllowedMarkerSize, markerSize); 240 241 //we need to use floor because CS3 UIComponents round the position 242 markerSize = Math.floor(markerSize); 243 return markerSize; 244 } 245 246 /** 247 * @private 248 * Calculates the sum of the chart's series marker sizes. 249 */ 250 protected function calculateTotalMarkerSize(axis:IAxis, sizes:Array):Number 251 { 252 var totalMarkerSize:Number = 0; 253 var allSeriesOfType:Array = ChartUtil.findSeriesOfType(this, this.chart as IChart); 254 var seriesCount:int = allSeriesOfType.length; 255 var seriesItemSpacing:Number = UIComponentUtil.getStyleValue(UIComponent(this.chart), "seriesItemSpacing") as Number; 256 for(var i:int = 0; i < seriesCount; i++) 257 { 258 var series:BarSeries = BarSeries(allSeriesOfType[i]); 259 var markerSize:Number = this.calculateMarkerSize(series, axis); 260 sizes.push(markerSize); 261 if(axis is IClusteringAxis) 262 { 263 totalMarkerSize += markerSize; 264 } 265 else 266 { 267 totalMarkerSize = Math.max(totalMarkerSize, markerSize); 268 } 269 } 270 totalMarkerSize += seriesItemSpacing * (seriesCount-1); 271 return totalMarkerSize; 272 } 273 274 /** 275 * @private 276 * Calculates the y offset caused by clustering. 277 */ 278 protected function calculateYOffset(valueAxis:IOriginAxis, otherAxis:IAxis, markerSizes:Array, totalMarkerSize:Number, allSeriesOfType:Array):Number 279 { 280 var seriesIndex:int = allSeriesOfType.indexOf(this); 281 var seriesItemSpacing:Number = UIComponentUtil.getStyleValue(UIComponent(this.chart), "seriesItemSpacing") as Number; 282 var seriesCount:int = allSeriesOfType.length; 283 //special case for axes that allow clustering 284 if(otherAxis is IClusteringAxis) 285 { 286 var yOffset:Number = 0; 287 for(var i:int = 0; i < seriesIndex; i++) 288 { 289 yOffset += markerSizes[i] as Number; 290 } 291 yOffset -= (markerSizes.length - (i+1)) * seriesItemSpacing; 292 //center based on the sum of all marker sizes 293 return -(totalMarkerSize / 2) + yOffset; 294 } 295 //center based on the marker size of this series 296 return -(markerSizes[seriesIndex] as Number) / 2; 297 } 298 299 /** 300 * @private 301 * Determines the origin of the column. Either the axis origin or the 302 * stacked value. 303 */ 304 protected function calculateOriginValue(index:int, axis:IOriginAxis, allSeriesOfType:Array):Object 305 { 306 return axis.origin; 307 } 308 309 //-------------------------------------- 310 // Private Methods 311 //-------------------------------------- 312 313 /** 314 * @private 315 * Draws the markers. Used with animation. 316 */ 317 private function drawMarkers(data:Array):void 318 { 319 var itemCount:int = this.length; 320 for(var i:int = 0; i < itemCount; i++) 321 { 322 var marker:DisplayObject = this.markers[i] as DisplayObject; 323 var markerX:Number = data[i * 2]; 324 var markerWidth:Number = data[i * 2 + 1]; 325 marker.x = markerX; 326 marker.width = markerWidth; 327 328 if(marker is UIComponent) 329 { 330 UIComponent(marker).drawNow(); 331 } 332 } 333 } 334 335 //-------------------------------------- 336 // Private Event Handlers 337 //-------------------------------------- 338 339 /** 340 * @private 341 * Draws the markers every time the tween updates. 342 */ 343 private function tweenUpdateHandler(event:AnimationEvent):void 344 { 345 var data:Array = event.parameters as Array; 346 this.drawMarkers(data); 347 } 348 349 } 350}