/* Copyright (c) 2009 Yahoo! Inc. All rights reserved. The copyrights embodied in the content of this file are licensed under the BSD (revised) open source license */ package com.yahoo.astra.fl.charts.series { import com.yahoo.astra.animation.Animation; import com.yahoo.astra.animation.AnimationEvent; import com.yahoo.astra.fl.charts.*; import com.yahoo.astra.fl.charts.axes.IAxis; import com.yahoo.astra.fl.charts.axes.IClusteringAxis; import com.yahoo.astra.fl.charts.axes.IOriginAxis; import com.yahoo.astra.fl.charts.skins.RectangleSkin; import com.yahoo.astra.fl.utils.UIComponentUtil; import fl.core.UIComponent; import flash.display.DisplayObject; import flash.geom.Point; /** * Renders data points as a series of horizontal bars. * * @author Josh Tynjala */ public class BarSeries extends CartesianSeries { //-------------------------------------- // Class Variables //-------------------------------------- /** * @private */ private static var defaultStyles:Object = { markerSkin: RectangleSkin, markerSize: 18 }; //-------------------------------------- // Class Methods //-------------------------------------- /** * @copy fl.core.UIComponent#getStyleDefinition() */ public static function getStyleDefinition():Object { return mergeStyles(defaultStyles, Series.getStyleDefinition()); } //-------------------------------------- // Constructor //-------------------------------------- /** * Constructor. */ public function BarSeries(data:Object = null) { super(data); } //-------------------------------------- // Properties //-------------------------------------- /** * @private * The Animation instance that controls animation in this series. */ private var _animation:Animation; //-------------------------------------- // Public Methods //-------------------------------------- /** * @inheritDoc */ override public function clone():ISeries { var series:BarSeries = new BarSeries(); if(this.dataProvider is Array) { //copy the array rather than pass it by reference series.dataProvider = (this.dataProvider as Array).concat(); } else if(this.dataProvider is XMLList) { series.dataProvider = (this.dataProvider as XMLList).copy(); } series.displayName = this.displayName; series.horizontalField = this.horizontalField; series.verticalField = this.verticalField; return series; } //-------------------------------------- // Protected Methods //-------------------------------------- /** * @private */ override protected function draw():void { super.draw(); this.graphics.clear(); //if we don't have data, let's get out of here if(!this.dataProvider) { return; } this.graphics.lineStyle(1, 0x0000ff); //grab the axes var cartesianChart:CartesianChart = this.chart as CartesianChart; var valueAxis:IOriginAxis = cartesianChart.horizontalAxis as IOriginAxis; var otherAxis:IAxis = cartesianChart.verticalAxis; if(!valueAxis) { throw new Error("To use a BarSeries object, the horizontal axis of the chart it appears within must be an IOriginAxis."); return; } var markerSizes:Array = []; var allSeriesOfType:Array = ChartUtil.findSeriesOfType(this, this.chart as IChart); var totalMarkerSize:Number = this.calculateTotalMarkerSize(otherAxis, markerSizes); var seriesIndex:int = allSeriesOfType.indexOf(this); var markerSize:Number = markerSizes[seriesIndex] as Number; var yOffset:Number = this.calculateYOffset(valueAxis, otherAxis, markerSizes, totalMarkerSize, allSeriesOfType); var startValues:Array = []; var endValues:Array = []; var itemCount:int = this.length; for(var i:int = 0; i < itemCount; i++) { var originValue:Object = this.calculateOriginValue(i, valueAxis, allSeriesOfType); var originPosition:Number = valueAxis.valueToLocal(originValue); var position:Point = IChart(this.chart).itemToPosition(this, i); var marker:DisplayObject = this.markers[i] as DisplayObject; marker.y = position.y + yOffset; marker.height = markerSize; //if we have a bad position, don't display the marker if(isNaN(position.x) || isNaN(position.y)) { this.invalidateMarker(ISeriesItemRenderer(marker)); } else if(this.isMarkerInvalid(ISeriesItemRenderer(marker))) { //initialize the marker to the origin marker.x = originPosition; marker.width = 0; if(marker is UIComponent) { (marker as UIComponent).drawNow(); } this.validateMarker(ISeriesItemRenderer(marker)); } //stupid Flash UIComponent rounding! position.x = Math.round(position.x); originPosition = Math.round(originPosition); var calculatedWidth:Number = originPosition - position.x; if(calculatedWidth < 0) { calculatedWidth = Math.abs(calculatedWidth); position.x = Math.round(originPosition); //always put the marker on the origin marker.x = position.x; } startValues.push(marker.x, marker.width); endValues.push(position.x, calculatedWidth); } //handle animating all the markers in one fell swoop. if(this._animation) { this._animation.removeEventListener(AnimationEvent.UPDATE, tweenUpdateHandler); this._animation.removeEventListener(AnimationEvent.COMPLETE, tweenUpdateHandler); this._animation = null; } //don't animate on livepreview! if(this.isLivePreview || !this.getStyleValue("animationEnabled")) { this.drawMarkers(endValues); } else { var animationDuration:int = this.getStyleValue("animationDuration") as int; var animationEasingFunction:Function = this.getStyleValue("animationEasingFunction") as Function; this._animation = new Animation(animationDuration, startValues, endValues); this._animation.addEventListener(AnimationEvent.UPDATE, tweenUpdateHandler); this._animation.addEventListener(AnimationEvent.COMPLETE, tweenUpdateHandler); this._animation.easingFunction = animationEasingFunction; } } /** * @private * Determines the maximum possible marker size for the containing chart. */ protected function calculateMaximumAllowedMarkerSize(axis:IAxis):Number { if(axis is IClusteringAxis) { var allSeriesOfType:Array = ChartUtil.findSeriesOfType(this, this.chart as IChart); return (this.height / IClusteringAxis(axis).clusterCount) / allSeriesOfType.length; } return Number.POSITIVE_INFINITY; } /** * @private * Determines the marker size for a series. */ protected function calculateMarkerSize(series:ISeries, axis:IAxis):Number { var markerSize:Number = UIComponentUtil.getStyleValue(UIComponent(series), "markerSize") as Number; var maximumAllowedMarkerSize:Number = this.calculateMaximumAllowedMarkerSize(axis); markerSize = Math.min(maximumAllowedMarkerSize, markerSize); //we need to use floor because CS3 UIComponents round the position markerSize = Math.floor(markerSize); return markerSize; } /** * @private * Calculates the sum of the chart's series marker sizes. */ protected function calculateTotalMarkerSize(axis:IAxis, sizes:Array):Number { var totalMarkerSize:Number = 0; var allSeriesOfType:Array = ChartUtil.findSeriesOfType(this, this.chart as IChart); var seriesCount:int = allSeriesOfType.length; for(var i:int = 0; i < seriesCount; i++) { var series:BarSeries = BarSeries(allSeriesOfType[i]); var markerSize:Number = this.calculateMarkerSize(series, axis); sizes.push(markerSize); if(axis is IClusteringAxis) { totalMarkerSize += markerSize; } else { totalMarkerSize = Math.max(totalMarkerSize, markerSize); } } return totalMarkerSize; } /** * @private * Calculates the y offset caused by clustering. */ protected function calculateYOffset(valueAxis:IOriginAxis, otherAxis:IAxis, markerSizes:Array, totalMarkerSize:Number, allSeriesOfType:Array):Number { var seriesIndex:int = allSeriesOfType.indexOf(this); //special case for axes that allow clustering if(otherAxis is IClusteringAxis) { var yOffset:Number = 0; for(var i:int = 0; i < seriesIndex; i++) { yOffset += markerSizes[i] as Number; } //center based on the sum of all marker sizes return -(totalMarkerSize / 2) + yOffset; } //center based on the marker size of this series return -(markerSizes[seriesIndex] as Number) / 2; } /** * @private * Determines the origin of the column. Either the axis origin or the * stacked value. */ protected function calculateOriginValue(index:int, axis:IOriginAxis, allSeriesOfType:Array):Object { return axis.origin; } //-------------------------------------- // Private Methods //-------------------------------------- /** * @private * Draws the markers. Used with animation. */ private function drawMarkers(data:Array):void { var itemCount:int = this.length; for(var i:int = 0; i < itemCount; i++) { var marker:DisplayObject = this.markers[i] as DisplayObject; var markerX:Number = data[i * 2]; var markerWidth:Number = data[i * 2 + 1]; marker.x = markerX; marker.width = markerWidth; if(marker is UIComponent) { UIComponent(marker).drawNow(); } } } //-------------------------------------- // Private Event Handlers //-------------------------------------- /** * @private * Draws the markers every time the tween updates. */ private function tweenUpdateHandler(event:AnimationEvent):void { var data:Array = event.parameters as Array; this.drawMarkers(data); } } }