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,51 @@
/*
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.axes
{
/**
* Positioning and other data used by an IAxisRenderer to draw
* items like ticks. This data is created by an IAxis instance.
*
* @author Josh Tynjala
* @see IAxis
* @see IAxisRenderer
*/
public class AxisData
{
//--------------------------------------
// Constructor
//--------------------------------------
/**
* Constructor.
*/
public function AxisData(position:Number, value:Object, label:String)
{
this.position = position;
this.value = value;
this.label = label;
}
//--------------------------------------
// Properties
//--------------------------------------
/**
* The position of the item on the axis renderer.
*/
public var position:Number;
/**
* The value of the item.
*/
public var value:Object;
/**
* The label value of the item.
*/
public var label:String;
}
}

View File

@ -0,0 +1,31 @@
/*
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.axes
{
/**
* Orientation values available to <code>IAxis</code> objects.
*
* @author Josh Tynjala
*/
public class AxisOrientation
{
//--------------------------------------
// Constants
//--------------------------------------
/**
* The AxisOrientation.VERTICAL constant specifies that a chart axis
* should be displayed vertically.
*/
public static const VERTICAL:String = "vertical";
/**
* The AxisOrientation.VERTICAL constant specifies that a chart axis
* should be displayed horizontally.
*/
public static const HORIZONTAL:String = "horizontal";
}
}

View File

@ -0,0 +1,272 @@
/*
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.axes
{
import com.yahoo.astra.fl.charts.IChart;
/**
* Implements some of the most common axis functionality
* to prevent duplicate code in IAxis implementations.
*
* <p>This class is not meant to be instantiated directly! It is an abstract base class.</p>
*
* @author Josh Tynjala
*/
public class BaseAxis
{
//--------------------------------------
// Constructor
//--------------------------------------
/**
* Constructor.
*/
public function BaseAxis()
{
}
//--------------------------------------
// Properties
//--------------------------------------
/**
* @private
* Storage for the chart property.
*/
private var _chart:IChart;
/**
* @copy com.yahoo.astra.fl.charts.axes.IAxis#chart
*/
public function get chart():IChart
{
return this._chart;
}
/**
* @private
*/
public function set chart(value:IChart):void
{
this._chart = value;
}
/**
* @private
* Storage for the renderer property.
*/
private var _renderer:IAxisRenderer;
//TODO: Consider having the renderer know about the axis
//rather than the axis knowing about the renderer. This
//change will allow multiple views to this model.
//if this is implemented, a separate controller will be
//needed too.
/**
* The visual renderer applied to this axis.
*/
public function get renderer():IAxisRenderer
{
return this._renderer;
}
/**
* @private
*/
public function set renderer(value:IAxisRenderer):void
{
this._renderer = value;
}
/**
* @private
* Storage for the labelFunction property.
*/
private var _labelFunction:Function;
/**
* @copy com.yahoo.astra.fl.charts.axes.labelFunction
*/
public function get labelFunction():Function
{
return this._labelFunction;
}
/**
* @private
*/
public function set labelFunction(value:Function):void
{
this._labelFunction = value;
}
/**
* @private
* Storage for the reverse property.
*/
private var _reverse:Boolean = false;
/**
* @copy com.yahoo.astra.fl.charts.axes.IAxis#reverse
*/
public function get reverse():Boolean
{
return this._reverse;
}
/**
* @private
*/
public function set reverse(value:Boolean):void
{
this._reverse = value;
}
/**
* @private
* Storage for the title property.
*/
private var _title:String = "";
/**
* @copy com.yahoo.astra.fl.charts.axes.IAxis#title
*/
public function get title():String
{
return this._title;
}
/**
* @private
*/
public function set title(value:String):void
{
this._title = value;
}
/**
* @private
* placeholder for maximum label width
*/
protected var _maxLabelWidth:Number;
/**
* Gets or sets the maximum width of a label
*/
public function get maxLabelWidth():Number
{
return _maxLabelWidth;
}
/**
* @private (setter)
*/
public function set maxLabelWidth(value:Number):void
{
_maxLabelWidth = value;
}
/**
* @private
* placeholder for maximum label width
*/
protected var _maxLabelHeight:Number;
/**
* Gets or sets the maximum height of a label
*/
public function get maxLabelHeight():Number
{
return _maxLabelHeight;
}
/**
* @private (setter)
*/
public function set maxLabelHeight(value:Number):void
{
_maxLabelHeight = value;
}
/**
* @private
*/
protected var _dataProvider:Array;
/**
* Data provider for the axis
*/
public function get dataProvider():Array
{
return _dataProvider;
}
/**
* @private (setter)
*/
public function set dataProvider(value:Array):void
{
_dataProvider = value;
this.parseDataProvider();
}
/**
* @private
*/
private var _labelSpacing:Number = 2;
/**
* @copy com.yahoo.astra.fl.charts.axes.IAxis#labelSpacing
*/
public function get labelSpacing():Number
{
return _labelSpacing;
}
/**
* @private (setter)
*/
public function set labelSpacing(value:Number):void
{
if(value != _labelSpacing) _labelSpacing = value;
}
//--------------------------------------
// Public Methods
//--------------------------------------
/**
* @copy com.yahoo.astra.fl.charts.axes.IAxis#valueToLabel()
*/
public function valueToLabel(value:Object):String
{
if(value == null)
{
return "";
}
var text:String = value.toString();
if(this._labelFunction != null)
{
text = this._labelFunction(value);
}
if(text == null)
{
text = "";
}
return text;
}
//--------------------------------------
// Protected Methods
//--------------------------------------
/**
* @private
*/
protected function parseDataProvider():void{}
}
}

View File

@ -0,0 +1,378 @@
/*
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.axes
{
import com.yahoo.astra.fl.charts.series.ISeries;
import com.yahoo.astra.fl.charts.CartesianChart;
import fl.core.UIComponent;
import flash.text.TextFormat;
/**
* An axis type representing a set of categories.
*
* @author Josh Tynjala
*/
public class CategoryAxis extends BaseAxis implements IAxis, IClusteringAxis
{
//--------------------------------------
// Constructor
//--------------------------------------
/**
* Constructor.
*/
public function CategoryAxis()
{
}
//--------------------------------------
// Properties
//--------------------------------------
/**
* @private
* Used to determine the position of an item based on a value.
*/
protected var categorySize:Number = 0;
/**
* @private
* Storage for the categoryNames property.
*/
private var _categoryNames:Array = [];
/**
* @private
* Indicates whether the category labels are user-defined or generated by the axis.
*/
private var _categoryNamesSetByUser:Boolean = false;
/**
* The category labels to display along the axis.
*/
public function get categoryNames():Array
{
return this._categoryNames;
}
/**
* @private
*/
public function set categoryNames(value:Array):void
{
this._categoryNamesSetByUser = value != null && value.length > 0;
if(!this._categoryNamesSetByUser)
{
this._categoryNames = [];
}
else
{
//ensure that all category names are strings
this._categoryNames = getCategoryNames(value);
}
}
/**
* @inheritDoc
*/
public function get clusterCount():int
{
return this.categoryNames.length;
}
/**
* @private
*/
private var _numLabels:Number;
/**
* @private
*/
private var _numLabelsSetByUser:Boolean = false;
/**
* @inheritDoc
*/
public function get numLabels():Number
{
return _numLabels;
}
/**
* @private (setter)
*/
public function set numLabels(value:Number):void
{
if(_numLabelsSetByUser) return;
_numLabels = value;
_numLabelsSetByUser = true;
}
/**
* @private
*/
private var _majorUnit:Number = 1;
/**
* @private
* Holds value for calculateCategoryCount
*/
private var _calculateCategoryCount:Boolean = false;
/**
* Indicates whether or not to calculate the number of categories (ticks and labels)
* when there is not enough room to display all labels on the axis. If set to true, the axis
* will determine the number of categories to plot. If not, all categories will be plotted.
*/
public function get calculateCategoryCount():Boolean
{
return _calculateCategoryCount;
}
/**
* @private (setter)
*/
public function set calculateCategoryCount(value:Boolean):void
{
_calculateCategoryCount = value;
}
//--------------------------------------
// Public Methods
//--------------------------------------
/**
* @inheritDoc
*/
public function valueToLocal(value:Object):Number
{
if(value === null)
{
return NaN;
}
var index:int = this.categoryNames.indexOf(value.toString());
if(index >= 0)
{
var position:int = this.categorySize * index + (this.categorySize / 2);
if(this.reverse)
{
position = this.renderer.length - position;
}
return position;
}
return NaN;
}
/**
* @inheritDoc
*/
public function updateScale():void
{
if(!this._categoryNamesSetByUser)
{
this.autoDetectCategories(this.dataProvider);
}
this.calculateCategorySize();
}
/**
* @inheritDoc
*/
public function getMaxLabel():String
{
var categoryCount:int = this.categoryNames.length;
var maxLength:Number = 0;
var currentLength:Number;
var maxString:String = "x";
for(var i:int = 0; i < categoryCount; i++)
{
currentLength = (this.categoryNames[i].toString()).length;
if(currentLength > maxLength)
{
maxLength = currentLength;
maxString = this.categoryNames[i];
}
}
return this.valueToLabel(maxString) as String;
}
//--------------------------------------
// Private Methods
//--------------------------------------
/**
* @private
* Update the labels by adding or removing some, setting the text, etc.
*/
private function autoDetectCategories(data:Array):void
{
var uniqueCategoryNames:Array = [];
var seriesCount:int = data.length;
for(var i:int = 0; i < seriesCount; i++)
{
var series:ISeries = data[i] as ISeries;
if(!series)
{
continue;
}
var seriesLength:int = series.length;
for(var j:int = 0; j < seriesLength; j++)
{
var category:Object = this.chart.itemToAxisValue(series, j, this);
//names must be unique
if(uniqueCategoryNames.indexOf(category) < 0)
{
uniqueCategoryNames.push(category);
}
}
}
this._categoryNames = getCategoryNames(uniqueCategoryNames.concat());
}
/**
* @private
* Determines the amount of space provided to each category.
*/
private function calculateCategorySize():void
{
var categoryCount:int = this.categoryNames.length;
this.categorySize = this.renderer.length;
if(categoryCount > 0)
{
this.categorySize /= categoryCount;
}
//If the number of labels will not fit on the axis or the user has specified the number of labels to
//display, calculate the major unit.
var maxLabelSize:Number = (this.chart as CartesianChart).horizontalAxis == this ? this.maxLabelWidth : this.maxLabelHeight;
if((this.categorySize < maxLabelSize && this.calculateCategoryCount) || (this._numLabelsSetByUser && this.numLabels != categoryCount))
{
this.calculateMajorUnit();
(this.renderer as ICartesianAxisRenderer).majorUnitSetByUser = false;
}
else
{
(this.renderer as ICartesianAxisRenderer).majorUnitSetByUser = true;
}
this.updateAxisRenderer();
}
/**
* @private
* Calculates which labels to skip if they will not all fit on the axis.
*/
private function calculateMajorUnit():void
{
var overflow:Number = 0;
var rotation:Number = 0;
var chart:CartesianChart = this.chart as CartesianChart;
var maxLabelSize:Number;
if(chart.horizontalAxis == this)
{
maxLabelSize = this.maxLabelWidth;
rotation = chart.getHorizontalAxisStyle("rotation") as Number;
if(rotation >= 0)
{
if(!isNaN(chart.horizontalAxisLabelData.rightLabelOffset)) overflow += chart.horizontalAxisLabelData.rightLabelOffset as Number;
}
if(rotation <= 0)
{
if(!isNaN(chart.horizontalAxisLabelData.leftLabelOffset)) overflow += chart.horizontalAxisLabelData.leftLabelOffset as Number;
}
}
else
{
maxLabelSize = this.maxLabelHeight;
rotation = chart.getVerticalAxisStyle("rotation") as Number;
if(!isNaN(chart.verticalAxisLabelData.topLabelOffset)) overflow = chart.verticalAxisLabelData.topLabelOffset as Number;
}
var labelSpacing:Number = this.labelSpacing;
maxLabelSize += (labelSpacing*2);
var categoryCount:int = this.categoryNames.length;
var maxNumLabels:Number = this.renderer.length/maxLabelSize;
//If the user specified number of labels to display, attempt to show the correct number.
if(this._numLabelsSetByUser)
{
maxNumLabels = Math.min(maxNumLabels, this.numLabels);
}
var tempMajorUnit:Number = Math.ceil(this.categoryNames.length/maxNumLabels);
this._majorUnit = tempMajorUnit;
if(this.renderer.length%tempMajorUnit != 0 && !this._numLabelsSetByUser)
{
var len:Number = Math.min(tempMajorUnit, ((this.renderer.length/2)-tempMajorUnit));
for(var i:int = 0;i < len; i++)
{
tempMajorUnit++;
if(this.renderer.length%tempMajorUnit == 0)
{
this._majorUnit = tempMajorUnit;
break;
}
}
}
}
/**
* @private
* Ensures all values in an array are string values
*/
private function getCategoryNames(value:Array):Array
{
var names:Array = [];
if(value != null && value.length > 0)
{
for(var i:int = 0; i < value.length; i++)
{
names.push(value[i].toString());
}
}
return names;
}
/**
* @private
*/
protected function updateAxisRenderer():void
{
var ticks:Array = [];
var categoryCount:int = this.categoryNames.length;
var currentCat:int = 0;
while(currentCat < categoryCount && !isNaN(categoryCount))
{
var category:String = this.categoryNames[currentCat];
var position:Number = this.valueToLocal(category);
var label:String = this.valueToLabel(category);
var axisData:AxisData = new AxisData(position, category, label);
ticks.push(axisData);
currentCat += this._majorUnit;
}
//If a major unit has been calculated, we are not plotting all categories.
//Adjust the postion of each tick.
if(this._majorUnit > 1)
{
categoryCount = ticks.length;
var categorySize:Number = this.renderer.length / categoryCount;
for(var i:int = 0; i < categoryCount; i++)
{
(ticks[i] as AxisData).position = categorySize * i + (categorySize/2);
}
}
this.renderer.ticks = ticks;
this.renderer.minorTicks = [];
}
}
}

