first commit
This commit is contained in:
345
com/yahoo/astra/fl/charts/series/BarSeries.as
Executable file
345
com/yahoo/astra/fl/charts/series/BarSeries.as
Executable file
@ -0,0 +1,345 @@
|
||||
/*
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
113
com/yahoo/astra/fl/charts/series/CartesianSeries.as
Executable file
113
com/yahoo/astra/fl/charts/series/CartesianSeries.as
Executable file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
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.fl.charts.legend.LegendItemData;
|
||||
|
||||
import fl.core.InvalidationType;
|
||||
|
||||
import flash.events.Event;
|
||||
|
||||
/**
|
||||
* Functionality common to most series appearing in cartesian charts.
|
||||
* Generally, a <code>CartesianSeries</code> object shouldn't be
|
||||
* instantiated directly. Instead, a subclass with a concrete implementation
|
||||
* should be used.
|
||||
*
|
||||
* @author Josh Tynjala
|
||||
*/
|
||||
public class CartesianSeries extends Series implements ILegendItemSeries
|
||||
{
|
||||
|
||||
//--------------------------------------
|
||||
// Constructor
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function CartesianSeries(data:Object = null)
|
||||
{
|
||||
super(data);
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// Properties
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Storage for the horizontalField property.
|
||||
*/
|
||||
private var _horizontalField:String;
|
||||
|
||||
/**
|
||||
* @copy com.yahoo.astra.fl.charts.ISeries#horizontalField
|
||||
*/
|
||||
public function get horizontalField():String
|
||||
{
|
||||
return this._horizontalField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set horizontalField(value:String):void
|
||||
{
|
||||
if(this._horizontalField != value)
|
||||
{
|
||||
this._horizontalField = value;
|
||||
this.dispatchEvent(new Event("dataChange"));
|
||||
this.invalidate(InvalidationType.DATA);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Storage for the verticalField property.
|
||||
*/
|
||||
private var _verticalField:String;
|
||||
|
||||
/**
|
||||
* @copy com.yahoo.astra.fl.charts.ISeries#verticalField
|
||||
*/
|
||||
public function get verticalField():String
|
||||
{
|
||||
return this._verticalField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set verticalField(value:String):void
|
||||
{
|
||||
if(this._verticalField != value)
|
||||
{
|
||||
this._verticalField = value;
|
||||
this.dispatchEvent(new Event("dataChange"));
|
||||
this.invalidate(InvalidationType.DATA);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// Public Methods
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @copy com.yahoo.astra.fl.charts.series.ILegendItemSeries#createLegendItemData()
|
||||
*/
|
||||
public function createLegendItemData():LegendItemData
|
||||
{
|
||||
var fillColor:uint = this.getStyleValue("fillColor") != null ? this.getStyleValue("fillColor") as uint : this.getStyleValue("color") as uint;
|
||||
var borderColor:uint = this.getStyleValue("borderColor") != null ? this.getStyleValue("borderColor") as uint : this.getStyleValue("color") as uint;
|
||||
return new LegendItemData(this.displayName, this.getStyleValue("markerSkin"),
|
||||
fillColor,
|
||||
this.getStyleValue("fillAlpha") as Number,
|
||||
borderColor,
|
||||
this.getStyleValue("borderAlpha") as Number);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
342
com/yahoo/astra/fl/charts/series/ColumnSeries.as
Executable file
342
com/yahoo/astra/fl/charts/series/ColumnSeries.as
Executable file
@ -0,0 +1,342 @@
|
||||
/*
|
||||
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;
|
||||
import flash.utils.Dictionary;
|
||||
|
||||
/**
|
||||
* Renders data points as a series of vertical columns.
|
||||
*
|
||||
* @author Josh Tynjala
|
||||
*/
|
||||
public class ColumnSeries extends CartesianSeries
|
||||
{
|
||||
|
||||
//--------------------------------------
|
||||
// Static Variables
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private static var defaultStyles:Object =
|
||||
{
|
||||
markerSkin: RectangleSkin,
|
||||
markerSize: 18
|
||||
};
|
||||
|
||||
//--------------------------------------
|
||||
// Static Methods
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @copy fl.core.UIComponent#getStyleDefinition()
|
||||
*/
|
||||
public static function getStyleDefinition():Object
|
||||
{
|
||||
return mergeStyles(defaultStyles, Series.getStyleDefinition());
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// Constructor
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function ColumnSeries(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:ColumnSeries = new ColumnSeries();
|
||||
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();
|
||||
|
||||
//if we don't have data, let's get out of here
|
||||
if(!this.dataProvider)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//grab the axes
|
||||
var cartesianChart:CartesianChart = this.chart as CartesianChart;
|
||||
var valueAxis:IOriginAxis = cartesianChart.verticalAxis as IOriginAxis;
|
||||
var otherAxis:IAxis = cartesianChart.horizontalAxis;
|
||||
if(!valueAxis)
|
||||
{
|
||||
throw new Error("To use a ColumnSeries object, the vertical axis of the chart it appears within must be an IOriginAxis.");
|
||||
return;
|
||||
}
|
||||
|
||||
var allSeriesOfType:Array = ChartUtil.findSeriesOfType(this, cartesianChart);
|
||||
var markerSizes:Array = [];
|
||||
var totalMarkerSize:Number = this.calculateTotalMarkerSize(otherAxis, markerSizes);
|
||||
var seriesIndex:int = allSeriesOfType.indexOf(this);
|
||||
var markerSize:Number = markerSizes[seriesIndex] as Number;
|
||||
var xOffset:Number = this.calculateXOffset(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.x = position.x + xOffset;
|
||||
|
||||
marker.width = 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.y = originPosition;
|
||||
marker.height = 0;
|
||||
|
||||
if(marker is UIComponent)
|
||||
{
|
||||
(marker as UIComponent).drawNow();
|
||||
}
|
||||
this.validateMarker(ISeriesItemRenderer(marker));
|
||||
}
|
||||
|
||||
//stupid Flash UIComponent rounding!
|
||||
position.y = Math.round(position.y);
|
||||
originPosition = Math.round(originPosition);
|
||||
|
||||
var calculatedHeight:Number = originPosition - position.y;
|
||||
if(calculatedHeight < 0)
|
||||
{
|
||||
calculatedHeight = Math.abs(calculatedHeight);
|
||||
position.y = Math.round(originPosition);
|
||||
//always put the marker on the origin
|
||||
marker.y = position.y;
|
||||
}
|
||||
|
||||
startValues.push(marker.y, marker.height);
|
||||
endValues.push(position.y, calculatedHeight);
|
||||
}
|
||||
|
||||
//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
|
||||
* Calculates the x offset caused by clustering.
|
||||
*/
|
||||
protected function calculateXOffset(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 xOffset:Number = 0;
|
||||
for(var i:int = 0; i < seriesIndex; i++)
|
||||
{
|
||||
xOffset += markerSizes[i] as Number;
|
||||
}
|
||||
//center based on the sum of all marker sizes
|
||||
return -(totalMarkerSize / 2) + xOffset;
|
||||
}
|
||||
|
||||
//center based on the marker size of this series
|
||||
return -(markerSizes[seriesIndex] as Number) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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:ColumnSeries = ColumnSeries(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
|
||||
* 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
|
||||
* 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.width / IClusteringAxis(axis).clusterCount) / allSeriesOfType.length;
|
||||
}
|
||||
return Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 markerY:Number = data[i * 2];
|
||||
var markerHeight:Number = data[i * 2 + 1];
|
||||
marker.y = markerY;
|
||||
marker.height = markerHeight;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
57
com/yahoo/astra/fl/charts/series/ICategorySeries.as
Executable file
57
com/yahoo/astra/fl/charts/series/ICategorySeries.as
Executable file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
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
|
||||
{
|
||||
/**
|
||||
* Defines functions and properties for an ISeries that relies on categories.
|
||||
*
|
||||
* @see com.yahoo.astra.fl.charts.legend.ILegend
|
||||
* @see com.yahoo.astra.fl.charts.legend.LegendItemData
|
||||
*
|
||||
* @author Josh Tynjala
|
||||
*/
|
||||
public interface ICategorySeries extends ISeries
|
||||
{
|
||||
|
||||
//--------------------------------------
|
||||
// Properties
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* The field used to access categories for this series.
|
||||
*/
|
||||
function get categoryField():String
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function set categoryField(value:String):void;
|
||||
|
||||
/**
|
||||
* The names of the categories displayed on the category axis. If the
|
||||
* chart does not have a category axis, this value will be ignored.
|
||||
*/
|
||||
function get categoryNames():Array;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function set categoryNames(value:Array):void
|
||||
|
||||
//--------------------------------------
|
||||
// Methods
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* Creates an Array of LegendItemData objects to pass to the chart's legend.
|
||||
*/
|
||||
function createLegendItemData():Array
|
||||
|
||||
/**
|
||||
* Determines the category to which the item belongs.
|
||||
*/
|
||||
function itemToCategory(item:Object, index:int):String;
|
||||
}
|
||||
}
|
||||
29
com/yahoo/astra/fl/charts/series/ILegendItemSeries.as
Executable file
29
com/yahoo/astra/fl/charts/series/ILegendItemSeries.as
Executable file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
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.fl.charts.legend.LegendItemData;
|
||||
|
||||
/**
|
||||
* A series type that can create data for a legend.
|
||||
*
|
||||
* @see com.yahoo.astra.fl.charts.legend.ILegend
|
||||
* @see com.yahoo.astra.fl.charts.legend.LegendItemData
|
||||
*
|
||||
* @author Josh Tynjala
|
||||
*/
|
||||
public interface ILegendItemSeries extends ISeries
|
||||
{
|
||||
|
||||
//--------------------------------------
|
||||
// Methods
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* Creates a LegendItemData object to pass to the chart's legend.
|
||||
*/
|
||||
function createLegendItemData():LegendItemData
|
||||
}
|
||||
}
|
||||
86
com/yahoo/astra/fl/charts/series/ISeries.as
Executable file
86
com/yahoo/astra/fl/charts/series/ISeries.as
Executable file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
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.fl.charts.Chart;
|
||||
import com.yahoo.astra.fl.charts.IChart;
|
||||
|
||||
import flash.events.IEventDispatcher;
|
||||
|
||||
//--------------------------------------
|
||||
// Events
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* Dispatched when the data property for an ISeries changes.
|
||||
*/
|
||||
[Event(name="dataChange", type="flash.events.Event")]
|
||||
|
||||
/**
|
||||
* A renderer for a series displayed on a chart.
|
||||
*
|
||||
* <p>Important: Must be a subclass of DisplayObject</p>
|
||||
*
|
||||
* @see flash.display.DisplayObject
|
||||
* @author Josh Tynjala
|
||||
*/
|
||||
public interface ISeries extends IEventDispatcher
|
||||
{
|
||||
|
||||
//--------------------------------------
|
||||
// Properties
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* The chart in which this series appears.
|
||||
*/
|
||||
function get chart():Object;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function set chart(value:Object):void;
|
||||
|
||||
/**
|
||||
* The data provider for this series. Accepts <code>Array</code> or <code>XMLList</code> objects.
|
||||
*/
|
||||
function get dataProvider():Object;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function set dataProvider(value:Object):void;
|
||||
|
||||
/**
|
||||
* The name of the series as it appears to the user.
|
||||
*/
|
||||
function get displayName():String;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function set displayName(value:String):void;
|
||||
|
||||
/**
|
||||
* The number of items in the series.
|
||||
*/
|
||||
function get length():int;
|
||||
|
||||
//--------------------------------------
|
||||
// Methods
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* Creates a copy of the ISeries object.
|
||||
*
|
||||
* @return a new ISeries object
|
||||
*/
|
||||
function clone():ISeries;
|
||||
|
||||
function itemRendererToIndex(renderer:ISeriesItemRenderer):int;
|
||||
|
||||
function itemToItemRenderer(item:Object):ISeriesItemRenderer;
|
||||
}
|
||||
}
|
||||
47
com/yahoo/astra/fl/charts/series/ISeriesItemRenderer.as
Executable file
47
com/yahoo/astra/fl/charts/series/ISeriesItemRenderer.as
Executable file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
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 flash.events.IEventDispatcher;
|
||||
|
||||
/**
|
||||
* A renderer for an item in a series on a chart.
|
||||
*
|
||||
* <p>Important: Must be a subclass of <code>DisplayObject</code></p>
|
||||
*
|
||||
* @see flash.display.DisplayObject
|
||||
* @author Josh Tynjala
|
||||
*/
|
||||
public interface ISeriesItemRenderer extends IEventDispatcher
|
||||
{
|
||||
|
||||
//--------------------------------------
|
||||
// Properties
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* The data provider for the item that this item renderer represents.
|
||||
* Custom implementations of <code>ISeriesItemRenderer</code>
|
||||
* may use this property to render additional information for
|
||||
* the user.
|
||||
*/
|
||||
function get data():Object;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function set data(value:Object):void;
|
||||
|
||||
/**
|
||||
* The series data that is displayed by this renderer.
|
||||
*/
|
||||
function get series():ISeries;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
function set series(value:ISeries):void;
|
||||
}
|
||||
}
|
||||
15
com/yahoo/astra/fl/charts/series/IStackedSeries.as
Executable file
15
com/yahoo/astra/fl/charts/series/IStackedSeries.as
Executable file
@ -0,0 +1,15 @@
|
||||
/*
|
||||
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
|
||||
{
|
||||
/**
|
||||
* A type of series that supports stacking.
|
||||
*
|
||||
* @author Josh Tynjala
|
||||
*/
|
||||
public interface IStackedSeries extends ISeries
|
||||
{
|
||||
}
|
||||
}
|
||||
420
com/yahoo/astra/fl/charts/series/LineSeries.as
Executable file
420
com/yahoo/astra/fl/charts/series/LineSeries.as
Executable file
@ -0,0 +1,420 @@
|
||||
/*
|
||||
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.NumericAxis;
|
||||
import com.yahoo.astra.fl.charts.skins.CircleSkin;
|
||||
import com.yahoo.astra.utils.GraphicsUtil;
|
||||
|
||||
import fl.core.UIComponent;
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
import flash.geom.Point;
|
||||
import flash.geom.Rectangle;
|
||||
|
||||
/**
|
||||
* The weight, in pixels, of the line drawn between points in this series.
|
||||
*
|
||||
* @default 3
|
||||
*/
|
||||
[Style(name="lineWeight", type="Number")]
|
||||
|
||||
/**
|
||||
* If true, lines are drawn between the markers. If false, only the markers are drawn.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
[Style(name="connectPoints", type="Boolean")]
|
||||
|
||||
/**
|
||||
* If true, draws a dashed line between discontinuous points.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
[Style(name="connectDiscontinuousPoints", type="Boolean")]
|
||||
|
||||
/**
|
||||
* The length of dashes in a discontinuous line.
|
||||
*
|
||||
* @default 10
|
||||
*/
|
||||
[Style(name="discontinuousDashLength", type="Number")]
|
||||
|
||||
/**
|
||||
* If true, the series will include a fill under the line, extending to the axis.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
[Style(name="showAreaFill", type="Boolean")]
|
||||
|
||||
/**
|
||||
* The alpha value of the area fill.
|
||||
*
|
||||
* @default 0.6
|
||||
*/
|
||||
[Style(name="areaFillAlpha", type="Number")]
|
||||
|
||||
/**
|
||||
* Renders data points as a series of connected line segments.
|
||||
*
|
||||
* @author Josh Tynjala
|
||||
*/
|
||||
public class LineSeries extends CartesianSeries
|
||||
{
|
||||
|
||||
//--------------------------------------
|
||||
// Class Variables
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private static var defaultStyles:Object =
|
||||
{
|
||||
markerSkin: CircleSkin,
|
||||
lineWeight: 3,
|
||||
connectPoints: true,
|
||||
connectDiscontinuousPoints: false,
|
||||
discontinuousDashLength: 10,
|
||||
showAreaFill: false,
|
||||
areaFillAlpha: 0.6
|
||||
};
|
||||
|
||||
//--------------------------------------
|
||||
// Class Methods
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @copy fl.core.UIComponent#getStyleDefinition()
|
||||
*/
|
||||
public static function getStyleDefinition():Object
|
||||
{
|
||||
return mergeStyles(defaultStyles, Series.getStyleDefinition());
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// Constructor
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function LineSeries(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:LineSeries = new LineSeries();
|
||||
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(!this.dataProvider)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var markerSize:Number = this.getStyleValue("markerSize") as Number;
|
||||
|
||||
var startValues:Array = [];
|
||||
var endValues:Array = [];
|
||||
var itemCount:int = this.length;
|
||||
for(var i:int = 0; i < itemCount; i++)
|
||||
{
|
||||
var position:Point = CartesianChart(this.chart).itemToPosition(this, i);
|
||||
|
||||
var marker:DisplayObject = this.markers[i] as DisplayObject;
|
||||
var ratio:Number = marker.width / marker.height;
|
||||
if(isNaN(ratio)) ratio = 1;
|
||||
marker.height = markerSize;
|
||||
marker.width = marker.height * ratio;
|
||||
|
||||
if(marker is UIComponent)
|
||||
{
|
||||
(marker as UIComponent).drawNow();
|
||||
}
|
||||
|
||||
//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)))
|
||||
{
|
||||
marker.x = position.x - marker.width / 2;
|
||||
marker.y = position.y - marker.height / 2;
|
||||
this.validateMarker(ISeriesItemRenderer(marker));
|
||||
}
|
||||
|
||||
//correct start value for marker size
|
||||
startValues.push(marker.x + marker.width / 2);
|
||||
startValues.push(marker.y + marker.height / 2);
|
||||
|
||||
endValues.push(position.x);
|
||||
endValues.push(position.y);
|
||||
}
|
||||
|
||||
//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;
|
||||
this.drawMarkers(startValues);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private function tweenUpdateHandler(event:AnimationEvent):void
|
||||
{
|
||||
this.drawMarkers(event.parameters as Array);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private function drawMarkers(data:Array):void
|
||||
{
|
||||
var primaryIsVertical:Boolean = true;
|
||||
var primaryAxis:NumericAxis = CartesianChart(this.chart).verticalAxis as NumericAxis;
|
||||
if(!primaryAxis)
|
||||
{
|
||||
primaryIsVertical = false;
|
||||
primaryAxis = CartesianChart(this.chart).horizontalAxis as NumericAxis;
|
||||
}
|
||||
|
||||
var originPosition:Number = primaryAxis.valueToLocal(primaryAxis.origin);
|
||||
|
||||
|
||||
|
||||
var lineColor:uint = this.getStyleValue("lineColor") != null ? this.getStyleValue("lineColor") as uint : this.getStyleValue("color") as uint;
|
||||
var connectDiscontinuousPoints:Boolean = this.getStyleValue("connectDiscontinuousPoints") as Boolean;
|
||||
var discontinuousDashLength:Number = this.getStyleValue("discontinuousDashLength") as Number;
|
||||
var showAreaFill:Boolean = this.getStyleValue("showAreaFill") as Boolean;
|
||||
|
||||
this.graphics.clear();
|
||||
this.setPrimaryLineStyle();
|
||||
this.beginAreaFill(showAreaFill, lineColor);
|
||||
|
||||
var firstValidPosition:Point;
|
||||
var lastValidPosition:Point;
|
||||
|
||||
//used to determine if the data must be drawn
|
||||
var seriesBounds:Rectangle = new Rectangle(0, 0, this.width, this.height);
|
||||
var lastMarkerValid:Boolean = false;
|
||||
var itemCount:int = this.length;
|
||||
for(var i:int = 0; i < itemCount; i++)
|
||||
{
|
||||
var marker:DisplayObject = DisplayObject(this.markers[i]);
|
||||
var xPosition:Number = data[i * 2] as Number;
|
||||
var yPosition:Number = data[i * 2 + 1] as Number;
|
||||
var markerValid:Boolean = !this.isMarkerInvalid(ISeriesItemRenderer(marker));
|
||||
|
||||
//if the position is valid, move or draw as needed
|
||||
if(markerValid)
|
||||
{
|
||||
marker.x = xPosition - marker.width / 2;
|
||||
marker.y = yPosition - marker.height / 2;
|
||||
|
||||
if(lastValidPosition && !lastMarkerValid && connectDiscontinuousPoints)
|
||||
{
|
||||
this.setPrimaryLineStyle(connectDiscontinuousPoints);
|
||||
|
||||
//draw a discontinuous line from the last valid position and the new valid position
|
||||
GraphicsUtil.drawDashedLine(this.graphics, lastValidPosition.x, lastValidPosition.y, xPosition, yPosition, discontinuousDashLength, discontinuousDashLength);
|
||||
|
||||
this.setPrimaryLineStyle();
|
||||
}
|
||||
else if(!lastValidPosition || (!lastMarkerValid && !connectDiscontinuousPoints))
|
||||
{
|
||||
//if the last position is not valid, simply move to the new position
|
||||
this.graphics.moveTo(xPosition, yPosition);
|
||||
}
|
||||
else //current and last position are both valid
|
||||
{
|
||||
var minX:Number = Math.min(lastValidPosition.x, xPosition);
|
||||
var maxX:Number = Math.max(lastValidPosition.x, xPosition);
|
||||
var minY:Number = Math.min(lastValidPosition.y, yPosition);
|
||||
var maxY:Number = Math.max(lastValidPosition.y, yPosition);
|
||||
var lineBounds:Rectangle = new Rectangle(minX, minY, maxX - minX, maxY - minY);
|
||||
|
||||
//if x or y position is equal between points, the rectangle will have
|
||||
//a width or height of zero (so no line will be drawn where one should!)
|
||||
if(lineBounds.width == 0)
|
||||
{
|
||||
lineBounds.width = 1;
|
||||
}
|
||||
|
||||
if(lineBounds.height == 0)
|
||||
{
|
||||
lineBounds.height = 1;
|
||||
}
|
||||
|
||||
//if line between the last point and this point is within
|
||||
//the series bounds, draw it, otherwise, only move to the new point.
|
||||
if(lineBounds.intersects(seriesBounds) ||
|
||||
yPosition == seriesBounds.y ||
|
||||
yPosition == seriesBounds.y + seriesBounds.height ||
|
||||
xPosition == seriesBounds.x ||
|
||||
xPosition == seriesBounds.x + seriesBounds.width)
|
||||
{
|
||||
this.graphics.lineTo(xPosition, yPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.graphics.moveTo(xPosition, yPosition);
|
||||
}
|
||||
}
|
||||
lastMarkerValid = true;
|
||||
lastValidPosition = new Point(xPosition, yPosition);
|
||||
if(!firstValidPosition)
|
||||
{
|
||||
firstValidPosition = lastValidPosition.clone();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(showAreaFill) this.closeAreaFill(primaryIsVertical, originPosition, firstValidPosition, lastValidPosition);
|
||||
this.setPrimaryLineStyle();
|
||||
this.beginAreaFill(showAreaFill, lineColor);
|
||||
lastMarkerValid = false;
|
||||
firstValidPosition = null;
|
||||
}
|
||||
}
|
||||
|
||||
if(showAreaFill)
|
||||
{
|
||||
this.closeAreaFill(primaryIsVertical, originPosition, firstValidPosition, lastValidPosition);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Begins drawing an area fill.
|
||||
*/
|
||||
private function beginAreaFill(showAreaFill:Boolean, color:uint):void
|
||||
{
|
||||
if(!showAreaFill)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var areaFillAlpha:Number = this.getStyleValue("areaFillAlpha") as Number;
|
||||
this.graphics.beginFill(color, areaFillAlpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Sets the line style when connecting points. The forceColor flag
|
||||
* will use the color even when the connectPoints style is set to false.
|
||||
* This is used primarily to allow connectDiscontinousPoints to work
|
||||
* when connectPoints is false.
|
||||
*/
|
||||
private function setPrimaryLineStyle(forceColor:Boolean = false):void
|
||||
{
|
||||
var connectPoints:Boolean = this.getStyleValue("connectPoints") as Boolean;
|
||||
if(!connectPoints && !forceColor)
|
||||
{
|
||||
this.graphics.lineStyle(0, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
var lineWeight:int = this.getStyleValue("lineWeight") as int;
|
||||
var lineColor:uint = this.getStyleValue("lineColor") != null ? this.getStyleValue("lineColor") as uint : this.getStyleValue("color") as uint;
|
||||
var lineAlpha:Number = this.getStyleValue("lineAlpha") as Number;
|
||||
this.graphics.lineStyle(lineWeight, lineColor, lineAlpha);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Closes an area fill. Called after the full line is drawn. May also be
|
||||
* called when bad data is encountered.
|
||||
*/
|
||||
private function closeAreaFill(vertical:Boolean, origin:Number, firstValidPosition:Point, lastValidPosition:Point):void
|
||||
{
|
||||
if(isNaN(origin) || firstValidPosition == null || lastValidPosition == null) return;
|
||||
this.graphics.lineStyle(0, 0, 0);
|
||||
if(vertical)
|
||||
{
|
||||
this.graphics.lineTo(lastValidPosition.x, origin);
|
||||
this.graphics.lineTo(firstValidPosition.x, origin);
|
||||
this.graphics.lineTo(firstValidPosition.x, firstValidPosition.y);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.graphics.lineTo(origin, lastValidPosition.y);
|
||||
this.graphics.lineTo(origin, firstValidPosition.y);
|
||||
this.graphics.lineTo(firstValidPosition.x, firstValidPosition.y);
|
||||
}
|
||||
this.graphics.endFill();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
770
com/yahoo/astra/fl/charts/series/PieSeries.as
Executable file
770
com/yahoo/astra/fl/charts/series/PieSeries.as
Executable file
@ -0,0 +1,770 @@
|
||||
/*
|
||||
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.PieChart;
|
||||
import com.yahoo.astra.fl.charts.legend.LegendItemData;
|
||||
import com.yahoo.astra.fl.charts.skins.RectangleSkin;
|
||||
import com.yahoo.astra.utils.GeomUtil;
|
||||
import com.yahoo.astra.utils.GraphicsUtil;
|
||||
import com.yahoo.astra.utils.NumberUtil;
|
||||
|
||||
import fl.core.InvalidationType;
|
||||
import fl.core.UIComponent;
|
||||
|
||||
import flash.display.Shape;
|
||||
import flash.display.Sprite;
|
||||
import flash.events.Event;
|
||||
import flash.geom.Point;
|
||||
import flash.text.TextField;
|
||||
import flash.text.TextFieldAutoSize;
|
||||
import flash.text.TextFormat;
|
||||
import flash.text.TextFormatAlign;
|
||||
|
||||
//--------------------------------------
|
||||
// Styles
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* The colors of the markers in this series.
|
||||
*
|
||||
* @default [0x729fcf, 0xfcaf3e, 0x73d216, 0xfce94f, 0xad7fa8, 0x3465a4]
|
||||
*/
|
||||
[Style(name="fillColors", type="Array")]
|
||||
|
||||
/**
|
||||
* If true, a label is displayed on each marker. The label text is created
|
||||
* with the labelFunction property of the series. The default label function
|
||||
* sets the label to the percentage value of the item.
|
||||
*
|
||||
* @default false
|
||||
* @see PieSeries#labelFunction
|
||||
*/
|
||||
[Style(name="showLabels", type="Boolean")]
|
||||
|
||||
/**
|
||||
* If true, marker labels that overlap previously-created labels will be
|
||||
* hidden to improve readability.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
[Style(name="hideOverlappingLabels", type="Boolean")]
|
||||
|
||||
/**
|
||||
* Renders data points as a series of pie-like wedges.
|
||||
*
|
||||
* @author Josh Tynjala
|
||||
*/
|
||||
public class PieSeries extends Series implements ICategorySeries
|
||||
{
|
||||
|
||||
//--------------------------------------
|
||||
// Class Variables
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private static var defaultStyles:Object =
|
||||
{
|
||||
fillColors:
|
||||
[
|
||||
0x00b8bf, 0x8dd5e7, 0xc0fff6, 0xffa928, 0xedff9f, 0xd00050,
|
||||
0xc6c6c6, 0xc3eafb, 0xfcffad, 0xcfff83, 0x444444, 0x4d95dd,
|
||||
0xb8ebff, 0x60558f, 0x737d7e, 0xa64d9a, 0x8e9a9b, 0x803e77
|
||||
],
|
||||
borderColors:
|
||||
[
|
||||
0x00b8bf, 0x8dd5e7, 0xc0fff6, 0xffa928, 0xedff9f, 0xd00050,
|
||||
0xc6c6c6, 0xc3eafb, 0xfcffad, 0xcfff83, 0x444444, 0x4d95dd,
|
||||
0xb8ebff, 0x60558f, 0x737d7e, 0xa64d9a, 0x8e9a9b, 0x803e77
|
||||
],
|
||||
fillAlphas: [1.0],
|
||||
borderAlphas: [0.0],
|
||||
markerSkins: [RectangleSkin],
|
||||
showLabels: false,
|
||||
hideOverlappingLabels: true
|
||||
//see textFormat default style defined in constructor below
|
||||
//works around stylemanager global style bug!
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private static const RENDERER_STYLES:Object =
|
||||
{
|
||||
fillColor: "fillColors",
|
||||
fillAlpha: "fillAlphas",
|
||||
borderColor: "borderColors",
|
||||
borderAlpha: "borderAlphas",
|
||||
skin: "markerSkins"
|
||||
};
|
||||
|
||||
//--------------------------------------
|
||||
// Class Methods
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @copy fl.core.UIComponent#getStyleDefinition()
|
||||
*/
|
||||
public static function getStyleDefinition():Object
|
||||
{
|
||||
return mergeStyles(defaultStyles, Series.getStyleDefinition());
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// Constructor
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function PieSeries(data:Object = null)
|
||||
{
|
||||
super(data);
|
||||
//we have to set this as an instance style because textFormat is
|
||||
//defined as a global style in StyleManager, and that takes
|
||||
//precedence over shared/class styles
|
||||
this.setStyle("textFormat", new TextFormat("_sans", 11, 0x000000, true, false, false, "", "", TextFormatAlign.LEFT, 0, 0, 0, 0));
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// Properties
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* The text fields used to display labels over each marker.
|
||||
*/
|
||||
protected var labels:Array = [];
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Holds the labels created by the previous redraw so that they can
|
||||
* be reused.
|
||||
*/
|
||||
protected var labelsCache:Array;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Storage for the masks that define the shapes of the markers.
|
||||
*/
|
||||
protected var markerMasks:Array = [];
|
||||
|
||||
/**
|
||||
* @private
|
||||
* The Animation instance that controls animation in this series.
|
||||
*/
|
||||
private var _animation:Animation;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private var _previousData:Array = [];
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Storage for the dataField property.
|
||||
*/
|
||||
private var _dataField:String;
|
||||
|
||||
/**
|
||||
* The field used to access data for this series.
|
||||
*/
|
||||
public function get dataField():String
|
||||
{
|
||||
return this._dataField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set dataField(value:String):void
|
||||
{
|
||||
if(this._dataField != value)
|
||||
{
|
||||
this._dataField = value;
|
||||
this.dispatchEvent(new Event("dataChange"));
|
||||
this.invalidate(InvalidationType.DATA);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Storage for the categoryField property.
|
||||
*/
|
||||
private var _categoryField:String;
|
||||
|
||||
/**
|
||||
* @copy com.yahoo.astra.fl.charts.series.ICategorySeries#categoryField
|
||||
*/
|
||||
public function get categoryField():String
|
||||
{
|
||||
return this._categoryField;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set categoryField(value:String):void
|
||||
{
|
||||
if(this._categoryField != value)
|
||||
{
|
||||
this._categoryField = value;
|
||||
this.dispatchEvent(new Event("dataChange"));
|
||||
this.invalidate(InvalidationType.DATA);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Storage for the categoryNames property.
|
||||
*/
|
||||
private var _categoryNames:Array;
|
||||
|
||||
/**
|
||||
* @copy com.yahoo.astra.fl.charts.series.ICategorySeries#categoryNames
|
||||
*/
|
||||
public function get categoryNames():Array
|
||||
{
|
||||
return this._categoryNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set categoryNames(value:Array):void
|
||||
{
|
||||
this._categoryNames = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Storage for the labelFunction property.
|
||||
*/
|
||||
private var _labelFunction:Function = defaultLabelFunction;
|
||||
|
||||
/**
|
||||
* A function may be set to determine the text value of the labels.
|
||||
*
|
||||
* <pre>function labelFunction(item:Object):String</pre>
|
||||
*/
|
||||
public function get labelFunction():Function
|
||||
{
|
||||
return this._labelFunction;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set labelFunction(value:Function):void
|
||||
{
|
||||
this._labelFunction = value;
|
||||
this.invalidate();
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// Public Methods
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override public function clone():ISeries
|
||||
{
|
||||
var series:PieSeries = new PieSeries();
|
||||
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;
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an item to it's value.
|
||||
*/
|
||||
public function itemToData(item:Object):Number
|
||||
{
|
||||
var primaryDataField:String = PieChart(this.chart).seriesToDataField(this);
|
||||
if(primaryDataField)
|
||||
{
|
||||
return Number(item[primaryDataField]);
|
||||
}
|
||||
return Number(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an item to the category in which it is displayed.
|
||||
*/
|
||||
public function itemToCategory(item:Object, index:int):String
|
||||
{
|
||||
var primaryCategoryField:String = PieChart(this.chart).seriesToCategoryField(this);
|
||||
if(primaryCategoryField)
|
||||
{
|
||||
return item[primaryCategoryField];
|
||||
}
|
||||
|
||||
if(this._categoryNames && index >= 0 && index < this._categoryNames.length)
|
||||
{
|
||||
return this._categoryNames[index];
|
||||
}
|
||||
return index.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an item's value to its percentage equivilent.
|
||||
*/
|
||||
public function itemToPercentage(item:Object):Number
|
||||
{
|
||||
var totalValue:Number = this.calculateTotalValue();
|
||||
if(totalValue == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
return 100 * (this.itemToData(item) / totalValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function createLegendItemData():Array
|
||||
{
|
||||
var items:Array = [];
|
||||
var markerSkins:Array = this.getStyleValue("markerSkins") as Array;
|
||||
var fillColors:Array = this.getStyleValue("fillColors") as Array;
|
||||
var legendItemCount:int = this.length;
|
||||
for(var i:int = 0; i < legendItemCount; i++)
|
||||
{
|
||||
var item:Object = this.dataProvider[i];
|
||||
var categoryName:String = this.itemToCategory(item, i);
|
||||
var markerSkin:Object = markerSkins[i % markerSkins.length]
|
||||
var fillColor:uint = fillColors[i % fillColors.length];
|
||||
var data:LegendItemData = new LegendItemData(categoryName, markerSkin, fillColor, 1, fillColor, 1);
|
||||
items.push(data);
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// Protected Methods
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
override protected function draw():void
|
||||
{
|
||||
var stylesInvalid:Boolean = this.isInvalid(InvalidationType.STYLES);
|
||||
var sizeInvalid:Boolean = this.isInvalid(InvalidationType.SIZE);
|
||||
var dataInvalid:Boolean = this.isInvalid(InvalidationType.DATA);
|
||||
|
||||
super.draw();
|
||||
|
||||
this.drawMarkers(stylesInvalid, sizeInvalid);
|
||||
|
||||
var showLabels:Boolean = this.getStyleValue("showLabels") as Boolean;
|
||||
this.createLabelCache();
|
||||
if(showLabels)
|
||||
{
|
||||
this.drawLabels();
|
||||
}
|
||||
this.clearLabelCache();
|
||||
|
||||
this.beginAnimation();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* All markers are removed from the display list.
|
||||
*/
|
||||
override protected function removeAllMarkers():void
|
||||
{
|
||||
super.removeAllMarkers();
|
||||
var markerCount:int = this.markerMasks.length;
|
||||
for(var i:int = 0; i < markerCount; i++)
|
||||
{
|
||||
var mask:Shape = this.markerMasks.pop() as Shape;
|
||||
this.removeChild(mask);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Add or remove markers as needed. current markers will be reused.
|
||||
*/
|
||||
override protected function refreshMarkers():void
|
||||
{
|
||||
super.refreshMarkers();
|
||||
|
||||
var itemCount:int = this.length;
|
||||
var difference:int = itemCount - this.markerMasks.length;
|
||||
if(difference > 0)
|
||||
{
|
||||
for(var i:int = 0; i < difference; i++)
|
||||
{
|
||||
var mask:Shape = new Shape();
|
||||
this.addChild(mask);
|
||||
this.markerMasks.push(mask);
|
||||
|
||||
var marker:Sprite = this.markers[i + (itemCount-difference)] as Sprite;
|
||||
marker.mask = mask;
|
||||
marker.width = this.width;
|
||||
marker.height = this.height;
|
||||
}
|
||||
}
|
||||
else if(difference < 0)
|
||||
{
|
||||
difference = Math.abs(difference);
|
||||
for(i = 0; i < difference; i++)
|
||||
{
|
||||
mask = this.markerMasks.pop() as Shape;
|
||||
this.removeChild(mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* The default function called to initialize the text on the marker
|
||||
* labels.
|
||||
*/
|
||||
protected function defaultLabelFunction(item:Object):String
|
||||
{
|
||||
var percentage:Number = this.itemToPercentage(item);
|
||||
return (percentage < 0.01 ? "< 0.01" : NumberUtil.roundToNearest(percentage, 0.01)) + "%";
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Draws the markers in this series.
|
||||
*/
|
||||
protected function drawMarkers(stylesInvalid:Boolean, sizeInvalid:Boolean):void
|
||||
{
|
||||
var markerCount:int = this.markers.length;
|
||||
for(var i:int = 0; i < markerCount; i++)
|
||||
{
|
||||
var marker:UIComponent = UIComponent(this.markers[i]);
|
||||
|
||||
if(stylesInvalid)
|
||||
{
|
||||
this.copyStylesToRenderer(ISeriesItemRenderer(marker), RENDERER_STYLES);
|
||||
}
|
||||
|
||||
if(sizeInvalid)
|
||||
{
|
||||
marker.width = this.width;
|
||||
marker.height = this.height;
|
||||
}
|
||||
//not really required, but we should validate anyway.
|
||||
this.validateMarker(ISeriesItemRenderer(marker));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Either creates a new label TextField or retrieves one from the
|
||||
* cache.
|
||||
*/
|
||||
protected function getLabel():TextField
|
||||
{
|
||||
var label:TextField;
|
||||
if(this.labelsCache.length > 0)
|
||||
{
|
||||
label = TextField(this.labelsCache.shift());
|
||||
}
|
||||
else
|
||||
{
|
||||
label = new TextField();
|
||||
label.autoSize = TextFieldAutoSize.LEFT;
|
||||
label.selectable = false;
|
||||
label.mouseEnabled = false;
|
||||
this.addChild(label);
|
||||
}
|
||||
label.visible = true;
|
||||
return label;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Updates the label text and positions the labels.
|
||||
*/
|
||||
protected function drawLabels():void
|
||||
{
|
||||
var textFormat:TextFormat = this.getStyleValue("textFormat") as TextFormat;
|
||||
var embedFonts:Boolean = this.getStyleValue("embedFonts") as Boolean;
|
||||
var hideOverlappingLabels:Boolean = this.getStyleValue("hideOverlappingLabels") as Boolean;
|
||||
|
||||
var angle:Number = 0;
|
||||
var valueSum:Number = 0;
|
||||
var totalValue:Number = this.calculateTotalValue();
|
||||
var markerCount:int = this.markers.length;
|
||||
for(var i:int = 0; i < markerCount; i++)
|
||||
{
|
||||
var label:TextField = this.getLabel();
|
||||
this.labels.push(label);
|
||||
label.defaultTextFormat = textFormat;
|
||||
label.embedFonts = embedFonts;
|
||||
label.text = this.labelFunction(this.dataProvider[i]);
|
||||
|
||||
var value:Number = this.itemToData(this.dataProvider[i]);
|
||||
if(totalValue == 0)
|
||||
{
|
||||
angle = 360 / this.length;
|
||||
}
|
||||
else
|
||||
{
|
||||
angle = 360 * ((valueSum + value / 2) / totalValue);
|
||||
}
|
||||
valueSum += value;
|
||||
var halfWidth:Number = this.width / 2;
|
||||
var halfHeight:Number = this.height / 2;
|
||||
var radius:Number = Math.min(halfWidth, halfHeight);
|
||||
var position:Point = Point.polar(2 * radius / 3, -GeomUtil.degreesToRadians(angle));
|
||||
label.x = halfWidth + position.x - label.width / 2;
|
||||
label.y = halfHeight + position.y - label.height / 2;
|
||||
|
||||
if(hideOverlappingLabels)
|
||||
{
|
||||
for(var j:int = 0; j < i; j++)
|
||||
{
|
||||
var previousLabel:TextField = TextField(this.labels[j]);
|
||||
if(previousLabel.hitTestObject(label))
|
||||
{
|
||||
label.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies a styles from the series to a child through a style map.
|
||||
*
|
||||
* @see copyStylesToChild()
|
||||
*/
|
||||
protected function copyStylesToRenderer(child:ISeriesItemRenderer, styleMap:Object):void
|
||||
{
|
||||
var index:int = this.markers.indexOf(child);
|
||||
var childComponent:UIComponent = child as UIComponent;
|
||||
for(var n:String in styleMap)
|
||||
{
|
||||
var styleValues:Array = this.getStyleValue(styleMap[n]) as Array;
|
||||
//if it doesn't exist, ignore it and go with the defaults for this series
|
||||
if(!styleValues) continue;
|
||||
childComponent.setStyle(n, styleValues[index % styleValues.length])
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// Private Methods
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Sets up the animation for the markers.
|
||||
*/
|
||||
private function beginAnimation():void
|
||||
{
|
||||
var itemCount:int = this.length;
|
||||
if(!this._previousData || this._previousData.length != this.length)
|
||||
{
|
||||
this._previousData = [];
|
||||
for(var i:int = 0; i < itemCount; i++)
|
||||
{
|
||||
this._previousData.push(0);
|
||||
}
|
||||
}
|
||||
|
||||
//handle animating all the markers in one fell swoop.
|
||||
if(this._animation)
|
||||
{
|
||||
if(this._animation.active)
|
||||
{
|
||||
this._animation.pause();
|
||||
}
|
||||
this._animation.removeEventListener(AnimationEvent.UPDATE, tweenUpdateHandler);
|
||||
this._animation.removeEventListener(AnimationEvent.PAUSE, tweenPauseHandler);
|
||||
this._animation.removeEventListener(AnimationEvent.COMPLETE, tweenCompleteHandler);
|
||||
this._animation = null;
|
||||
}
|
||||
|
||||
var data:Array = this.dataProviderToArrayOfNumbers();
|
||||
|
||||
//don't animate on livepreview!
|
||||
if(this.isLivePreview || !this.getStyleValue("animationEnabled"))
|
||||
{
|
||||
this.renderMarkerMasks(data);
|
||||
}
|
||||
else
|
||||
{
|
||||
var animationDuration:int = this.getStyleValue("animationDuration") as int;
|
||||
var animationEasingFunction:Function = this.getStyleValue("animationEasingFunction") as Function;
|
||||
|
||||
this._animation = new Animation(animationDuration, this._previousData, data);
|
||||
this._animation.addEventListener(AnimationEvent.UPDATE, tweenUpdateHandler);
|
||||
this._animation.addEventListener(AnimationEvent.PAUSE, tweenPauseHandler);
|
||||
this._animation.addEventListener(AnimationEvent.COMPLETE, tweenCompleteHandler);
|
||||
this._animation.easingFunction = animationEasingFunction;
|
||||
this.renderMarkerMasks(this._previousData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Determines the total sum of all values in the data provider.
|
||||
*/
|
||||
private function calculateTotalValue():Number
|
||||
{
|
||||
var totalValue:Number = 0;
|
||||
var itemCount:int = this.length;
|
||||
for(var i:int = 0; i < itemCount; i++)
|
||||
{
|
||||
var currentItem:Object = this.dataProvider[i];
|
||||
var value:Number = this.itemToData(currentItem);
|
||||
|
||||
if(!isNaN(value))
|
||||
{
|
||||
totalValue += value;
|
||||
}
|
||||
}
|
||||
return totalValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Retreives all the numeric values from the data provider
|
||||
* and places them into an Array so that they may be used
|
||||
* in an animation.
|
||||
*/
|
||||
private function dataProviderToArrayOfNumbers():Array
|
||||
{
|
||||
var output:Array = [];
|
||||
|
||||
var itemCount:int = this.length;
|
||||
for(var i:int = 0; i < itemCount; i++)
|
||||
{
|
||||
var item:Object = this.dataProvider[i];
|
||||
var value:Number = 0;
|
||||
if(item != null)
|
||||
{
|
||||
value = this.itemToData(item);
|
||||
}
|
||||
output.push(value);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private function tweenUpdateHandler(event:AnimationEvent):void
|
||||
{
|
||||
this.renderMarkerMasks(event.parameters as Array);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private function tweenCompleteHandler(event:AnimationEvent):void
|
||||
{
|
||||
this.tweenUpdateHandler(event);
|
||||
this.tweenPauseHandler(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private function tweenPauseHandler(event:AnimationEvent):void
|
||||
{
|
||||
this._previousData = (event.parameters as Array).concat();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private function renderMarkerMasks(data:Array):void
|
||||
{
|
||||
var values:Array = [];
|
||||
var totalValue:Number = 0;
|
||||
var itemCount:int = data.length;
|
||||
for(var i:int = 0; i < itemCount; i++)
|
||||
{
|
||||
var value:Number = Number(data[i]);
|
||||
|
||||
values.push(value);
|
||||
if(!isNaN(value))
|
||||
{
|
||||
totalValue += value;
|
||||
}
|
||||
}
|
||||
|
||||
var totalAngle:Number = 0;
|
||||
var halfWidth:Number = this.width / 2;
|
||||
var halfHeight:Number = this.height / 2;
|
||||
var radius:Number = Math.min(halfWidth, halfHeight);
|
||||
var fillColors:Array = this.getStyleValue("fillColors") as Array;
|
||||
|
||||
var angle:Number = 0;
|
||||
for(i = 0; i < itemCount; i++)
|
||||
{
|
||||
value = Number(data[i]);
|
||||
if(totalValue == 0)
|
||||
{
|
||||
angle = 360 / data.length;
|
||||
}
|
||||
else
|
||||
{
|
||||
angle = 360 * (value / totalValue);
|
||||
}
|
||||
|
||||
var mask:Shape = this.markerMasks[i] as Shape;
|
||||
mask.graphics.clear();
|
||||
mask.graphics.beginFill(0xff0000);
|
||||
GraphicsUtil.drawWedge(mask.graphics, halfWidth, halfHeight, totalAngle, angle, radius);
|
||||
mask.graphics.endFill();
|
||||
totalAngle += angle;
|
||||
|
||||
var marker:UIComponent = UIComponent(this.markers[i]);
|
||||
marker.drawNow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Places all the existing labels in a cache so that they may be reused
|
||||
* when we redraw the series.
|
||||
*/
|
||||
private function createLabelCache():void
|
||||
{
|
||||
this.labelsCache = this.labels.concat();
|
||||
this.labels = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* If any labels are left in the cache after we've redrawn, they can be
|
||||
* removed from the display list.
|
||||
*/
|
||||
private function clearLabelCache():void
|
||||
{
|
||||
var cacheLength:int = this.labelsCache.length;
|
||||
for(var i:int = 0; i < cacheLength; i++)
|
||||
{
|
||||
var label:TextField = TextField(this.labelsCache.shift());
|
||||
this.removeChild(label);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
536
com/yahoo/astra/fl/charts/series/Series.as
Executable file
536
com/yahoo/astra/fl/charts/series/Series.as
Executable file
@ -0,0 +1,536 @@
|
||||
/*
|
||||
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.fl.charts.events.ChartEvent;
|
||||
|
||||
import fl.core.InvalidationType;
|
||||
import fl.core.UIComponent;
|
||||
import fl.transitions.easing.Strong;
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
import flash.display.InteractiveObject;
|
||||
import flash.display.Shape;
|
||||
import flash.events.Event;
|
||||
import flash.events.MouseEvent;
|
||||
import flash.utils.Dictionary;
|
||||
import flash.utils.getQualifiedClassName;
|
||||
|
||||
//--------------------------------------
|
||||
// Styles
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* The easing function for animations that occur on data changes.
|
||||
*/
|
||||
[Style(name="animationEasingFunction", type="Function")]
|
||||
|
||||
/**
|
||||
* The duration for animations that occur on data changes.
|
||||
*/
|
||||
[Style(name="animationDuration", type="int")]
|
||||
|
||||
/**
|
||||
* If true, data changes will be displayed with animations. If false, changes will happen instantly.
|
||||
*/
|
||||
[Style(name="animationEnabled", type="Boolean")]
|
||||
|
||||
/**
|
||||
* The base color used by objects displayed in this series.
|
||||
*/
|
||||
[Style(name="color", type="uint")]
|
||||
|
||||
/**
|
||||
* The border color used by programatic skins in this series.
|
||||
*/
|
||||
[Style(name="borderColor", type="uint")]
|
||||
|
||||
/**
|
||||
* The fill color used by programatic skins in this series.
|
||||
*/
|
||||
[Style(name="fillColor", type="uint")]
|
||||
|
||||
/**
|
||||
* The Class used to instantiate each marker's skin.
|
||||
*/
|
||||
[Style(name="markerSkin", type="Class")]
|
||||
|
||||
/**
|
||||
* The size, in pixels, of each marker.
|
||||
*/
|
||||
[Style(name="markerSize", type="Number")]
|
||||
|
||||
/**
|
||||
* The alpha value from 0.0 to 1.0 to use for drawing the markers.
|
||||
*/
|
||||
[Style(name="markerAlpha", type="Number")]
|
||||
|
||||
/**
|
||||
* The alpha value from 0.0 to 1.0 to use for drawing the fills of markers.
|
||||
*/
|
||||
[Style(name="fillAlpha", type="Number")]
|
||||
|
||||
/**
|
||||
* The alpha value from 0.0 to 1.0 to use for drawing the border of markers.
|
||||
*/
|
||||
[Style(name="borderAlpha", type="Number")]
|
||||
|
||||
/**
|
||||
* Functionality common to most series. Generally, a <code>Series</code> object
|
||||
* shouldn't be instantiated directly. Instead, a subclass with a concrete
|
||||
* implementation should be used.
|
||||
*
|
||||
* @author Josh Tynjala
|
||||
*/
|
||||
public class Series extends UIComponent implements ISeries
|
||||
{
|
||||
|
||||
//--------------------------------------
|
||||
// Class Variables
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private static var defaultStyles:Object =
|
||||
{
|
||||
markerSkin: Shape, //an empty display object
|
||||
fillColor: null,
|
||||
markerSize: 10,
|
||||
markerAlpha: 1.0,
|
||||
fillAlpha: 1.0,
|
||||
borderAlpha: 1.0,
|
||||
animationEnabled: true,
|
||||
animationEasingFunction: fl.transitions.easing.Strong.easeOut,
|
||||
animationDuration: 500,
|
||||
borderColor: null,
|
||||
color: 0x00b8bf
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
private static const RENDERER_STYLES:Object =
|
||||
{
|
||||
skin: "markerSkin",
|
||||
fillColor: "fillColor",
|
||||
borderColor: "borderColor",
|
||||
color: "color",
|
||||
fillAlpha: "fillAlpha",
|
||||
borderAlpha: "borderAlpha"
|
||||
};
|
||||
|
||||
//--------------------------------------
|
||||
// Class Methods
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @copy fl.core.UIComponent#getStyleDefinition()
|
||||
*/
|
||||
public static function getStyleDefinition():Object
|
||||
{
|
||||
return mergeStyles(defaultStyles, UIComponent.getStyleDefinition());
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// Constructor
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function Series(dataProvider:Object = null)
|
||||
{
|
||||
super();
|
||||
this._dataProvider = dataProvider;
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// Properties
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
protected var markers:Array = [];
|
||||
|
||||
/**
|
||||
* @private
|
||||
* A set of flags to indicate if special considerations need to be taken for the markers.
|
||||
*/
|
||||
protected var markerInvalidHash:Dictionary = new Dictionary(true);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Storage for the chart property.
|
||||
*/
|
||||
private var _chart:Object;
|
||||
|
||||
/**
|
||||
* @copy com.yahoo.astra.fl.charts.ISeries#chart
|
||||
*/
|
||||
public function get chart():Object
|
||||
{
|
||||
return this._chart;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set chart(value:Object):void
|
||||
{
|
||||
this._chart = value;
|
||||
//this is a fun hack to ensure that series know if their parent charts are in live preview
|
||||
if(this._chart == null || this._chart.parent == null)
|
||||
{
|
||||
this.isLivePreview = false;
|
||||
}
|
||||
var className:String;
|
||||
try
|
||||
{
|
||||
className = getQualifiedClassName(this._chart.parent);
|
||||
}
|
||||
catch (e:Error) {}
|
||||
this.isLivePreview = (className == "fl.livepreview::LivePreviewParent");
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* A lookup system to convert from an item to its item renderer.
|
||||
*/
|
||||
private var _itemToItemRendererHash:Dictionary = new Dictionary();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Storage for the itemRenderer property.
|
||||
*/
|
||||
private var _itemRenderer:Object = SeriesItemRenderer;
|
||||
|
||||
/**
|
||||
* The class used to instantiate item renderers.
|
||||
*/
|
||||
public function get itemRenderer():Object
|
||||
{
|
||||
return this._itemRenderer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set itemRenderer(value:Object):void
|
||||
{
|
||||
if(this._itemRenderer != value)
|
||||
{
|
||||
this._itemRenderer = value;
|
||||
this.invalidate("itemRenderer");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Storage for the data property.
|
||||
*/
|
||||
private var _dataProvider:Object;
|
||||
|
||||
/**
|
||||
* @copy com.yahoo.astra.fl.charts.ISeries#data
|
||||
*/
|
||||
public function get dataProvider():Object
|
||||
{
|
||||
return this._dataProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set dataProvider(value:Object):void
|
||||
{
|
||||
if(this._dataProvider != value)
|
||||
{
|
||||
//if we get XML data and it isn't an XMLList,
|
||||
//ignore the root tag
|
||||
if(value is XML && !(value is XMLList))
|
||||
{
|
||||
value = value.elements();
|
||||
}
|
||||
|
||||
if(value is XMLList)
|
||||
{
|
||||
value = XMLList(value).copy();
|
||||
}
|
||||
else if(value is Array)
|
||||
{
|
||||
value = (value as Array).concat();
|
||||
}
|
||||
|
||||
this._dataProvider = value;
|
||||
this.dispatchEvent(new Event("dataChange"));
|
||||
this.invalidate(InvalidationType.DATA);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Storage for the displayName property.
|
||||
*/
|
||||
private var _displayName:String;
|
||||
|
||||
/**
|
||||
* @copy com.yahoo.astra.fl.charts.ISeries#data
|
||||
*/
|
||||
public function get displayName():String
|
||||
{
|
||||
return this._displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set displayName(value:String):void
|
||||
{
|
||||
this._displayName = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @copy com.yahoo.astra.fl.charts.ISeries#length
|
||||
*/
|
||||
public function get length():int
|
||||
{
|
||||
if(this._dataProvider is Array)
|
||||
{
|
||||
return (this._dataProvider as Array).length;
|
||||
}
|
||||
else if(this._dataProvider is XMLList)
|
||||
{
|
||||
return (this._dataProvider as XMLList).length();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// Public Methods
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @copy com.yahoo.astra.fl.charts.ISeries#clone()
|
||||
*/
|
||||
public function clone():ISeries
|
||||
{
|
||||
var series:Series = new Series();
|
||||
series.dataProvider = this.dataProvider;
|
||||
series.displayName = this.displayName;
|
||||
return series;
|
||||
}
|
||||
|
||||
/**
|
||||
* @copy com.yahoo.astra.fl.charts.ISeries#itemRendererToIndex()
|
||||
*/
|
||||
public function itemRendererToIndex(renderer:ISeriesItemRenderer):int
|
||||
{
|
||||
return this.markers.indexOf(renderer);
|
||||
}
|
||||
|
||||
public function itemToItemRenderer(item:Object):ISeriesItemRenderer
|
||||
{
|
||||
return this._itemToItemRendererHash[item] as ISeriesItemRenderer;
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// Protected Methods
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
override protected function draw():void
|
||||
{
|
||||
//the class for the item renderers has changed. remove all markers
|
||||
//so that they may be recreated.
|
||||
if(this.isInvalid("itemRenderer"))
|
||||
{
|
||||
this.removeAllMarkers();
|
||||
}
|
||||
|
||||
if(this.isInvalid("itemRenderer", InvalidationType.DATA, InvalidationType.STYLES))
|
||||
{
|
||||
this.refreshMarkers();
|
||||
this._itemToItemRendererHash = new Dictionary(true);
|
||||
var itemCount:int = this.markers.length;
|
||||
for(var i:int = 0; i < itemCount; i++)
|
||||
{
|
||||
var marker:ISeriesItemRenderer = this.markers[i] as ISeriesItemRenderer;
|
||||
if(this.isInvalid(InvalidationType.DATA)) //update data if needed
|
||||
{
|
||||
marker.data = this.dataProvider[i];
|
||||
}
|
||||
this._itemToItemRendererHash[marker.data] = marker;
|
||||
|
||||
var markerComponent:UIComponent = marker as UIComponent;
|
||||
this.copyStylesToChild(markerComponent, RENDERER_STYLES);
|
||||
markerComponent.drawNow();
|
||||
}
|
||||
}
|
||||
|
||||
super.draw();
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* All markers are removed from the display list.
|
||||
*/
|
||||
protected function removeAllMarkers():void
|
||||
{
|
||||
var markerCount:int = this.markers.length;
|
||||
for(var i:int = 0; i < markerCount; i++)
|
||||
{
|
||||
var marker:ISeriesItemRenderer = this.markers.pop() as ISeriesItemRenderer;
|
||||
marker.removeEventListener(MouseEvent.ROLL_OVER, markerRollOverHandler);
|
||||
marker.removeEventListener(MouseEvent.ROLL_OUT, markerRollOutHandler);
|
||||
marker.removeEventListener(MouseEvent.CLICK, markerClickHandler);
|
||||
marker.removeEventListener(MouseEvent.DOUBLE_CLICK, markerDoubleClickHandler);
|
||||
this.removeChild(DisplayObject(marker));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Add or remove markers as needed. current markers will be reused.
|
||||
*/
|
||||
protected function refreshMarkers():void
|
||||
{
|
||||
var itemCount:int = this.length;
|
||||
var difference:int = itemCount - this.markers.length;
|
||||
if(difference > 0)
|
||||
{
|
||||
for(var i:int = 0; i < difference; i++)
|
||||
{
|
||||
var marker:ISeriesItemRenderer = new this.itemRenderer();
|
||||
marker.series = this;
|
||||
InteractiveObject(marker).doubleClickEnabled = true;
|
||||
marker.addEventListener(MouseEvent.ROLL_OVER, markerRollOverHandler, false, 0, true);
|
||||
marker.addEventListener(MouseEvent.ROLL_OUT, markerRollOutHandler, false, 0, true);
|
||||
marker.addEventListener(MouseEvent.CLICK, markerClickHandler, false, 0, true);
|
||||
marker.addEventListener(MouseEvent.DOUBLE_CLICK, markerDoubleClickHandler, false, 0, true);
|
||||
this.addChild(DisplayObject(marker));
|
||||
this.markers.push(marker);
|
||||
this.invalidateMarker(marker);
|
||||
}
|
||||
}
|
||||
else if(difference < 0)
|
||||
{
|
||||
difference = Math.abs(difference);
|
||||
for(i = 0; i < difference; i++)
|
||||
{
|
||||
marker = this.markers.pop() as ISeriesItemRenderer;
|
||||
this.validateMarker(marker);
|
||||
marker.removeEventListener(MouseEvent.ROLL_OVER, markerRollOverHandler);
|
||||
marker.removeEventListener(MouseEvent.ROLL_OUT, markerRollOutHandler);
|
||||
marker.removeEventListener(MouseEvent.CLICK, markerClickHandler);
|
||||
marker.removeEventListener(MouseEvent.DOUBLE_CLICK, markerDoubleClickHandler);
|
||||
this.removeChild(DisplayObject(marker));
|
||||
}
|
||||
}
|
||||
|
||||
var markerCount:int = this.markers.length;
|
||||
for(i = 0; i < markerCount; i++)
|
||||
{
|
||||
marker = ISeriesItemRenderer(this.markers[i]);
|
||||
marker.data = this.dataProvider[i];
|
||||
DisplayObject(marker).alpha = this.getStyleValue("markerAlpha") as Number;
|
||||
this.copyStylesToChild(UIComponent(marker), RENDERER_STYLES);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether special considerations should be taken for a newly created marker.
|
||||
*/
|
||||
protected function isMarkerInvalid(marker:ISeriesItemRenderer):Boolean
|
||||
{
|
||||
return this.markerInvalidHash[marker];
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidates a marker (considered new).
|
||||
*/
|
||||
protected function invalidateMarker(marker:ISeriesItemRenderer):void
|
||||
{
|
||||
markerInvalidHash[marker] = true;
|
||||
DisplayObject(marker).visible = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* We never want the series to callLater after invalidating.
|
||||
* The chart will ALWAYS handle drawing.
|
||||
*/
|
||||
override public function invalidate(property:String = InvalidationType.ALL, callLater:Boolean = true):void
|
||||
{
|
||||
//never call later!
|
||||
super.invalidate(property, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a marker valid. To be used by subclasses.
|
||||
*/
|
||||
protected function validateMarker(marker:ISeriesItemRenderer):void
|
||||
{
|
||||
DisplayObject(marker).visible = true;
|
||||
delete markerInvalidHash[marker];
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Notify the parent chart that the user's mouse is over this marker.
|
||||
*/
|
||||
protected function markerRollOverHandler(event:MouseEvent):void
|
||||
{
|
||||
var itemRenderer:ISeriesItemRenderer = ISeriesItemRenderer(event.currentTarget);
|
||||
var index:int = this.itemRendererToIndex(itemRenderer);
|
||||
var item:Object = this.dataProvider[index];
|
||||
var rollOver:ChartEvent = new ChartEvent(ChartEvent.ITEM_ROLL_OVER, index, item, itemRenderer, this);
|
||||
this.dispatchEvent(rollOver);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Notify the parent chart that the user's mouse has left this marker.
|
||||
*/
|
||||
protected function markerRollOutHandler(event:MouseEvent):void
|
||||
{
|
||||
var itemRenderer:ISeriesItemRenderer = ISeriesItemRenderer(event.currentTarget);
|
||||
var index:int = this.itemRendererToIndex(itemRenderer);
|
||||
var item:Object = this.dataProvider[index];
|
||||
var rollOut:ChartEvent = new ChartEvent(ChartEvent.ITEM_ROLL_OUT, index, item, itemRenderer, this);
|
||||
this.dispatchEvent(rollOut);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Notify the parent chart that the user clicked this marker.
|
||||
*/
|
||||
protected function markerClickHandler(event:MouseEvent):void
|
||||
{
|
||||
var itemRenderer:ISeriesItemRenderer = ISeriesItemRenderer(event.currentTarget);
|
||||
var index:int = this.itemRendererToIndex(itemRenderer);
|
||||
var item:Object = this.dataProvider[index];
|
||||
var click:ChartEvent = new ChartEvent(ChartEvent.ITEM_CLICK, index, item, itemRenderer, this);
|
||||
this.dispatchEvent(click);
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Notify the parent chart that the user double-clicked this marker.
|
||||
*/
|
||||
protected function markerDoubleClickHandler(event:MouseEvent):void
|
||||
{
|
||||
var itemRenderer:ISeriesItemRenderer = ISeriesItemRenderer(event.currentTarget);
|
||||
var index:int = this.itemRendererToIndex(itemRenderer);
|
||||
var item:Object = this.dataProvider[index];
|
||||
var doubleClick:ChartEvent = new ChartEvent(ChartEvent.ITEM_DOUBLE_CLICK, index, item, itemRenderer, this);
|
||||
this.dispatchEvent(doubleClick);
|
||||
}
|
||||
}
|
||||
}
|
||||
190
com/yahoo/astra/fl/charts/series/SeriesItemRenderer.as
Executable file
190
com/yahoo/astra/fl/charts/series/SeriesItemRenderer.as
Executable file
@ -0,0 +1,190 @@
|
||||
/*
|
||||
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.fl.charts.skins.IProgrammaticSkin;
|
||||
import com.yahoo.astra.fl.utils.UIComponentUtil;
|
||||
|
||||
import fl.core.InvalidationType;
|
||||
import fl.core.UIComponent;
|
||||
|
||||
import flash.display.DisplayObject;
|
||||
|
||||
//--------------------------------------
|
||||
// Styles
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* The DisplayObject subclass used to display the background.
|
||||
*/
|
||||
[Style(name="skin", type="Class")]
|
||||
|
||||
/**
|
||||
* The color used by a skin that uses fill colors.
|
||||
*/
|
||||
[Style(name="fillColor", type="uint")]
|
||||
|
||||
/**
|
||||
* The color used by a skin that uses border colors.
|
||||
*/
|
||||
[Style(name="borderColor", type="uint")]
|
||||
|
||||
/**
|
||||
* The alpha used by a skin that has a fill alpha.
|
||||
*/
|
||||
[Style(name="fillAlpha", type="Number")]
|
||||
|
||||
/**
|
||||
* The alpha used by a skin that has a border alpha.
|
||||
*/
|
||||
[Style(name="borderAlpha", type="Number")]
|
||||
|
||||
/**
|
||||
* The primary item renderer class for a chart series.
|
||||
*
|
||||
* @see com.yahoo.astra.fl.charts.series.Series
|
||||
*
|
||||
* @author Josh Tynjala
|
||||
*/
|
||||
public class SeriesItemRenderer extends UIComponent implements ISeriesItemRenderer
|
||||
{
|
||||
|
||||
//--------------------------------------
|
||||
// Constructor
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function SeriesItemRenderer()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// Properties
|
||||
//--------------------------------------
|
||||
|
||||
protected var aspectRatio:Number = 1;
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
protected var skin:DisplayObject;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Storage for the series property.
|
||||
*/
|
||||
private var _series:ISeries;
|
||||
|
||||
public function get series():ISeries
|
||||
{
|
||||
return this._series;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set series(value:ISeries):void
|
||||
{
|
||||
if(this._series != value)
|
||||
{
|
||||
this._series = value;
|
||||
this.invalidate(InvalidationType.DATA)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Storage for the data property.
|
||||
*/
|
||||
private var _data:Object;
|
||||
|
||||
/**
|
||||
* @copy com.yahoo.astra.fl.charts.IDataTipRenderer#data
|
||||
*/
|
||||
public function get data():Object
|
||||
{
|
||||
return this._data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
public function set data(value:Object):void
|
||||
{
|
||||
if(this._data != value)
|
||||
{
|
||||
this._data = value;
|
||||
this.invalidate(InvalidationType.DATA);
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// Protected Methods
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
override protected function draw():void
|
||||
{
|
||||
var stylesInvalid:Boolean = this.isInvalid(InvalidationType.STYLES);
|
||||
var sizeInvalid:Boolean = this.isInvalid(InvalidationType.SIZE);
|
||||
|
||||
if(stylesInvalid)
|
||||
{
|
||||
if(this.skin)
|
||||
{
|
||||
this.removeChild(this.skin);
|
||||
this.skin = null;
|
||||
}
|
||||
|
||||
var SkinType:Object = this.getStyleValue("skin");
|
||||
this.skin = UIComponentUtil.getDisplayObjectInstance(this, SkinType);
|
||||
if(this.skin)
|
||||
{
|
||||
this.addChildAt(this.skin, 0);
|
||||
|
||||
if(this.skin is UIComponent)
|
||||
{
|
||||
(this.skin as UIComponent).drawNow();
|
||||
}
|
||||
this.aspectRatio = this.skin.width / this.skin.height;
|
||||
}
|
||||
}
|
||||
|
||||
if(this.skin && (stylesInvalid || sizeInvalid))
|
||||
{
|
||||
this.skin.width = this.width;
|
||||
this.skin.height = this.height;
|
||||
|
||||
if(this.skin is IProgrammaticSkin)
|
||||
{
|
||||
var color:uint = this.getStyleValue("color") as uint;
|
||||
var fillColor:uint = this.getStyleValue("fillColor") != null ? this.getStyleValue("fillColor") as uint : color;
|
||||
(this.skin as IProgrammaticSkin).fillColor = fillColor;
|
||||
|
||||
var borderColor:uint = this.getStyleValue("borderColor") != null ? this.getStyleValue("borderColor") as uint : color;
|
||||
(this.skin as IProgrammaticSkin).borderColor = borderColor;
|
||||
|
||||
var borderAlpha:Number = this.getStyleValue("borderAlpha") as Number;
|
||||
(this.skin as IProgrammaticSkin).borderAlpha = borderAlpha;
|
||||
var fillAlpha:Number = this.getStyleValue("fillAlpha") as Number;
|
||||
(this.skin as IProgrammaticSkin).fillAlpha = fillAlpha;
|
||||
}
|
||||
|
||||
if(this.skin is UIComponent)
|
||||
{
|
||||
(this.skin as UIComponent).drawNow();
|
||||
}
|
||||
}
|
||||
|
||||
super.draw();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
132
com/yahoo/astra/fl/charts/series/StackedBarSeries.as
Executable file
132
com/yahoo/astra/fl/charts/series/StackedBarSeries.as
Executable file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
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.fl.charts.IChart;
|
||||
import com.yahoo.astra.fl.charts.ChartUtil;
|
||||
import com.yahoo.astra.fl.charts.axes.IAxis;
|
||||
import com.yahoo.astra.fl.charts.axes.IOriginAxis;
|
||||
import com.yahoo.astra.fl.charts.axes.IClusteringAxis;
|
||||
|
||||
/**
|
||||
* A bar series type that stacks.
|
||||
*
|
||||
* @author Josh Tynjala
|
||||
*/
|
||||
public class StackedBarSeries extends BarSeries implements IStackedSeries
|
||||
{
|
||||
|
||||
//--------------------------------------
|
||||
// Constructor
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function StackedBarSeries(data:Object=null)
|
||||
{
|
||||
super(data);
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// Protected Methods
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override protected function calculateYOffset(valueAxis:IOriginAxis, otherAxis:IAxis, markerSizes:Array, totalMarkerSize:Number, allSeriesOfType:Array):Number
|
||||
{
|
||||
if(!ChartUtil.isStackingAllowed(valueAxis, this))
|
||||
{
|
||||
return super.calculateYOffset(valueAxis, otherAxis, markerSizes, totalMarkerSize, allSeriesOfType);
|
||||
}
|
||||
|
||||
var seriesIndex:int = allSeriesOfType.indexOf(this);
|
||||
return -(markerSizes[seriesIndex] as Number) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override protected function calculateTotalMarkerSize(axis:IAxis, sizes:Array):Number
|
||||
{
|
||||
if(!ChartUtil.isStackingAllowed(axis, this))
|
||||
{
|
||||
return super.calculateTotalMarkerSize(axis, sizes);
|
||||
}
|
||||
|
||||
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);
|
||||
totalMarkerSize = Math.max(totalMarkerSize, markerSize);
|
||||
}
|
||||
return totalMarkerSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override 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);
|
||||
}
|
||||
return Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Determines the origin of the bar. Either the axis origin or the
|
||||
* stacked value.
|
||||
*/
|
||||
override protected function calculateOriginValue(index:int, axis:IOriginAxis, allSeriesOfType:Array):Object
|
||||
{
|
||||
if(!ChartUtil.isStackingAllowed(axis, this))
|
||||
{
|
||||
return super.calculateOriginValue(index, axis, allSeriesOfType);
|
||||
}
|
||||
|
||||
var seriesIndex:int = allSeriesOfType.indexOf(this);
|
||||
var originValue:Object = axis.origin;
|
||||
if(seriesIndex > 0)
|
||||
{
|
||||
var previousSeries:StackedBarSeries = StackedBarSeries(allSeriesOfType[seriesIndex - 1]);
|
||||
|
||||
var isPositive:Boolean = IChart(this.chart).itemToAxisValue(this, index, axis) >= 0;
|
||||
|
||||
for(var i:int = seriesIndex - 1; i > -1; i--)
|
||||
{
|
||||
if(isPositive)
|
||||
{
|
||||
if(IChart(this.chart).itemToAxisValue(StackedBarSeries(allSeriesOfType[i]), index, axis) > 0)
|
||||
{
|
||||
originValue = IChart(this.chart).itemToAxisValue(StackedBarSeries(allSeriesOfType[i]), index, axis);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(IChart(this.chart).itemToAxisValue(StackedBarSeries(allSeriesOfType[i]), index, axis) < 0)
|
||||
{
|
||||
originValue = IChart(this.chart).itemToAxisValue(StackedBarSeries(allSeriesOfType[i]), index, axis);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return originValue;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
130
com/yahoo/astra/fl/charts/series/StackedColumnSeries.as
Executable file
130
com/yahoo/astra/fl/charts/series/StackedColumnSeries.as
Executable file
@ -0,0 +1,130 @@
|
||||
/*
|
||||
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.fl.charts.ChartUtil;
|
||||
import com.yahoo.astra.fl.charts.IChart;
|
||||
import com.yahoo.astra.fl.charts.axes.IAxis;
|
||||
import com.yahoo.astra.fl.charts.axes.IOriginAxis;
|
||||
import com.yahoo.astra.fl.charts.axes.IClusteringAxis;
|
||||
|
||||
/**
|
||||
* A column series type that stacks.
|
||||
*
|
||||
* @author Josh Tynjala
|
||||
*/
|
||||
public class StackedColumnSeries extends ColumnSeries implements IStackedSeries
|
||||
{
|
||||
|
||||
//--------------------------------------
|
||||
// Constructor
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function StackedColumnSeries(data:Object = null)
|
||||
{
|
||||
super(data);
|
||||
}
|
||||
|
||||
//--------------------------------------
|
||||
// Protected Methods
|
||||
//--------------------------------------
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override protected function calculateXOffset(valueAxis:IOriginAxis, otherAxis:IAxis, markerSizes:Array, totalMarkerSize:Number, allSeriesOfType:Array):Number
|
||||
{
|
||||
if(!ChartUtil.isStackingAllowed(valueAxis, this))
|
||||
{
|
||||
return super.calculateXOffset(valueAxis, otherAxis, markerSizes, totalMarkerSize, allSeriesOfType);
|
||||
}
|
||||
|
||||
var seriesIndex:int = allSeriesOfType.indexOf(this);
|
||||
return -(markerSizes[seriesIndex] as Number) / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override protected function calculateTotalMarkerSize(axis:IAxis, sizes:Array):Number
|
||||
{
|
||||
if(!ChartUtil.isStackingAllowed(axis, this))
|
||||
{
|
||||
return super.calculateTotalMarkerSize(axis, sizes);
|
||||
}
|
||||
|
||||
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:ColumnSeries = ColumnSeries(allSeriesOfType[i]);
|
||||
var markerSize:Number = this.calculateMarkerSize(series, axis);
|
||||
sizes.push(markerSize);
|
||||
totalMarkerSize = Math.max(totalMarkerSize, markerSize);
|
||||
}
|
||||
return totalMarkerSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
override protected function calculateMaximumAllowedMarkerSize(axis:IAxis):Number
|
||||
{
|
||||
if(axis is IClusteringAxis)
|
||||
{
|
||||
var allSeriesOfType:Array = ChartUtil.findSeriesOfType(this, this.chart as IChart);
|
||||
return (this.width / IClusteringAxis(axis).clusterCount);
|
||||
}
|
||||
return Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* Determines the origin of the column. Either the axis origin or the
|
||||
* stacked value.
|
||||
*/
|
||||
override protected function calculateOriginValue(index:int, axis:IOriginAxis, allSeriesOfType:Array):Object
|
||||
{
|
||||
if(!ChartUtil.isStackingAllowed(axis, this))
|
||||
{
|
||||
return super.calculateOriginValue(index, axis, allSeriesOfType);
|
||||
}
|
||||
|
||||
var seriesIndex:int = allSeriesOfType.indexOf(this);
|
||||
var originValue:Object = axis.origin;
|
||||
if(seriesIndex > 0)
|
||||
{
|
||||
var previousSeries:StackedColumnSeries = StackedColumnSeries(allSeriesOfType[seriesIndex - 1]);
|
||||
|
||||
var isPositive:Boolean = IChart(this.chart).itemToAxisValue(this, index, axis) >= 0;
|
||||
|
||||
for(var i:int = seriesIndex - 1; i > -1; i--)
|
||||
{
|
||||
if(isPositive)
|
||||
{
|
||||
if(IChart(this.chart).itemToAxisValue(StackedColumnSeries(allSeriesOfType[i]), index, axis) > 0)
|
||||
{
|
||||
originValue = IChart(this.chart).itemToAxisValue(StackedColumnSeries(allSeriesOfType[i]), index, axis);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(IChart(this.chart).itemToAxisValue(StackedColumnSeries(allSeriesOfType[i]), index, axis) < 0)
|
||||
{
|
||||
originValue = IChart(this.chart).itemToAxisValue(StackedColumnSeries(allSeriesOfType[i]), index, axis);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return originValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user