| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // Licensed to the Apache Software Foundation (ASF) under one or more |
| // contributor license agreements. See the NOTICE file distributed with |
| // this work for additional information regarding copyright ownership. |
| // The ASF licenses this file to You under the Apache License, Version 2.0 |
| // (the "License"); you may not use this file except in compliance with |
| // the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package mx.charts |
| { |
| |
| import flash.events.Event; |
| |
| import mx.charts.chartClasses.AxisLabelSet; |
| import mx.charts.chartClasses.NumericAxis; |
| import mx.core.mx_internal; |
| |
| use namespace mx_internal; |
| |
| /** |
| * The LogAxis class maps numeric values logarithmically |
| * between a minimum and maximum value along a chart axis. |
| * By default, it determines <code>minimum</code>, <code>maximum</code>, |
| * and <code>interval</code> values from the charting data |
| * to fit all of the chart elements on the screen. |
| * You can also explicitly set specific values for these properties. |
| * A LogAxis object cannot correctly render negative values, |
| * as Log10() of a negative number is <code>undefined</code>. |
| * |
| * @mxml |
| * |
| * <p>The <code><mx:LogAxis></code> tag inherits all the properties |
| * of its parent classes and adds the following properties:</p> |
| * |
| * <pre> |
| * <mx:LogAxis |
| * <strong>Properties</strong> |
| * interval="10" |
| * maximum="null" |
| * maximumLabelPrecision="null" |
| * minimum="null" |
| * /> |
| * </pre> |
| * |
| * @see mx.charts.chartClasses.IAxis |
| * |
| * @includeExample examples/LogAxisExample.mxml |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public class LogAxis extends NumericAxis |
| { |
| include "../core/Version.as"; |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Constructor |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * Constructor. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function LogAxis() |
| { |
| super(); |
| |
| computedInterval = 1; |
| } |
| |
| //------------------------------------------------------------------------- |
| // |
| // Variables |
| // |
| //------------------------------------------------------------------------- |
| |
| private var _actualAssignedMaximum:Number; |
| |
| private var _actualAssignedMinimum:Number; |
| |
| //----------------------------------------------------------------------- |
| // |
| // Overridden properties |
| // |
| //----------------------------------------------------------------------- |
| |
| //------------------------------------------ |
| // direction |
| //------------------------------------------ |
| [Inspectable(category="General", enumeration="normal,inverted", defaultValue="normal")] |
| /** |
| * @private |
| */ |
| override public function set direction(value:String):void |
| { |
| if(value == "inverted") |
| { |
| if(!(isNaN(_actualAssignedMaximum))) |
| { |
| computedMinimum = -Math.ceil(Math.log(_actualAssignedMaximum) / Math.LN10); |
| assignedMinimum = computedMinimum; |
| } |
| if(!(isNaN(_actualAssignedMinimum))) |
| { |
| computedMaximum = -Math.floor(Math.log(_actualAssignedMinimum) / Math.LN10) |
| assignedMaximum = computedMaximum; |
| } |
| } |
| else |
| { |
| if(!(isNaN(_actualAssignedMaximum))) |
| { |
| computedMaximum = Math.ceil(Math.log(_actualAssignedMaximum) / Math.LN10); |
| assignedMaximum = computedMaximum; |
| } |
| if(!(isNaN(_actualAssignedMinimum))) |
| { |
| computedMinimum = Math.floor(Math.log(_actualAssignedMinimum) / Math.LN10); |
| assignedMinimum = computedMinimum; |
| } |
| |
| } |
| super.direction = value; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // |
| // Properties |
| // |
| //---------------------------------------------------------------------------- |
| |
| //---------------------------------- |
| // interval |
| //---------------------------------- |
| |
| [Inspectable(category="General")] |
| |
| /** |
| * Specifies the multiplier label values along the axis. |
| * A value of 10 generates labels at 1, 10, 100, 1000, etc., |
| * while a value of 100 generates labels at 1, 100, 10000, etc. |
| * Flex calculates the interval if this property |
| * is set to <code>NaN</code>. |
| * Intervals must be even powers of 10, and must be greater than or equal to 10. |
| * The LogAxis rounds the interval down to an even power of 10, if necessary. |
| * |
| * @default 10 |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get interval():Number |
| { |
| return Math.pow(10, computedInterval); |
| } |
| |
| /** |
| * @private |
| */ |
| public function set interval(value:Number):void |
| { |
| if (!isNaN(value)) |
| value = Math.max(1, Math.floor(Math.log(value) / Math.LN10)); |
| |
| if (isNaN(value)) |
| value = 1; |
| |
| computedInterval = value; |
| invalidateCache(); |
| |
| dispatchEvent(new Event("axisChange")); |
| } |
| |
| //---------------------------------- |
| // maximum |
| //---------------------------------- |
| |
| [Inspectable(category="General")] |
| |
| /** |
| * Specifies the maximum value for an axis label. |
| * If <code>NaN</code>, Flex determines the maximum value |
| * from the data in the chart. |
| * The maximum value must be an even power of 10. |
| * The LogAxis rounds an explicit maximum value |
| * up to an even power of 10, if necessary. |
| * |
| * @default NaN |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get maximum():Number |
| { |
| if(direction == "inverted") |
| return Math.pow(10, -computedMinimum); |
| else |
| return Math.pow(10, computedMaximum); |
| } |
| |
| /** |
| * @private |
| */ |
| public function set maximum(value:Number):void |
| { |
| if(direction == "inverted") |
| { |
| computedMinimum = -Math.ceil(Math.log(value) / Math.LN10); |
| assignedMinimum = computedMinimum; |
| } |
| else |
| { |
| computedMaximum = Math.ceil(Math.log(value) / Math.LN10); |
| assignedMaximum = computedMaximum; |
| } |
| _actualAssignedMaximum = value; |
| invalidateCache(); |
| |
| dispatchEvent(new Event("mappingChange")); |
| dispatchEvent(new Event("axisChange")); |
| } |
| |
| //---------------------------------- |
| // maximumLabelPrecision |
| //---------------------------------- |
| |
| /** |
| * @private |
| */ |
| private var _maximumLabelPrecision:Number; |
| |
| /** |
| * @private |
| */ |
| public function get maximumLabelPrecision():Number |
| { |
| return _maximumLabelPrecision; |
| } |
| |
| /** |
| * Specifies the maximum number of decimal places for representing fractional |
| * values on the labels generated by this axis. By default, the |
| * Axis autogenerates this value from the labels themselves. A value of 0 |
| * round to the nearest integer value, while a value of 2 rounds to the nearest |
| * 1/100th of a value. |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function set maximumLabelPrecision(value:Number):void |
| { |
| _maximumLabelPrecision = value; |
| |
| invalidateCache(); |
| } |
| |
| //---------------------------------- |
| // minimum |
| //---------------------------------- |
| |
| [Inspectable(category="General")] |
| |
| /** |
| * Specifies the minimum value for an axis label. |
| * If <code>NaN</code>, Flex determines the minimum value |
| * from the data in the chart. |
| * The minimum value must be an even power of 10. |
| * The LogAxis will round an explicit minimum value |
| * downward to an even power of 10 if necessary. |
| * |
| * @default NaN |
| * |
| * @langversion 3.0 |
| * @playerversion Flash 9 |
| * @playerversion AIR 1.1 |
| * @productversion Flex 3 |
| */ |
| public function get minimum():Number |
| { |
| if(direction == "inverted") |
| return Math.pow(10, -computedMaximum); |
| else |
| return Math.pow(10, computedMinimum); |
| } |
| |
| /** |
| * @private |
| */ |
| public function set minimum(value:Number):void |
| { |
| if(direction == "inverted") |
| { |
| if (value == 0) |
| assignedMaximum = NaN; |
| else |
| { |
| assignedMaximum = -(Math.floor(Math.log(value) / Math.LN10)); |
| } |
| computedMaximum = assignedMaximum; |
| } |
| else |
| { |
| if (value == 0) |
| assignedMinimum = NaN; |
| else |
| { |
| assignedMinimum = Math.floor(Math.log(value) / Math.LN10); |
| } |
| computedMinimum = assignedMinimum; |
| } |
| _actualAssignedMinimum = value; |
| invalidateCache(); |
| |
| dispatchEvent(new Event("mappingChange")); |
| dispatchEvent(new Event("axisChange")); |
| } |
| |
| //-------------------------------------------------------------------------- |
| // |
| // Overridden methods: NumericAxis |
| // |
| //-------------------------------------------------------------------------- |
| |
| /** |
| * @private |
| */ |
| override protected function adjustMinMax(minValue:Number, |
| maxValue:Number):void |
| { |
| // esg: We always floor to the nearest power of 10 |
| //if (autoAdjust && isNaN(assignedMinimum)) |
| computedMinimum = Math.floor(computedMinimum); |
| |
| // esg: We always ceil o the nearest power of 10 |
| //if (autoAdjust && isNaN(assignedMaximum)) |
| computedMaximum = Math.ceil(computedMaximum); |
| } |
| |
| /** |
| * @private |
| */ |
| override public function mapCache(cache:Array /* of ChartItem */, field:String, |
| convertedField:String, |
| indexValues:Boolean = false):void |
| { |
| const ln10:Number = Math.LN10; |
| |
| var n:int = cache.length; |
| var i:int; |
| var v:Object; |
| var vf:Number; |
| var parseFunction:Function = this.parseFunction; |
| |
| if (parseFunction != null) |
| { |
| for (i = 0; i < n; i++) |
| { |
| v = cache[i]; |
| if(direction == "inverted") |
| v[convertedField] = -(Math.log(-(Number(parseFunction(v[field])))) / ln10); |
| else |
| v[convertedField] = Math.log(Number(parseFunction(v[field]))) / ln10; |
| } |
| } |
| else |
| { |
| for (i = 0; i < n && cache[i][field] == null; i++) |
| { |
| } |
| |
| if (i == n) |
| return; |
| |
| if (cache[i][field] is String) |
| { |
| for (i = 0; i < n; i++) |
| { |
| v = cache[i]; |
| if(direction == "inverted") |
| v[convertedField] = -(Math.log(-(Number(v[field]))) / ln10); |
| else |
| v[convertedField] = Math.log(Number(v[field]) / ln10); |
| } |
| } |
| else if (cache[i][field] is XML || |
| cache[i][field] is XMLList) |
| { |
| for (i = 0; i < n; i++) |
| { |
| v = cache[i]; |
| if(direction == "inverted") |
| v[convertedField] = -(Math.log(-(Number(v[field].toString()))) / ln10); |
| else |
| v[convertedField] = Math.log(Number(v[field].toString())) / ln10; |
| } |
| } |
| else if (cache[i][field] is Number || |
| cache[i][field] is int || |
| cache[i][field] is uint) |
| { |
| for (i = 0; i < n; i++) |
| { |
| v = cache[i]; |
| if(direction == "inverted") |
| v[convertedField] = v[field] == null ? |
| NaN : |
| -(Math.log(-(v[field])) / ln10); |
| else |
| v[convertedField] = v[field] == null ? |
| NaN : |
| Math.log(v[field]) / ln10; |
| } |
| } |
| else |
| { |
| for (i = 0; i < n; i++) |
| { |
| v = cache[i]; |
| if(direction == "inverted") |
| v[convertedField] = -(Math.log(-(parseFloat(v[field]))) / ln10); |
| else |
| v[convertedField] = Math.log(parseFloat(v[field])) / ln10; |
| } |
| } |
| } |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function buildLabelCache():Boolean |
| { |
| if (labelCache) |
| return false; |
| |
| labelCache = []; |
| |
| var r:Number = computedMaximum - computedMinimum; |
| |
| var labelBase:Number = labelMinimum - |
| Math.floor((labelMinimum - computedMinimum) / computedInterval) * |
| computedInterval; |
| |
| var labelTop:Number = computedMaximum + 0.000001 |
| |
| var labelFunction:Function = this.labelFunction; |
| |
| var i:Number; |
| var v:Number; |
| var roundedValue:Number; |
| |
| var roundBase:Number; |
| if (!isNaN(_maximumLabelPrecision)) |
| roundBase = Math.pow(10, _maximumLabelPrecision); |
| |
| if (labelFunction != null) |
| { |
| var previousValue:Number = NaN; |
| for (i = labelBase; i <= labelTop; i += computedInterval) |
| { |
| if(direction == "inverted") |
| v = Math.pow(10, -i); |
| else |
| v = Math.pow(10, i); |
| |
| roundedValue = isNaN(_maximumLabelPrecision) ? |
| v : |
| Math.round(v * roundBase) / roundBase; |
| |
| labelCache.push(new AxisLabel((i - computedMinimum) / r, v, |
| labelFunction(roundedValue, previousValue, this))); |
| |
| previousValue = v; |
| } |
| } |
| else |
| { |
| for (i = labelBase; i <= labelTop; i += computedInterval) |
| { |
| if(direction == "inverted") |
| v = Math.pow(10, -i); |
| else |
| v = Math.pow(10, i); |
| |
| roundedValue = isNaN(_maximumLabelPrecision) ? |
| v : |
| Math.round(v * roundBase) / roundBase; |
| |
| labelCache.push(new AxisLabel((i - computedMinimum)/r, v, |
| roundedValue.toString())); |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function buildMinorTickCache():Array /* of Number */ |
| { |
| var cache:Array /* of Number */ = []; |
| |
| var n:int = labelCache.length; |
| for (var i:int = 0; i < n; i++) |
| { |
| cache.push(labelCache[i].position); |
| } |
| |
| return cache; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function reduceLabels(intervalStart:AxisLabel, |
| intervalEnd:AxisLabel):AxisLabelSet |
| { |
| var intervalMultiplier:Number = |
| Math.round((Math.log(Number(intervalEnd.value)) / Math.LN10) - |
| Math.log(Number(intervalStart.value)) / Math.LN10); |
| intervalMultiplier = |
| Math.floor(intervalMultiplier / computedInterval) + 1; |
| |
| var labels:Array /* of AxisLabel */ = []; |
| var newMinorTicks:Array /* of Number */ = []; |
| var newTicks:Array /* of Number */ = []; |
| |
| var r:Number = computedMaximum - computedMinimum; |
| |
| var labelBase:Number = labelMinimum - |
| Math.floor((labelMinimum - computedMinimum) / computedInterval) * |
| computedInterval; |
| |
| var labelTop:Number = computedMaximum + 0.000001 |
| |
| var n:int = labelCache.length; |
| for (var i:int = 0; i < n; i += intervalMultiplier) |
| { |
| var ci:AxisLabel = labelCache[i]; |
| labels.push(ci); |
| newTicks.push(ci.position); |
| newMinorTicks.push(ci.position); |
| } |
| |
| var labelSet:AxisLabelSet = new AxisLabelSet(); |
| labelSet.labels = labels; |
| labelSet.minorTicks = newMinorTicks; |
| labelSet.ticks = newTicks; |
| labelSet.accurate = true; |
| return labelSet; |
| } |
| |
| /** |
| * @private |
| */ |
| override public function invertTransform(value:Number):Object |
| { |
| update(); |
| |
| return Math.pow(10, |
| value * (computedMaximum - computedMinimum) + computedMinimum); |
| } |
| |
| /** |
| * @private |
| */ |
| override protected function guardMinMax(min:Number, max:Number):Array /* of int */ |
| { |
| if (isNaN(min) || !isFinite(min)) |
| min = 0; |
| if (isNaN(max) || !isFinite(max)) |
| max = min + 2; |
| if (max == min) |
| max = min + 2; |
| |
| return [min,max]; |
| } |
| } |
| |
| } |