View File

@ -0,0 +1,990 @@
/*
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.axes
{
import com.yahoo.astra.utils.GeomUtil;
import com.yahoo.astra.utils.NumberUtil;
import com.yahoo.astra.display.BitmapText;
import com.yahoo.astra.utils.DynamicRegistration;
import fl.core.InvalidationType;
import fl.core.UIComponent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
//--------------------------------------
// Styles
//--------------------------------------
//-- Axis
/**
* If false, the axis is not drawn. Titles, labels, ticks, and grid
* lines may still be drawn, however, so you must specifically hide each
* item if nothing should be drawn.
*
* @default true
*/
[Style(name="showAxis", type="Boolean")]
/**
* The line weight, in pixels, for the axis.
*
* @default 1
*/
[Style(name="axisWeight", type="int")]
/**
* The line color for the axis.
*
* @default #888a85
*/
[Style(name="axisColor", type="uint")]
//-- Labels
/**
* If true, labels will be displayed on the axis.
*
* @default true
*/
[Style(name="showLabels", type="Boolean")]
/**
* The distance, in pixels, between a label and the axis.
*
* @default 2
*/
[Style(name="labelDistance", type="Number")]
/**
* The distance, in pixels, between a title and the axis labels.
*
* @default 2
*/
[Style(name="titleDistance", type="Number")]
/**
* If true, labels that overlap previously drawn labels on the axis will be
* hidden. The first and last labels on the axis will always be drawn.
*
* @default true
*/
[Style(name="hideOverlappingLabels", type="Boolean")]
/**
* The angle, in degrees, of the labels on the axis. May be a value
* between <code>-90</code> and <code>90</code>.
*
* @default 0
*/
[Style(name="labelRotation", type="Number")]
/**
* The angle, in degrees, of the title on the axis. May be a value
* between <code>-90</code> and <code>90</code>.
*
* @default 0
*/
[Style(name="titleRotation", type="Number")]
//-- Ticks
/**
* If true, ticks will be displayed on the axis.
*
* @default true
*/
[Style(name="showTicks", type="Boolean")]
/**
* The line weight, in pixels, for the ticks on the axis.
*
* @default 1
*/
[Style(name="tickWeight", type="int")]
/**
* The line color for the ticks on the axis.
*
* @default #888a85
*/
[Style(name="tickColor", type="uint")]
/**
* The length, in pixels, of the ticks on the axis.
*
* @default 4
*/
[Style(name="tickLength", type="Number")]
/**
* The position of the ticks on the axis.
*
* @default "cross"
* @see TickPosition
*/
[Style(name="tickPosition", type="String")]
//-- Minor ticks
/**
* If true, ticks will be displayed on the axis at minor positions.
*
* @default true
*/
[Style(name="showMinorTicks", type="Boolean")]
/**
* The line weight, in pixels, for the minor ticks on the axis.
*
* @default 1
*/
[Style(name="minorTickWeight", type="int")]
/**
* The line color for the minor ticks on the axis.
*
* @default #888a85
*/
[Style(name="minorTickColor", type="uint")]
/**
* The length of the minor ticks on the axis.
*
* @default 3
*/
[Style(name="minorTickLength", type="Number")]
/**
* The position of the minor ticks on the axis.
*
* @default "outside"
* @see com.yahoo.astra.fl.charts.TickPosition
*/
[Style(name="minorTickPosition", type="String")]
//-- Title
/**
* If true, the axis title will be displayed.
*
* @default 2
*/
[Style(name="showTitle", type="Boolean")]
/**
* The TextFormat object to use to render the axis title label.
*
* @default TextFormat("_sans", 11, 0x000000, false, false, false, '', '', TextFormatAlign.LEFT, 0, 0, 0, 0)
*/
[Style(name="titleTextFormat", type="TextFormat")]
/**
* The default axis renderer for a cartesian chart.
*
* @see com.yahoo.astra.fl.charts.CartesianChart
* @author Josh Tynjala
*/
public class DefaultAxisRenderer extends UIComponent implements ICartesianAxisRenderer
{
//--------------------------------------
// Class Variables
//--------------------------------------
/**
* @private
*/
private static var defaultStyles:Object =
{
//axis
showAxis: true,
axisWeight: 1,
axisColor: 0x888a85,
//labels
showLabels: true,
labelDistance: 2,
embedFonts: false,
hideOverlappingLabels: true,
labelRotation: 0,
titleRotation: 0,
titleDistance: 2,
//ticks
showTicks: true,
tickWeight: 1,
tickColor: 0x888a85,
tickLength: 4,
tickPosition: TickPosition.CROSS,
//minor ticks
showMinorTicks: true,
minorTickWeight: 1,
minorTickColor: 0x888a85,
minorTickLength: 3,
minorTickPosition: TickPosition.OUTSIDE,
//title
showTitle: true,
titleTextFormat: new TextFormat("_sans", 11, 0x000000, false, false, false, "", "", TextFormatAlign.LEFT, 0, 0, 0, 0)
};
//--------------------------------------
// Class Methods
//--------------------------------------
/**
* @copy fl.core.UIComponent#getStyleDefinition()
*/
public static function getStyleDefinition():Object
{
return mergeStyles(defaultStyles, UIComponent.getStyleDefinition());
}
//--------------------------------------
// Constructor
//--------------------------------------
/**
* Constructor.
*/
public function DefaultAxisRenderer(orientation:String)
{
super();
this.orientation = orientation;
}
//--------------------------------------
// Properties
//--------------------------------------
/**
* @private
* Storage for the TextFields used for labels on this axis.
*/
protected var labelTextFields:Array = [];
/**
* @private
* A cache to allow the reuse of TextFields when redrawing the renderer.
*/
private var _labelCache:Array;
/**
* @private
* The TextField used to display the axis title.
*/
protected var titleTextField:BitmapText;
/**
* @inheritDoc
*/
public function get length():Number
{
if(this.orientation == AxisOrientation.VERTICAL)
{
return this.contentBounds.height;
}
return this.contentBounds.width;
}
/**
* @private
* Storage for the orientation property.
*/
private var _orientation:String = AxisOrientation.VERTICAL;
/**
* @inheritDoc
*/
public function get orientation():String
{
return this._orientation;
}
/**
* @private
*/
public function set orientation(value:String):void
{
if(this._orientation != value)
{
this._orientation = value;
this.invalidate();
}
}
/**
* @private
* Storage for the contentBounds property.
*/
protected var _contentBounds:Rectangle = new Rectangle();
/**
* @inheritDoc
*/
public function get contentBounds():Rectangle
{
return this._contentBounds;
}
/**
* @private
* Storage for the ticks property.
*/
private var _ticks:Array = [];
/**
* @inheritDoc
*/
public function get ticks():Array
{
return this._ticks;
}
/**
* @private
*/
public function set ticks(value:Array):void
{
this._ticks = value;
this.invalidate(InvalidationType.DATA);
}
/**
* @private
* Storage for the minorTicks property.
*/
private var _minorTicks:Array = [];
/**
* @inheritDoc
*/
public function get minorTicks():Array
{
return this._minorTicks;
}
/**
* @private
*/
public function set minorTicks(value:Array):void
{
this._minorTicks = value;
this.invalidate(InvalidationType.DATA);
}
/**
* @private
* Storage for the title property.
*/
private var _title:String = "";
/**
* @inheritDoc
*/
public function get title():String
{
return this._title;
}
/**
* @private
*/
public function set title(value:String):void
{
if(this._title != value)
{
this._title = value ? value : "";
this.invalidate();
}
}
private var _outerTickOffset:Number = 0;
public function get outerTickOffset():Number
{
return _outerTickOffset;
}
public function set outerTickOffset(value:Number):void
{
_outerTickOffset = value;
}
/**
* @private
* Storage for the majorUnitSetByUser
*/
private var _majorUnitSetByUser:Boolean = false;
/**
* Indicates whether the major unit is user-defined or generated by the axis.
*/
public function get majorUnitSetByUser():Boolean
{
return this._majorUnitSetByUser;
}
/**
* @private (setter)
*/
public function set majorUnitSetByUser(value:Boolean):void
{
this._majorUnitSetByUser = value;
}
//--------------------------------------
// Public Methods
//--------------------------------------
/**
* @inheritDoc
*/
public function updateAxis():void
{
var showLabels:Boolean = this.getStyleValue("showLabels") as Boolean;
var labelDistance:Number = this.getStyleValue("labelDistance") as Number;
var textFormat:TextFormat = this.getStyleValue("textFormat") as TextFormat;
var labelRotation:Number = this.getStyleValue("labelRotation") as Number;
var embedFonts:Boolean = this.getStyleValue("embedFonts") as Boolean;
labelRotation = Math.max(-90, Math.min(labelRotation, 90));
this.createCache();
this.updateLabels(this.ticks, showLabels, textFormat, labelDistance, labelRotation, embedFonts);
this.clearCache();
this.updateTitle();
this.draw();
}
//--------------------------------------
// Protected Methods
//--------------------------------------
/**
* @private
*/
override protected function configUI():void
{
super.configUI();
if(!this.titleTextField)
{
this.titleTextField = new BitmapText();
this.titleTextField.autoSize = TextFieldAutoSize.LEFT;
this.addChild(this.titleTextField);
}
}
/**
* @private
*/
override protected function draw():void
{
this.graphics.clear();
this.positionTitle();
var showTicks:Boolean = this.getStyleValue("showTicks") as Boolean;
var showMinorTicks:Boolean = this.getStyleValue("showMinorTicks") as Boolean;
var filteredMinorTicks:Array = this.minorTicks.concat();
if(showMinorTicks && showTicks)
{
//filter out minor ticks that appear at the same position
//as major ticks.
filteredMinorTicks = filteredMinorTicks.filter(function(item:AxisData, index:int, source:Array):Boolean
{
return !this.ticks.some(function(item2:AxisData, index2:int, source2:Array):Boolean
{
//using fuzzyEquals because we may encounter rounding errors
return NumberUtil.fuzzyEquals(item.position, item2.position, 10);
});
}, this);
}
this.drawAxis();
var showLabels:Boolean = this.getStyleValue("showLabels") as Boolean;
var labelDistance:Number = this.getStyleValue("labelDistance") as Number;
var textFormat:TextFormat = this.getStyleValue("textFormat") as TextFormat;
var labelRotation:Number = this.getStyleValue("labelRotation") as Number;
var embedFonts:Boolean = this.getStyleValue("embedFonts") as Boolean;
labelRotation = Math.max(-90, Math.min(labelRotation, 90));
this.positionLabels(this.ticks, showLabels, labelDistance, labelRotation, embedFonts);
var tickPosition:String = this.getStyleValue("tickPosition") as String;
var tickLength:Number = this.getStyleValue("tickLength") as Number;
var tickWeight:int = this.getStyleValue("tickWeight") as int;
var tickColor:uint = this.getStyleValue("tickColor") as uint;
this.drawTicks(this.ticks, showTicks, tickPosition, tickLength, tickWeight, tickColor);
var minorTickPosition:String = this.getStyleValue("minorTickPosition") as String;
var minorTickLength:Number = this.getStyleValue("minorTickLength") as Number;
var minorTickWeight:int = this.getStyleValue("minorTickWeight") as int;
var minorTickColor:uint = this.getStyleValue("minorTickColor") as uint;
this.drawTicks(filteredMinorTicks, showMinorTicks, minorTickPosition, minorTickLength, minorTickWeight, minorTickColor);
super.draw();
}
/**
* @private
* Updates the title text and styles.
*/
protected function updateTitle():void
{
var showTitle:Boolean = this.getStyleValue("showTitle") as Boolean;
if(!showTitle)
{
this.titleTextField.text = "";
}
else
{
var textFormat:TextFormat = this.getStyleValue("titleTextFormat") as TextFormat;
var embedFonts:Boolean = this.getStyleValue("embedFonts") as Boolean;
this.titleTextField.defaultTextFormat = textFormat;
this.titleTextField.embedFonts = embedFonts;
this.titleTextField.text = this.title;
var titleRotation:Number = this.getStyleValue("titleRotation") as Number;
this.titleTextField.rotation = Math.max(-90, Math.min(titleRotation, 90));;
}
}
/**
* @private
* Positions the title along the axis.
*/
protected function positionTitle():void
{
var showTitle:Boolean = this.getStyleValue("showTitle") as Boolean;
this.titleTextField.visible = showTitle;
if(showTitle)
{
var titleRotation:Number = this.titleTextField.rotation;
if(this.orientation == AxisOrientation.VERTICAL)
{
this.titleTextField.y = this.contentBounds.y + (this.contentBounds.height) / 2;
this.titleTextField.x = 0;
if(titleRotation > 0)
{
this.titleTextField.x += this.titleTextField.contentHeight * (titleRotation/90);
this.titleTextField.y -= this.titleTextField.height/2;
}
else if(titleRotation < 0)
{
this.titleTextField.y += this.titleTextField.height/2;
}
else
{
this.titleTextField.y -= this.titleTextField.height /2;
}
}
else //horizontal
{
this.titleTextField.x = this.contentBounds.x + (this.contentBounds.width/2);
this.titleTextField.y = this.y + this.height - this.titleTextField.height;
if(titleRotation > 0)
{
this.titleTextField.x += (-.5 + titleRotation/90) * this.titleTextField.width;
}
else if(titleRotation < 0)
{
this.titleTextField.x -= (this.titleTextField.width * (1 - Math.abs(titleRotation/180)))/2;
this.titleTextField.y += this.titleTextField.height - (Math.sin((90 - titleRotation) * Math.PI/180) * this.titleTextField.contentHeight);
}
else
{
this.titleTextField.x -= this.titleTextField.width/2;
}
}
}
}
/**
* @private
* Draws the axis origin line.
*/
protected function drawAxis():void
{
var showAxis:Boolean = this.getStyleValue("showAxis") as Boolean;
if(!showAxis)
{
return;
}
var axisWeight:int = this.getStyleValue("axisWeight") as int;
var axisColor:uint = this.getStyleValue("axisColor") as uint;
this.graphics.lineStyle(axisWeight, axisColor);
if(this.orientation == AxisOrientation.VERTICAL)
{
//we round these values because that's what the Flash CS3 components do
//with positions
var verticalX:Number = this.contentBounds.x;
var verticalStart:Number = this.contentBounds.y;
var verticalEnd:Number = this.contentBounds.y + this.contentBounds.height;
this.graphics.moveTo(verticalX, verticalStart);
this.graphics.lineTo(verticalX, verticalEnd);
}
else //horizontal
{
var horizontalY:Number = this.contentBounds.y + this.contentBounds.height;
var horizontalStart:Number = this.contentBounds.x;
var horizontalEnd:Number = this.contentBounds.x + this.contentBounds.width;
this.graphics.moveTo(horizontalStart, horizontalY);
this.graphics.lineTo(horizontalEnd, horizontalY);
}
}
/**
* @private
* Draws a set of ticks on the axis.
*/
protected function drawTicks(data:Array, showTicks:Boolean, tickPosition:String,
tickLength:Number, tickWeight:Number, tickColor:uint):void
{
if(!showTicks)
{
return;
}
this.graphics.lineStyle(tickWeight, tickColor);
var dataCount:int = data.length;
for(var i:int = 0; i < dataCount; i++)
{
var axisData:AxisData = AxisData(data[i]);
if(isNaN(axisData.position))
{
//skip bad positions
continue;
}
var position:Number = axisData.position;
if(this.orientation == AxisOrientation.VERTICAL)
{
position += this.contentBounds.y;
}
else
{
position += this.contentBounds.x;
}
position = position;
switch(tickPosition)
{
case TickPosition.OUTSIDE:
{
if(this.orientation == AxisOrientation.VERTICAL)
{
this.graphics.moveTo(this.contentBounds.x - tickLength, position);
this.graphics.lineTo(this.contentBounds.x, position);
}
else
{
this.graphics.moveTo(position, this.contentBounds.y + this.contentBounds.height);
this.graphics.lineTo(position, this.contentBounds.y + this.contentBounds.height + tickLength);
}
break;
}
case TickPosition.INSIDE:
{
if(this.orientation == AxisOrientation.VERTICAL)
{
this.graphics.moveTo(this.contentBounds.x, position);
this.graphics.lineTo(this.contentBounds.x + tickLength, position);
}
else
{
this.graphics.moveTo(position, this.contentBounds.y + this.contentBounds.height - tickLength);
this.graphics.lineTo(position, this.contentBounds.y + this.contentBounds.height);
}
break;
}
default: //CROSS
{
if(this.orientation == AxisOrientation.VERTICAL)
{
this.graphics.moveTo(this.contentBounds.x - tickLength / 2, position);
this.graphics.lineTo(this.contentBounds.x + tickLength / 2, position);
}
else
{
this.graphics.moveTo(position, this.contentBounds.y + this.contentBounds.height - tickLength / 2);
this.graphics.lineTo(position, this.contentBounds.y + this.contentBounds.height + tickLength / 2);
}
break;
}
}
}
}
/**
* @private
* Saves the label TextFields so that they may be reused.
*/
protected function createCache():void
{
this._labelCache = this.labelTextFields.concat();
this.labelTextFields = [];
}
/**
* @private
* Removes unused label TextFields.
*/
protected function clearCache():void
{
var cacheLength:int = this._labelCache.length;
for(var i:int = 0; i < cacheLength; i++)
{
var label:BitmapText = BitmapText(this._labelCache.shift());
this.removeChild(label);
}
}
/**
* @private
* Creates the labels, sets their text and styles them. Positions the labels too.
*/
protected function updateLabels(data:Array, showLabels:Boolean, textFormat:TextFormat, labelDistance:Number, labelRotation:Number, embedFonts:Boolean):void
{
if(!showLabels)
{
return;
}
var dataCount:int = data.length;
for(var i:int = 0; i < dataCount; i++)
{
var axisData:AxisData = AxisData(data[i]);
var position:Number = axisData.position;
if(isNaN(position))
{
//skip bad positions
continue;
}
var label:BitmapText = this.getLabel();
label.defaultTextFormat = textFormat;
label.embedFonts = embedFonts;
label.rotation = 0;
label.text = axisData.label;
this.labelTextFields.push(label);
}
this.positionLabels(data, showLabels, labelDistance, labelRotation, embedFonts);
}
/**
* @private
* Positions a set of labels on the axis.
*/
protected function positionLabels(labels:Array, showLabels:Boolean, labelDistance:Number, labelRotation:Number, embedFonts:Boolean):void
{
var labelCount:int = this.labelTextFields.length;
for(var i:int = 0; i < labelCount; i++)
{
var label:BitmapText = BitmapText(this.labelTextFields[i]);
label.rotation = 0;
var axisData:AxisData = AxisData(this.ticks[i]);
var position:Number = axisData.position;
if(this.orientation == AxisOrientation.VERTICAL)
{
position += this.contentBounds.y;
if(showLabels)
{
label.x = this.contentBounds.x - labelDistance - this.outerTickOffset - label.width;
label.y = position - label.height/2;
}
if(labelRotation == 0)
{
//do nothing. already ideally positioned
}
else if(labelRotation < 90 && labelRotation > 0)
{
DynamicRegistration.rotate(label, new Point(label.width, label.height / 2), labelRotation);
}
else if(labelRotation > -90 && labelRotation < 0)
{
DynamicRegistration.rotate(label, new Point(label.width, label.height / 2), labelRotation);
}
else if(labelRotation == -90)
{
label.y -= label.width / 2;
DynamicRegistration.rotate(label, new Point(label.width, label.height / 2), labelRotation);
}
else //90
{
label.y += label.width / 2;
DynamicRegistration.rotate(label, new Point(label.width, label.height / 2), labelRotation);
}
}
else //horizontal
{
position += this.contentBounds.x;
if(showLabels)
{
label.y = this.contentBounds.y + this.contentBounds.height + labelDistance + this.outerTickOffset;
}
if(labelRotation > 0)
{
label.x = position;
label.y -= (label.height * labelRotation / 180);
DynamicRegistration.rotate(label, new Point(0, label.height / 2), labelRotation);
}
else if(labelRotation < 0)
{
label.x = position - label.width;
label.y -= (label.height * Math.abs(labelRotation) / 180);
DynamicRegistration.rotate(label, new Point(label.width, label.height / 2), labelRotation);
}
else //labelRotation == 0
{
label.x = position - label.width / 2;
}
}
this.handleOverlappingLabels();
}
}
/**
* @private
* Either creates a new label TextField or retrieves one from the cache.
*/
protected function getLabel():BitmapText
{
if(this._labelCache.length > 0)
{
return BitmapText(this._labelCache.shift());
}
var labelRotation:Number = this.getStyleValue("labelRotation") as Number;
var label:BitmapText = new BitmapText();
label.selectable = false;
label.autoSize = TextFieldAutoSize.LEFT;
this.addChild(label);
return label;
}
/**
* @private
* If labels overlap, some may need to be hidden.
*/
protected function handleOverlappingLabels():void
{
var showLabels:Boolean = this.getStyleValue("showLabels");
var hideOverlappingLabels:Boolean = this.getStyleValue("hideOverlappingLabels");
if(!showLabels || !hideOverlappingLabels)
{
return;
}
var labelRotation:Number = this.getStyleValue("labelRotation") as Number;
var lastVisibleLabel:BitmapText;
var labelCount:int = this.labelTextFields.length;
for(var i:int = 0; i < labelCount; i++)
{
var index:int = labelRotation >= 0 ? i : (labelCount - i - 1);
var label:BitmapText = BitmapText(this.labelTextFields[index]);
label.visible = true;
if(lastVisibleLabel)
{
if(this.orientation == AxisOrientation.HORIZONTAL)
{
if(labelRotation >= 0)
{
var xDifference:Number = label.x - lastVisibleLabel.x;
}
else
{
xDifference = (lastVisibleLabel.x + lastVisibleLabel.textWidth) - (label.x + label.textWidth);
}
if(lastVisibleLabel.textWidth > xDifference)
{
var offset:Point = Point.polar(xDifference, GeomUtil.degreesToRadians(labelRotation));
if(Math.abs(offset.y) <= label.textHeight)
{
label.visible = false;
}
}
/*
var xDifference:Number;
var maxWidth:Number;
if(labelRotation > 0)
{
xDifference = Math.abs(label.x - lastVisibleLabel.x);
maxWidth = lastVisibleLabel.rotationWidth;
}
else
{
xDifference = Math.abs((lastVisibleLabel.x + lastVisibleLabel.width) - (label.x + label.rotationWidth));
maxWidth = label.rotationWidth;
}
if(maxWidth > xDifference)
{
label.visible = false;
}
*/
}
else //vertical
{
/*
var yDifference:Number = Math.abs(lastVisibleLabel.y - label.y);
var maxHeight:Number;
if(lastVisibleLabel.y > label.y)
{
maxHeight = label.rotationHeight;
}
else
{
maxHeight = lastVisibleLabel.rotationHeight;
}
if(maxHeight > yDifference)
{
label.visible = false;
}
*/
if(labelRotation >= 0)
{
var yDifference:Number = lastVisibleLabel.y - label.y;
}
else
{
yDifference = (lastVisibleLabel.y + lastVisibleLabel.textHeight) - (label.y + label.textHeight);
}
yDifference = Math.abs(yDifference);
if(lastVisibleLabel.textHeight > yDifference)
{
offset = Point.polar(yDifference, GeomUtil.degreesToRadians(labelRotation));
if(offset.x <= label.textWidth)
{
label.visible = false;
}
}
}
}
if(label.visible)
{
lastVisibleLabel = label;
}
}
}
}
}

