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}