345 lines
10 KiB
ActionScript
Executable File
345 lines
10 KiB
ActionScript
Executable File
/*
|
||
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);
|
||
}
|
||
|
||
}
|
||
} |