View File

@ -0,0 +1,191 @@
/*
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.axes
{
import com.yahoo.astra.utils.NumberUtil;
import fl.core.UIComponent;
/**
* Renders grid lines associated with a cartesian axis.
*
* @author Josh Tynjala
*/
public class DefaultGridLinesRenderer extends UIComponent implements IGridLinesRenderer
{
//--------------------------------------
// Constructor
//--------------------------------------
/**
* Constructor.
*/
public function DefaultGridLinesRenderer()
{
super();
}
//--------------------------------------
// Properties
//--------------------------------------
/**
* @private
* Storage for the axisRenderer property.
*/
private var _axisRenderer:IAxisRenderer;
/**
* @inheritDoc
*/
public function get axisRenderer():IAxisRenderer
{
return this._axisRenderer;
}
/**
* @private
*/
public function set axisRenderer(value:IAxisRenderer):void
{
if(this._axisRenderer != value)
{
this._axisRenderer = value;
this.invalidate();
}
}
//--------------------------------------
// Protected Methods
//--------------------------------------
/**
* @private
*/
override protected function draw():void
{
this.graphics.clear();
if(!this.axisRenderer)
{
return;
}
var showLines:Boolean = this.getStyleValue("showLines") as Boolean;
var showMinorLines:Boolean = this.getStyleValue("showMinorLines") as Boolean;
//grab the line and minor line data from the axis renderer
var lines:Array = this.axisRenderer.ticks.concat();
var minorLines:Array = this.axisRenderer.minorTicks.concat();
if(showMinorLines && showLines)
{
//filter out minor ticks that appear at the same position
//as major ticks.
minorLines = minorLines.filter(function(item:AxisData, index:int, source:Array):Boolean
{
return !lines.some(function(item2:AxisData, index2:int, source2:Array):Boolean
{
//using fuzzyEquals because we may encounter rounding errors
return NumberUtil.fuzzyEquals(item.position, item2.position, 10);
});
});
}
var lineWeight:int = this.getStyleValue("lineWeight") as int;
var lineColor:uint = this.getStyleValue("lineColor") as uint;
var fillColors:Array = this.getStyleValue("fillColors") as Array;
var fillAlphas:Array = this.getStyleValue("fillAlphas") as Array;
this.drawLines(lines, showLines, lineWeight, lineColor, fillColors, fillAlphas);
var minorLineWeight:int = this.getStyleValue("minorLineWeight") as int;
var minorLineColor:uint = this.getStyleValue("minorLineColor") as uint;
this.drawLines(minorLines, showMinorLines, minorLineWeight, minorLineColor);
super.draw();
}
/**
* Draws a set of lines based on AxisData positioning.
*/
protected function drawLines(data:Array, showLines:Boolean, lineWeight:Number, lineColor:uint, fillColors:Array = null, fillAlphas:Array = null):void
{
var lastPosition:Number;
var renderer:ICartesianAxisRenderer = ICartesianAxisRenderer(this.axisRenderer);
var dataCount:int = data.length;
var showZeroGridLine:Boolean = this.getStyleValue("showZeroGridLine") as Boolean;
var zeroGridLineWeight:Number = this.getStyleValue("zeroGridLineWeight") as Number;
var zeroGridLineColor:uint = this.getStyleValue("zeroGridLineColor") as uint;
for(var i:int = 0; i < dataCount; i++)
{
var axisData:AxisData = AxisData(data[i]);
if(isNaN(axisData.position))
{
//skip bad positions
continue;
}
var position:Number = axisData.position;
var nonOriginZero:Boolean =(i > 0 && axisData.value == 0 && showZeroGridLine);
if(renderer.orientation == AxisOrientation.VERTICAL)
{
if(!isNaN(lastPosition) && fillColors && fillColors.length > 0)
{
var color:uint = fillColors[(i - 1) % fillColors.length];
var alpha:Number = (fillAlphas && fillAlphas.length > 0) ? fillAlphas[(i - 1) % fillAlphas.length] : 1;
this.graphics.lineStyle(0, 0, 0);
this.graphics.beginFill(color, alpha);
this.graphics.drawRect(0, lastPosition, renderer.contentBounds.width, position - lastPosition);
this.graphics.endFill();
}
if(showLines)
{
if(nonOriginZero)
{
this.graphics.lineStyle(zeroGridLineWeight, zeroGridLineColor);
}
else
{
this.graphics.lineStyle(lineWeight, lineColor);
}
this.graphics.moveTo(0, position);
this.graphics.lineTo(renderer.contentBounds.width, position);
}
}
else
{
if(!isNaN(lastPosition) && fillColors && fillColors.length > 0)
{
color = fillColors[(i - 1) % fillColors.length];
alpha = (fillAlphas && fillAlphas.length > 0) ? fillAlphas[(i - 1) % fillAlphas.length] : 1;
this.graphics.lineStyle(0, 0, 0);
this.graphics.beginFill(color, alpha);
this.graphics.drawRect(lastPosition, 0, position - lastPosition, renderer.contentBounds.height);
this.graphics.endFill();
}
if(showLines)
{
if(nonOriginZero)
{
this.graphics.lineStyle(zeroGridLineWeight, zeroGridLineColor);
}
else
{
this.graphics.lineStyle(lineWeight, lineColor);
}
this.graphics.moveTo(position, 0);
this.graphics.lineTo(position, renderer.contentBounds.height);
}
}
lastPosition = position;
}
}
}
}

