948 lines
24 KiB
ActionScript
Executable File
948 lines
24 KiB
ActionScript
Executable File
/*
|
|
Copyright (c) 2009 Yahoo! Inc. All rights reserved.
|
|
The copyrights embodied in the content of this file are licensed under the BSD (revised) open source license
|
|
*/
|
|
package com.yahoo.astra.fl.charts.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;
|
|
}
|
|
}
|
|
}
|
|
} |