first commit
This commit is contained in:
948
com/yahoo/astra/fl/charts/axes/NumericAxis.as
Executable file
948
com/yahoo/astra/fl/charts/axes/NumericAxis.as
Executable 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user