View File

@ -0,0 +1,151 @@
/*
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.axes
{
import com.yahoo.astra.fl.charts.IChart;
/**
* Data-only representation of a chart's axis.
*
* @author Josh Tynjala
*/
public interface IAxis
{
/**
* The chart in which this axis appears.
*/
function get chart():IChart;
/**
* @private
*/
function set chart(value:IChart):void;
/**
* The visual renderer associated with this axis.
*/
function get renderer():IAxisRenderer;
/**
* @private
*/
function set renderer(value:IAxisRenderer):void;
/**
* Sets the direction of the labels and other visual objects along the axis.
* By default, vertical axes draw objects from bottom to top, and horizontal
* axes draw objects from left to right.
*/
function get reverse():Boolean;
/**
* @private
*/
function set reverse(value:Boolean):void;
/**
* The text that will appear next to the axis to indicate information
* about the data that it displays.
*/
function get title():String;
/**
* @private
*/
function set title(value:String):void;
/**
* A function may be set to determine the text value of the labels.
*
* <pre>function labelFunction(value:Object):String</pre>
*/
function get labelFunction():Function;
/**
* @private
*/
function set labelFunction(value:Function):void
/**
* Gets or sets the maximum width of a label
*/
function get maxLabelWidth():Number;
/**
* @private (setter)
*/
function set maxLabelWidth(value:Number):void;
/**
* Gets or sets the maximum width of a label
*/
function get maxLabelHeight():Number;
/**
* @private (setter)
*/
function set maxLabelHeight(value:Number):void;
/**
* Data used in determining the axis scale
*/
function get dataProvider():Array;
/**
* @private (setter)
*/
function set dataProvider(value:Array):void;
/**
* @private
*/
function get numLabels():Number;
/**
* @private (setter)
*/
function set numLabels(value:Number):void;
/**
* The space, in pixels, between labels on an axis.
*/
function get labelSpacing():Number;
/**
* @private (setter)
*/
function set labelSpacing(value:Number):void;
/**
* Determines the axis scale based on the input data set.
* Seperating this function from the draw method optimizes processing time,
* and it allows the chart to synchronize its axes.
*
*/
function updateScale():void;
/**
* Calculates the position of a data point along the axis.
*
* @param value The data used to determine the position
* @return The display position in pixels on the axis
*/
function valueToLocal(value:Object):Number;
/**
* Converts a value on the axis to formatted label text.
*
* @param value the value of the item for which a label is needed
* @return the formatted label text
*/
function valueToLabel(value:Object):String;
/**
* Returns the maximum string length of a label on the axis.
*
* @return the formatted label
*/
function getMaxLabel():String;
}
}

