first commit

This commit is contained in:
2020-10-20 00:58:15 +02:00
commit 7f1b9bfca5
222 changed files with 56918 additions and 0 deletions

View 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);
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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;
}
}

View 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
}
}

View 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;
}
}

View 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;
}
}

View 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
{
}
}

View 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();
}
}
}

View 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);
}
}
}
}

View 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);
}
}
}

View 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();
}
}
}

View 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;
}
}
}

View 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;
}
}
}