View File

@ -0,0 +1,50 @@
/*
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.axes
{
/**
* A visual representation of an IAxis instance.
*
* Should be a subclass of UIComponent.
*
* @author Josh Tynjala
*/
public interface IAxisRenderer
{
//--------------------------------------
// Properties
//--------------------------------------
/**
* The total length of the axis renderer, in pixels.
*/
function get length():Number;
/**
* An Array of AxisData objects specifying the positions of the ticks.
*
* @see AxisData
*/
function get ticks():Array;
/**
* @private
*/
function set ticks(value:Array):void;
/**
* An Array of AxisData objects specifying the positions of the minor ticks.
*
* @see AxisData
*/
function get minorTicks():Array
/**
* @private
*/
function set minorTicks(value:Array):void;
}
}

View File

@ -0,0 +1,80 @@
/*
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.axes
{
import flash.geom.Rectangle;
/**
* Interface for a cartesian chart's axis renderers.
*
* @see com.yahoo.astra.fl.charts.CartesianChart
*/
public interface ICartesianAxisRenderer extends IAxisRenderer
{
//--------------------------------------
// Properties
//--------------------------------------
/**
* Determines if the axis is displayed vertically or horizontally.
*
* @see com.yahoo.astra.fl.charts.axes.AxisOrientation
*/
function get orientation():String;
/**
* @private
*/
function set orientation(value:String):void;
/**
* The title text to display on the axis.
*/
function get title():String;
/**
* @private
*/
function set title(value:String):void;
/**
* Represents the area where content should be drawn within the axis.
* This value is used to determine the containing chart's own
* <code>contentBounds</code> property.
*/
function get contentBounds():Rectangle;
/**
* Indicates the number of pixels of an outer tick.
*/
function get outerTickOffset():Number
/**
* @private (setter)
*/
function set outerTickOffset(value:Number):void
/**
* Indicates whether the user explicitly set a major unit for the axis of this renderer.
*/
function get majorUnitSetByUser():Boolean;
/**
* @private (setter)
*/
function set majorUnitSetByUser(value:Boolean):void;
//--------------------------------------
// Methods
//--------------------------------------
/**
* Calculates the <code>contentBounds</code> value for the axis renderer.
* Seperating this function from the draw method optimizes processing time,
* and it allows the chart to synchronize its axes.
*/
function updateAxis():void;
}
}

View File

@ -0,0 +1,30 @@
/*
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.axes
{
/**
* An axis that supports clustering. When combined with a series that
* supports clustering, the number of clusters will allow the series to
* determine the optimal positioning of markers.
*
* @author Josh Tynjala
*/
public interface IClusteringAxis extends IAxis
{
//--------------------------------------
// Properties
//--------------------------------------
/**
* The number of clusters available on the axis. In the case of the
* CategoryAxis, this is the number of category names displayed on the
* axis.
*
* @see CategoryAxis
*/
function get clusterCount():int;
}
}

View File

@ -0,0 +1,30 @@
/*
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.axes
{
/**
* A renderer for grid lines appearing on a chart's axis.
*
* @author Josh Tynjala
*/
public interface IGridLinesRenderer
{
//--------------------------------------
// Properties
//--------------------------------------
/**
* The axis renderer from which the grid lines receive their
* major and minor unit data.
*/
function get axisRenderer():IAxisRenderer;
/**
* @private
*/
function set axisRenderer(value:IAxisRenderer):void;
}
}

View File

@ -0,0 +1,26 @@
/*
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.axes
{
/**
* An axis with an origin.
*
* @author Josh Tynjala
*/
public interface IOriginAxis extends IAxis
{
/**
* Returns the value of the origin. This is not the position of the
* origin. To get the origin's position, pass the origin value to
* valueToLocal().
*
* Note: This value may not be the true origin value. It may be a
* minimum or maximum value if the actual origin is not visible.
*
* @see IAxis#valueToLocal()
*/
function get origin():Object;
}
}

View File

@ -0,0 +1,16 @@
/*
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.axes
{
/**
* Interface for a chart's axis renderers.
*
* @author Josh Tynjala
*/
public interface IRadialAxisRenderer extends IAxisRenderer
{
//nothing yet!
}
}

View File

@ -0,0 +1,49 @@
/*
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.axes
{
import com.yahoo.astra.fl.charts.series.IStackedSeries;
/**
* A type of axis that allows values to be stacked.
*
* @author Josh Tynjala
*/
public interface IStackingAxis extends IAxis
{
//--------------------------------------
// Properties
//--------------------------------------
/**
* If true, the axis will allow the stacking of series that implement
* the interface IStackedSeries.
*
* <p>Must be explicitly enabled.
*
* @see com.yahoo.astra.fl.charts.series.IStackedSeries
*/
function get stackingEnabled():Boolean;
/**
* @private
*/
function set stackingEnabled(value:Boolean):void;
//--------------------------------------
// Methods
//--------------------------------------
/**
* Calculates the sum of values if they were stacked on the axis.
* The first value is important because some axis types, such as
* NumericAxis, may differentiate between positive and negative values.
*
* @see NumericAxis
*/
function stack(top:Object, ...rest:Array):Object;
}
}

View File

@ -0,0 +1,948 @@
/*
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.axes
{
import com.yahoo.astra.fl.charts.series.ISeries;
import com.yahoo.astra.fl.utils.UIComponentUtil;
import com.yahoo.astra.utils.NumberUtil;
import com.yahoo.astra.fl.charts.CartesianChart;
import flash.utils.Dictionary;
import fl.core.UIComponent;
/**
* An axis type representing a numeric range from minimum to maximum
* with major and minor divisions.
*
* @author Josh Tynjala
*/
public class NumericAxis extends BaseAxis implements IAxis, IOriginAxis, IStackingAxis
{
//--------------------------------------
// Constructor
//--------------------------------------
/**
* Constructor.
*/
public function NumericAxis()
{
}
//--------------------------------------
// Properties
//--------------------------------------
/**
* @private
* The multiplier used to calculate the position on the renderer from an
* axis value.
*/
protected var positionMultiplier:Number = 0;
/**
* @private
* Storage for the minimum value.
*/
private var _minimum:Number = 0;
/**
* @private
* Indicates whether the minimum bound is user-defined or generated by the axis.
*/
private var _minimumSetByUser:Boolean = false;
/**
* The minimum value displayed on the axis. By default, this value is generated
* by the axis itself. If the user defines this value, the axis will skip this
* automatic generation. To enable this behavior again, set this property to NaN.
*/
public function get minimum():Number
{
return this._minimum;
}
/**
* @private
*/
public function set minimum(value:Number):void
{
this._minimum = value;
this._minimumSetByUser = !isNaN(value);
}
/**
* @private
* Storage for the maximum value.
*/
private var _maximum:Number = 100;
/**
* @private
* Indicates whether the maximum bound is user-defined or generated by the axis.
*/
private var _maximumSetByUser:Boolean = false;
/**
* The maximum value displayed on the axis. By default, this value is generated
* by the axis itself. If the user defines this value, the axis will skip this
* automatic generation. To enable this behavior again, set this property to NaN.
*/
public function get maximum():Number
{
return this._maximum;
}
/**
* @private
*/
public function set maximum(value:Number):void
{
this._maximum = value;
this._maximumSetByUser = !isNaN(value);
}
//-- Units
/**
* @private
* Storage for the major unit.
*/
private var _majorUnit:Number = 10;
/**
* @private
* Indicates whether the major unit is user-defined or generated by the axis.
*/
private var _majorUnitSetByUser:Boolean = false;
/**
* The major unit at which new ticks and labels are drawn. By default, this value
* is generated by the axis itself. If the user defines this value, the axis will
* skip the automatic generation. To enable this behavior again, set this property
* to NaN.
*/
public function get majorUnit():Number
{
return this._majorUnit;
}
/**
* @private
*/
public function set majorUnit(value:Number):void
{
this._majorUnit = value;
this._majorUnitSetByUser = !isNaN(value);
}
/**
* @private
* Storage for the minor unit.
*/
private var _minorUnit:Number = 0;
/**
* @private
* Indicates whether the minor unit is user-defined or generated by the axis.
*/
private var _minorUnitSetByUser:Boolean = false;
/**
* The minor unit at which new ticks are drawn. By default, this value
* is generated by the axis itself. If the user defines this value, the axis will
* skip the automatic generation. To enable this behavior again, set this property
* to NaN.
*/
public function get minorUnit():Number
{
return this._minorUnit;
}
/**
* @private
*/
public function set minorUnit(value:Number):void
{
this._minorUnit = value;
this._minorUnitSetByUser = !isNaN(value);
}
/**
* @inheritDoc
*/
public function get origin():Object
{
var origin:Number = 0;
if(this.scale == ScaleType.LOGARITHMIC)
{
origin = 1;
}
origin = Math.max(origin, this.minimum);
origin = Math.min(origin, this.maximum);
return origin;
}
/**
* @private
* Storage for the stackingEnabled property.
*/
private var _stackingEnabled:Boolean = false;
/**
* @inheritDoc
*/
public function get stackingEnabled():Boolean
{
return this._stackingEnabled;
}
/**
* @private
*/
public function set stackingEnabled(value:Boolean):void
{
this._stackingEnabled = value;
}
/**
* @private
* Storage for the alwaysShowZero property.
*/
private var _alwaysShowZero:Boolean = true;
/**
* If true, the axis will attempt to keep zero visible at all times.
* If both the minimum and maximum values displayed on the axis are
* above zero, the minimum will be reset to zero. If both minimum and
* maximum appear below zero, the maximum will be reset to zero. If
* the minimum and maximum appear at positive and negative values
* respectively, zero is already visible and the axis scale does not
* change.
*
* <p>This property has no affect if you manually set the minimum and
* maximum values of the axis.</p>
*/
public function get alwaysShowZero():Boolean
{
return this._alwaysShowZero;
}
/**
* @private
*/
public function set alwaysShowZero(value:Boolean):void
{
this._alwaysShowZero = value;
}
/**
* @private
* Storage for the snapToUnits property.
*/
private var _snapToUnits:Boolean = true;
/**
* If true, the labels, ticks, gridlines, and other objects will snap to
* the nearest major or minor unit. If false, their position will be based
* on the minimum value.
*/
public function get snapToUnits():Boolean
{
return this._snapToUnits;
}
/**
* @private
*/
public function set snapToUnits(value:Boolean):void
{
this._snapToUnits = value;
}
/**
* @private
* Storage for the scale property.
*/
private var _scale:String = ScaleType.LINEAR;
/**
* The type of scaling used to display items on the axis.
*
* @see com.yahoo.astra.fl.charts.ScaleType
*/
public function get scale():String
{
return this._scale;
}
/**
* @private
*/
public function set scale(value:String):void
{
this._scale = value;
}
/**
* @private
*/
private var _dataMinimum:Number = NaN;
/**
* @private
*/
private var _dataMaximum:Number = NaN;
/**
* @private
*/
private var _numLabels:Number;
/**
* @private
*/
private var _numLabelsSetByUser:Boolean = false;
/**
* @inheritDoc
*/
public function get numLabels():Number
{
return _numLabels;
}
/**
* @private (setter)
*/
public function set numLabels(value:Number):void
{
if(_numLabelsSetByUser) return;
_numLabels = value;
_numLabelsSetByUser = true;
_majorUnitSetByUser = false;
_minorUnitSetByUser = false;
}
/**
* @private
*/
private var _roundMajorUnit:Boolean = true;
/**
* Indicates whether to round the major unit
*/
public function get roundMajorUnit():Boolean
{
return _roundMajorUnit;
}
/**
* @private (setter)
*/
public function set roundMajorUnit(value:Boolean):void
{
_roundMajorUnit = value;
}
/**
* @private
* Holds value for idealPixels
*/
private var _idealPixels:Number = 70;
/**
* Desired distance between majorUnits. Used to calculate the major unit
* when unspecified and <code>calculateByLabelSize</code> is set to false.
*/
public function get idealPixels():Number
{
return _idealPixels;
}
/**
* @private (setter)
*/
public function set idealPixels(value:Number):void
{
_idealPixels = value;
}
/**
* @private
* Holds value for calculateByLabelSize
*/
private var _calculateByLabelSize:Boolean = false;
/**
* Indicates whether to use the maximum size of an axis label
* when calculating the majorUnit.
*/
public function get calculateByLabelSize():Boolean
{
return _calculateByLabelSize;
}
/**
* @private (setter)
*/
public function set calculateByLabelSize(value:Boolean):void
{
_calculateByLabelSize = value;
}
//--------------------------------------
// Public Methods
//--------------------------------------
/**
* @inheritDoc
*/
public function valueToLocal(data:Object):Number
{
if(data == null)
{
//bad data. a properly-designed renderer will not draw this.
return NaN;
}
var position:Number = 0;
if(this.scale == ScaleType.LINEAR)
{
position = (Number(data) - this.minimum) * this.positionMultiplier;
}
else
{
var logOfData:Number = Math.log(Number(data));
var logOfMinimum:Number = Math.log(this.minimum);
position = (logOfData - logOfMinimum) * this.positionMultiplier;
}
if(this.reverse)
{
position = this.renderer.length - position;
}
//the vertical axis has its origin on the bottom
if(this.renderer is ICartesianAxisRenderer && ICartesianAxisRenderer(this.renderer).orientation == AxisOrientation.VERTICAL)
{
position = this.renderer.length - position;
}
return Math.round(position);
}
/**
* @inheritDoc
*/
public function stack(top:Object, ...rest:Array):Object
{
var numericValue:Number = Number(top);
var negative:Boolean = false;
if(numericValue < 0)
{
negative = true;
}
var restCount:int = rest.length;
for(var i:int = 0; i < restCount; i++)
{
var currentValue:Number = Number(rest[i]);
if(negative && currentValue < 0)
{
numericValue += currentValue;
}
else if(!negative && currentValue > 0)
{
numericValue += currentValue;
}
}
return numericValue;
}
/**
* @inheritDoc
*/
public function updateScale():void
{
this.resetScale();
this.calculatePositionMultiplier();
(this.renderer as ICartesianAxisRenderer).majorUnitSetByUser = this._majorUnitSetByUser;
this.renderer.ticks = this.createAxisData(this.majorUnit);
this.renderer.minorTicks = this.createAxisData(this.minorUnit);
}
/**
* @inheritDoc
*/
public function getMaxLabel():String
{
var difference:Number = Math.round(this.maximum - this.minimum);
var maxString:String = this.valueToLabel(this.maximum);
var minString:String = this.valueToLabel(this.minimum);
var halfString:String = this.valueToLabel(Math.round(difference/2));
var thirdString:String = this.valueToLabel(Math.round(difference/3));
if(maxString.length < minString.length) maxString = minString;
if(halfString.length > maxString.length) maxString = halfString;
if(thirdString.length > maxString.length) maxString = thirdString;
return maxString as String;
}
//--------------------------------------
// Protected Methods
//--------------------------------------
/**
* @private
* If the minimum, maximum, major unit or minor unit have not been set by the user,
* these values must be generated by the axis. May be overridden to use custom
* scaling algorithms.
*/
protected function resetScale():void
{
//use the discovered min and max from the data
//if the developer didn't specify anything
if(!this._minimumSetByUser)
{
this._minimum = this._dataMinimum;
}
if(!this._maximumSetByUser)
{
this._maximum = this._dataMaximum;
}
this.checkMinLessThanMax();
this.pinToOrigin();
this.calculateMajorUnit();
this.adjustMinAndMaxFromMajorUnit();
this.correctLogScaleMinimum();
//ensure that min != max
if(!this._maximumSetByUser && this._minimum == this._maximum)
{
this._maximum = this._minimum + 1;
if(!this._majorUnitSetByUser)
{
//rarely happens, so I'll hardcode a nice major unit
//for our difference of one
this._majorUnit = 0.5;
}
}
this.calculateMinorUnit();
//even if they are manually set by the user, check all values for possible floating point errors.
//we don't want extra labels or anything like that!
this._minimum = NumberUtil.roundToPrecision(this._minimum, 10);
this._maximum = NumberUtil.roundToPrecision(this._maximum, 10);
this._majorUnit = NumberUtil.roundToPrecision(this._majorUnit, 10);
this._minorUnit = NumberUtil.roundToPrecision(this._minorUnit, 10);
}
/**
* @private
* Determines the best major unit.
*/
protected function calculateMajorUnit():void
{
if(this._majorUnitSetByUser)
{
return;
}
var chart:CartesianChart = this.chart as CartesianChart;
var labelSpacing:Number = 0;
var approxLabelDistance:Number = this.idealPixels;
var overflow:Number = 0;
if(this.calculateByLabelSize)
{
var rotation:Number;
//Check to see if this axis is horizontal. Since the width of labels will be variable, we will need to apply a different alogrithm to determine the majorUnit.
if(chart.horizontalAxis == this)
{
//extract the approximate width of the labels by getting the textWidth of the maximum date when rendered by the label function with the textFormat of the renderer.
approxLabelDistance = this.maxLabelWidth;
rotation = chart.getHorizontalAxisStyle("rotation") as Number;
if(rotation >= 0)
{
if(!isNaN(chart.horizontalAxisLabelData.rightLabelOffset)) overflow += chart.horizontalAxisLabelData.rightLabelOffset as Number;
}
if(rotation <= 0)
{
if(!isNaN(chart.horizontalAxisLabelData.leftLabelOffset)) overflow += chart.horizontalAxisLabelData.leftLabelOffset as Number;
}
}
else
{
approxLabelDistance = this.maxLabelHeight;
rotation = chart.getVerticalAxisStyle("rotation") as Number;
if(!isNaN(chart.verticalAxisLabelData.topLabelOffset)) overflow = chart.verticalAxisLabelData.topLabelOffset as Number;
}
labelSpacing = this.labelSpacing;
approxLabelDistance += (labelSpacing*2);
}
var difference:Number = this.maximum - this.minimum;
var tempMajorUnit:Number = 0;
var maxLabels:Number = ((this.renderer.length + overflow) - labelSpacing)/approxLabelDistance;
if(this.calculateByLabelSize)
{
maxLabels = Math.floor(maxLabels);
//Adjust the max labels to account for potential maximum and minimum adjustments that may occur.
if(!this._maximumSetByUser && !this._minimumSetByUser && !(this.alwaysShowZero && this._minimum == 0)) maxLabels -= 1;
}
//If set by user, use specified number of labels unless its too many
if(this._numLabelsSetByUser)
{
maxLabels = Math.min(maxLabels, this.numLabels);
}
tempMajorUnit = difference/maxLabels;
if(!this.calculateByLabelSize)
{
tempMajorUnit = this.niceNumber(tempMajorUnit);
}
else if(this.roundMajorUnit)
{
var order:Number = Math.ceil(Math.log(tempMajorUnit) * Math.LOG10E);
var roundedMajorUnit:Number = Math.pow(10, order);
if (roundedMajorUnit / 2 >= tempMajorUnit)
{
var roundedDiff:Number = Math.floor((roundedMajorUnit / 2 - tempMajorUnit) / (Math.pow(10,order-1)/2));
tempMajorUnit = roundedMajorUnit/2 - roundedDiff*Math.pow(10,order-1)/2;
}
else
{
tempMajorUnit = roundedMajorUnit;
}
}
if(!isNaN(tempMajorUnit)) this._majorUnit = tempMajorUnit;
}
/**
* @private
* Determines the best minor unit.
*/
protected function calculateMinorUnit():void
{
if(this._minorUnitSetByUser)
{
return;
}
var range:Number = this.maximum - this.minimum;
var majorUnitSpacing:Number = this.renderer.length * (this.majorUnit / range);
if(this._majorUnit != 1)
{
if(this._majorUnit % 2 == 0)
{
this._minorUnit = this._majorUnit / 2;
}
else if(this._majorUnit % 3 == 0)
{
this._minorUnit = this._majorUnit / 3;
}
else this._minorUnit = 0;
}
}
/**
* @private
* Creates the AxisData objects for the axis renderer.
*/
protected function createAxisData(unit:Number):Array
{
if(unit <= 0)
{
return [];
}
var data:Array = [];
var displayedMaximum:Boolean = false;
var value:Number = this.minimum;
while(value < this.maximum || NumberUtil.fuzzyEquals(value, this.maximum))
{
if(value % 1 != 0) value = NumberUtil.roundToPrecision(value, 10);
//because Flash UIComponents round the position to the nearest pixel, we need to do the same.
var position:Number = Math.round(this.valueToLocal(value));
var label:String = this.valueToLabel(value);
var axisData:AxisData = new AxisData(position, value, label);
data.push(axisData);
//if the maximum has been displayed, we're done!
if(displayedMaximum) break;
//a bad unit will get us stuck in an infinite loop
if(unit <= 0)
{
value = this.maximum;
}
else
{
value += unit;
if(this.snapToUnits && !this._minimumSetByUser && this.alwaysShowZero)
{
value = NumberUtil.roundDownToNearest(value, unit);
}
if(this._majorUnitSetByUser) value = Math.min(value, this.maximum);
}
displayedMaximum = NumberUtil.fuzzyEquals(value, this.maximum);
}
return data;
}
//--------------------------------------
// Private Methods
//--------------------------------------
/**
* @private
* If we want to always show zero, corrects the min or max as needed.
*/
private function pinToOrigin():void
{
//if we're pinned to zero, and min or max is supposed to be generated,
//make sure zero is somewhere in the range
if(this.alwaysShowZero)
{
if(!this._minimumSetByUser && this._minimum > 0 && this._maximum > 0)
{
this._minimum = 0;
}
else if(!this._maximumSetByUser && this._minimum < 0 && this._maximum < 0)
{
this._maximum = 0;
}
}
}
/**
* @private
* Increases the maximum and decreases the minimum based on the major unit.
*/
private function adjustMinAndMaxFromMajorUnit():void
{
//adjust the maximum so that it appears on a major unit
//but don't change the maximum if the user set it or it is pinned to zero
if(!this._maximumSetByUser && !(this.alwaysShowZero && this._maximum == 0))
{
var oldMaximum:Number = this._maximum;
if(this._minimumSetByUser)
{
//if the user sets the minimum, we need to ensure that the maximum is an increment of the major unit starting from
//the minimum instead of zero
this._maximum = NumberUtil.roundUpToNearest(this._maximum - this._minimum, this._majorUnit);
this._maximum += this._minimum;
}
else
{
this._maximum = NumberUtil.roundUpToNearest(this._maximum, this._majorUnit);
}
//uncomment to include an additional major unit in this adjustment
if(this._maximum == oldMaximum /*|| this._maximum - oldMaximum < this._majorUnit */)
{
this._maximum += this._majorUnit;
}
}
//adjust the minimum so that it appears on a major unit
//but don't change the minimum if the user set it or it is pinned to zero
if(!this._minimumSetByUser && !(this.alwaysShowZero && this._minimum == 0))
{
var oldMinimum:Number = this._minimum;
this._minimum = NumberUtil.roundDownToNearest(this._minimum, this._majorUnit);
//uncomment to include an additional major unit in this adjustment
if(this._minimum == oldMinimum /*|| oldMinimum - this._minimum < this._majorUnit*/)
{
this._minimum -= this._majorUnit;
}
}
}
/**
* @private
* If we're using logarithmic scale, corrects the minimum if it gets set
* to a value <= 0.
*/
private function correctLogScaleMinimum():void
{
//logarithmic scale can't have a minimum value <= 0. If that's the case, push it up to 1.0
//TODO: Determine if there's a better way to handle this...
if(!this._minimumSetByUser && this.scale == ScaleType.LOGARITHMIC && this._minimum <= 0)
{
//use the dataMinimum if it's between 0 and 1
//otherwise, just use 1
if(this._dataMinimum > 0 && this._dataMinimum < 1)
{
this._minimum = this._dataMinimum;
}
else
{
this._minimum = 1;
}
}
}
/**
* @private
* Calculates a "nice" number for use with major or minor units
* on the axis. Only returns numbers similar to 10, 20, 25, and 50.
*/
private function niceNumber(value:Number):Number
{
if(value == 0)
{
return 0;
}
var count:int = 0;
while(value > 10.0e-8)
{
value /= 10;
count++;
}
//all that division in the while loop up there
//could cause rounding errors. Don't you hate that?
value = NumberUtil.roundToPrecision(value, 10);
if(value > 4.0e-8)
{
value = 5.0e-8;
}
else if(value > 2.0e-8)
{
value = 2.5e-8;
}
else if(value > 1.0e-8)
{
value = 2.0e-8;
}
else
{
value = 1.0e-8;
}
for(var i:int = count; i > 0; i--)
{
value *= 10;
}
return value;
}
/**
* @private
* Swaps the minimum and maximum values, if needed.
*/
private function checkMinLessThanMax():void
{
if(this._minimum > this._maximum)
{
var temp:Number = this._minimum;
this._minimum = this._maximum;
this._maximum = temp;
//be sure to swap these flags too!
var temp2:Boolean = this._minimumSetByUser;
this._minimumSetByUser = this._maximumSetByUser;
this._maximumSetByUser = temp2;
}
}
/**
* @private
* Calculates the multiplier used to convert a data point to an actual position
* on the axis.
*/
private function calculatePositionMultiplier():void
{
var range:Number = this.maximum - this.minimum;
if(this.scale == ScaleType.LOGARITHMIC)
{
range = Math.log(this.maximum) - Math.log(this.minimum);
}
if(range == 0)
{
this.positionMultiplier = 0;
return;
}
this.positionMultiplier = this.renderer.length / range;
}
/**
* @private
*/
override protected function parseDataProvider():void
{
var seriesCount:int = this.dataProvider.length;
var dataMinimum:Number = NaN;
var dataMaximum:Number = NaN;
for(var i:int = 0; i < seriesCount; i++)
{
var series:ISeries = this.dataProvider[i] as ISeries;
var seriesLength:int = series.length;
for(var j:int = 0; j < seriesLength; j++)
{
var item:Object = series.dataProvider[j];
if(item === null)
{
continue;
}
//automatically calculates stacked values
var value:Number = Number(this.chart.itemToAxisValue(series, j, this));
if(isNaN(value))
{
continue; //skip bad data
}
//don't let bad data propogate
//Math.min()/Math.max() with a NaN argument will choose NaN. Ya Rly.
dataMinimum = isNaN(dataMinimum) ? value : Math.min(dataMinimum, value);
dataMaximum = isNaN(dataMaximum) ? value : Math.max(dataMaximum, value);
}
}
if(!isNaN(dataMinimum) && !isNaN(dataMaximum))
{
this._dataMinimum = dataMinimum;
this._dataMaximum = dataMaximum;
}
else
{
//some sensible defaults
this._dataMinimum = 0;
this._dataMaximum = 1;
}
if(!this._minimumSetByUser)
{
this._minimum = this._dataMinimum;
}
if(!this._maximumSetByUser)
{
this._maximum = this._dataMaximum;
}
}
}
}

View File

@ -0,0 +1,273 @@
/*
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.axes
{
import com.yahoo.astra.utils.GeomUtil;
import com.yahoo.astra.utils.NumberUtil;
import fl.core.UIComponent;
import flash.geom.Point;
//TODO: Add support for labels.
/**
* The default axis renderer for radial axes.
*
* @author Josh Tynjala
*/
public class RadialAxisRenderer extends UIComponent implements IRadialAxisRenderer
{
//--------------------------------------
// Class Variables
//--------------------------------------
/**
* @private
*/
private static var defaultStyles:Object =
{
//axis
showAxis: true,
axisWeight: 1,
axisColor: 0x888a85,
//ticks
showTicks: true,
tickWeight: 1,
tickColor: 0x888a85,
tickLength: 4,
tickPosition: TickPosition.INSIDE,
//minor ticks
showMinorTicks: true,
minorTickWeight: 1,
minorTickColor: 0x888a85,
minorTickLength: 3,
minorTickPosition: TickPosition.INSIDE
};
//--------------------------------------
// Class Methods
//--------------------------------------
/**
* @copy fl.core.UIComponent#getStyleDefinition()
*/
public static function getStyleDefinition():Object
{
return mergeStyles(defaultStyles, UIComponent.getStyleDefinition());
}
//--------------------------------------
// Constructor
//--------------------------------------
/**
* Constructor.
*/
public function RadialAxisRenderer()
{
super();
}
//--------------------------------------
// Properties
//--------------------------------------
/**
* @inheritDoc
*/
public function get length():Number
{
return Math.min(this.width, this.height) * Math.PI;
}
/**
* @private
* Storage for the ticks property.
*/
private var _ticks:Array = [];
/**
* @inheritDoc
*/
public function get ticks():Array
{
return this._ticks;
}
/**
* @private
*/
public function set ticks(value:Array):void
{
this._ticks = value;
this.invalidate();
}
/**
* @private
* Storage for the minorTicks property.
*/
private var _minorTicks:Array = [];
/**
* @inheritDoc
*/
public function get minorTicks():Array
{
return this._minorTicks;
}
/**
* @private
*/
public function set minorTicks(value:Array):void
{
this._minorTicks = value;
this.invalidate();
}
//--------------------------------------
// Public Methods
//--------------------------------------
/**
* @inheritDoc
*/
public function updateBounds():void
{
//no labels are created at this time, so this function is pretty useless
}
//--------------------------------------
// Protected Methods
//--------------------------------------
/**
* @private
*/
override protected function draw():void
{
var showTicks:Boolean = this.getStyleValue("showTicks") as Boolean;
var showMinorTicks:Boolean = this.getStyleValue("showMinorTicks") as Boolean;
var ticks:Array = this.ticks.concat();
var minorTicks:Array = this.minorTicks.concat();
if(showMinorTicks && showTicks)
{
//filter out minor ticks that appear at the same position
//as major ticks.
minorTicks = minorTicks.filter(function(item:AxisData, index:int, source:Array):Boolean
{
return !ticks.some(function(item2:AxisData, index2:int, source2:Array):Boolean
{
//using fuzzyEquals because we may encounter rounding errors
return NumberUtil.fuzzyEquals(item.position, item2.position, 10);
});
});
}
this.graphics.clear();
this.drawAxis();
var tickPosition:String = this.getStyleValue("tickPosition") as String;
var tickLength:Number = this.getStyleValue("tickLength") as Number;
var tickWeight:int = this.getStyleValue("tickWeight") as int;
var tickColor:uint = this.getStyleValue("tickColor") as uint;
this.drawTicks(ticks, showTicks, tickPosition, tickLength, tickWeight, tickColor);
var minorTickPosition:String = this.getStyleValue("minorTickPosition") as String;
var minorTickLength:Number = this.getStyleValue("minorTickLength") as Number;
var minorTickWeight:int = this.getStyleValue("minorTickWeight") as int;
var minorTickColor:uint = this.getStyleValue("minorTickColor") as uint;
this.drawTicks(minorTicks, showMinorTicks, minorTickPosition, minorTickLength, minorTickWeight, minorTickColor);
super.draw();
}
/**
* @private
* Draws the main axis line.
*/
protected function drawAxis():void
{
var showAxis:Boolean = this.getStyleValue("showAxis") as Boolean;
if(!showAxis)
{
return;
}
var axisWeight:int = this.getStyleValue("axisWeight") as int;
var axisColor:uint = this.getStyleValue("axisColor") as uint;
this.graphics.lineStyle(axisWeight, axisColor);
var center:Point = new Point(this.width / 2, this.height / 2);
var radius:Number = Math.min(center.x, center.y);
this.graphics.drawCircle(center.x, center.y, radius);
}
/**
* @private
* Draws a set of ticks along the main axis line. This function is shared
* by major and minor ticks.
*/
protected function drawTicks(data:Array, showTicks:Boolean, tickPosition:String,
tickLength:Number, tickWeight:Number, tickColor:uint):void
{
if(!showTicks)
{
return;
}
this.graphics.lineStyle(tickWeight, tickColor);
var center:Point = new Point(this.width / 2, this.height / 2);
var radius:Number = Math.min(center.x, center.y);
var dataCount:int = data.length;
for(var i:int = 0; i < dataCount; i++)
{
var axisData:AxisData = AxisData(data[i]);
if(isNaN(axisData.position))
{
//skip bad positions
continue;
}
var position:Number = axisData.position;
var angle:Number = GeomUtil.degreesToRadians(position * 360 / this.length);
var tickCenter:Point = Point.polar(radius, angle);
tickCenter = tickCenter.add(center);
switch(tickPosition)
{
case TickPosition.OUTSIDE:
var outsideEnd:Point = Point.polar(tickLength, angle);
outsideEnd = outsideEnd.add(tickCenter);
this.graphics.moveTo(tickCenter.x, tickCenter.y);
this.graphics.lineTo(outsideEnd.x, outsideEnd.y);
break;
case TickPosition.INSIDE:
var insideEnd:Point = Point.polar(tickLength, GeomUtil.degreesToRadians(180 + GeomUtil.radiansToDegrees(angle)));
insideEnd = insideEnd.add(tickCenter);
this.graphics.moveTo(tickCenter.x, tickCenter.y);
this.graphics.lineTo(insideEnd.x, insideEnd.y);
break;
default: //CROSS
outsideEnd = Point.polar(tickLength / 2, angle);
outsideEnd = outsideEnd.add(tickCenter);
insideEnd = Point.polar(tickLength / 2, GeomUtil.degreesToRadians(180 + GeomUtil.radiansToDegrees(angle)));
insideEnd = insideEnd.add(tickCenter);
this.graphics.moveTo(outsideEnd.x, outsideEnd.y);
this.graphics.lineTo(insideEnd.x, insideEnd.y);
break;
}
}
}
}
}

View File

@ -0,0 +1,31 @@
/*
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.axes
{
/**
* Scale types available to <code>IAxis</code> objects.
*
* @author Josh Tynjala
*/
public class ScaleType
{
//--------------------------------------
// Constants
//--------------------------------------
/**
* The ScaleType.LINEAR constant specifies that chart axis objects
* should be displayed on a linear scale.
*/
public static const LINEAR:String = "linear";
/**
* The ScaleType.LOGARITHMIC constant specifies that chart axis objects
* should be displayed on a logarithmic scale.
*/
public static const LOGARITHMIC:String = "logarithmic";
}
}

View File

@ -0,0 +1,37 @@
/*
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.axes
{
/**
* Position values available to axis ticks.
*
* @author Josh Tynjala
*/
public class TickPosition
{
//--------------------------------------
// Constants
//--------------------------------------
/**
* The TickPosition.OUTSIDE constant specifies that chart axis ticks
* should be displayed on the outside of the axis.
*/
public static const OUTSIDE:String = "outside";
/**
* The TickPosition.INSIDE constant specifies display of chart axis
* ticks should be displayed on the inside of the axis.
*/
public static const INSIDE:String = "inside";
/**
* The TickPosition.CROSS constant specifies display of chart axis ticks
* should be displayed crossing the axis.
*/
public static const CROSS:String = "cross";
}
}

File diff suppressed because it is too large Load Diff